This commit is contained in:
wea_ondara
2022-12-20 17:33:29 +01:00
committed by apucher
parent c0f7b919f7
commit b805eed622
34 changed files with 2319 additions and 35 deletions

View File

@@ -1,13 +1,30 @@
package jef.platform.mysql;
import jef.MigrationException;
import jef.expressions.Expression;
import jef.model.DbContext;
import jef.model.DbField;
import jef.model.DbFieldBuilder;
import jef.model.ModelBuilder;
import jef.platform.base.Database;
import jef.platform.base.DatabaseOptions;
import jef.platform.mysql.query.MysqlQueryBuilder;
import jef.serializable.SerializableObject;
import jef.util.Util;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class MysqlDatabase extends Database {
private final MysqlPlatform platform;
public MysqlDatabase(Connection connection, DatabaseOptions options, MysqlPlatform platform) {
super(connection, options);
this.platform = platform;
@@ -17,4 +34,300 @@ public class MysqlDatabase extends Database {
public void migrate() throws MigrationException {
platform.getMigrationApplier(connection, options).migrate();
}
@Override
public <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException {
MysqlQueryBuilder queryBuilder = new MysqlQueryBuilder();
queryBuilder.visit(expression);
return queryRaw(context, clazz, queryBuilder.getQuery());
}
@Override
public <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException {
var modelBuilder = ModelBuilder.from(context.getClass());
var entity = modelBuilder.entity(clazz);
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
for (DbFieldBuilder<?> field : entity.fields()) {
Field f = field.getField().getField();
if (f == null) {
if (field.getField().isDatabaseField()) {
columns.add(field.getField().getName());
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
}
continue;
}
if (!field.getField().isDatabaseField()) {
continue;
}
f.setAccessible(true);
columns.add(field.getField().getName());
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var columnsString = columns.stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
var placeholdersString = columns.stream().map(e -> "?").collect(Collectors.joining(","));
var query = "INSERT INTO `" + entity.name() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
var i = 1;
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
try {
valueBinder.accept(stmt, i++);
} catch (Throwable e) {
if (e instanceof SQLException) {
throw (SQLException) e;
}
throw new IllegalStateException("This may not happen", e);
}
}
var added = stmt.executeUpdate();
if (added != 1) {
throw new SQLException("Failed to insert entity");
}
try (var res = stmt.getGeneratedKeys()) {
var primary = entity.getEntity().getPrimaryKey();
if (primary != null) {
if (!res.next()) {
throw new SQLException("Failed to add entity");
}
var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
idField.getField().setAccessible(true);
if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
idField.getField().set(model, res.getInt(1));
} else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
idField.getField().set(model, res.getLong(1));
} else {
throw new UnsupportedOperationException();
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
@Override
public <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException {
var modelBuilder = ModelBuilder.from(context.getClass());
var entity = modelBuilder.entity(clazz);
if (entity.getEntity().getPrimaryKey() == null) {
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
}
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
for (DbFieldBuilder<?> field : entity.fields()) {
Field f = field.getField().getField();
if (f == null) {
if (field.getField().isDatabaseField()) {
columns.add(field.getField().getName());
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
}
continue;
}
if (!field.getField().isDatabaseField()) {
continue;
}
f.setAccessible(true);
columns.add(field.getField().getName());
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var updatesString = columns.stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(", "));
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
//add values for where clause
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
Field f = field.getField();
f.setAccessible(true);
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var query = "UPDATE `" + entity.name() + "` SET " + updatesString + " WHERE " + whereString;
System.out.println(query);
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
var i = 1;
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
try {
valueBinder.accept(stmt, i++);
} catch (Throwable e) {
if (e instanceof SQLException) {
throw (SQLException) e;
}
throw new IllegalStateException("This may not happen", e);
}
}
var updated = stmt.executeUpdate();
if (updated != 1) {
throw new SQLException("Failed to update entity");
}
try (var res = stmt.getGeneratedKeys()) {
// var primary = entity.getEntity().getPrimaryKey();
// if (primary != null) {
// if (!res.next()) {
// throw new SQLException("Failed to add entity");
// }
// var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
// idField.getField().setAccessible(true);
// if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
// idField.getField().set(model, res.getInt(1));
// } else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
// idField.getField().set(model, res.getLong(1));
// } else {
// throw new UnsupportedOperationException();
// }
// }
// } catch (IllegalAccessException e) {
// throw new RuntimeException(e);
}
}
}
@Override
public <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException {
var modelBuilder = ModelBuilder.from(context.getClass());
var entity = modelBuilder.entity(clazz);
if (entity.getEntity().getPrimaryKey() == null) {
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
}
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
//add values for where clause
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
Field f = field.getField();
f.setAccessible(true);
try {
if (f.get(model) == null) {
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
} else if (field.getField().getType() == String.class) {
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
throw new UnsupportedOperationException("char");
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
} else {
throw new UnsupportedOperationException();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
var query = "DELETE FROM `" + entity.name() + "` WHERE " + whereString;
System.out.println(query);
try (var stmt = connection.prepareStatement(query)) {
var i = 1;
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
try {
valueBinder.accept(stmt, i++);
} catch (Throwable e) {
if (e instanceof SQLException) {
throw (SQLException) e;
}
throw new IllegalStateException("This may not happen", e);
}
}
var deleted = stmt.executeUpdate();
if (deleted != 1) {
throw new SQLException("Failed to delete entity");
}
}
}
}

View File

@@ -0,0 +1,423 @@
package jef.platform.mysql.dbinterface;
import jef.asm.access.model.ClassDescription;
import jef.asm.access.model.GetterDescription;
import jef.asm.access.model.SetterDescription;
import jef.model.DbField;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public abstract class ByteCodeHelper {
public final static Type T_BOOL = Type.getType(boolean.class);
public final static Type T_BYTE = Type.getType(byte.class);
public final static Type T_CHAR = Type.getType(char.class);
public final static Type T_CLASS = Type.getType(Class.class);
public final static Type T_CONNECTION = Type.getType(Connection.class);
public final static Type T_DOUBLE = Type.getType(double.class);
public final static Type T_FIELD = Type.getType(Field.class);
public final static Type T_FLOAT = Type.getType(float.class);
public final static Type T_INT = Type.getType(int.class);
public final static Type T_LONG = Type.getType(long.class);
public final static Type T_MYSQL_ENTITY_DB_INTERFACE = Type.getType(MysqlEntityDbInterface.class);
public final static Type T_OBJECT = Type.getType(Object.class);
public final static Type T_OVERRIDE = Type.getType(Override.class);
public final static Type T_PREPARED_STATEMENT = Type.getType(PreparedStatement.class);
public final static Type T_RESULT_SET = Type.getType(ResultSet.class);
public final static Type T_SHORT = Type.getType(short.class);
public final static Type T_SQLEXCEPTION = Type.getType(SQLException.class);
public final static Type T_STRING = Type.getType(String.class);
public final static Type T_VOID = Type.getType(void.class);
//region getter
private static void visitGetFieldDirectAccess(MethodVisitor mv, Field field, int varIndexEntity) {
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
mv.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(), Type.getInternalName(field.getType()));
}
private static void visitGetter(MethodVisitor mv, GetterDescription getter, int varIndexEntity) {
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
getter.getMethodDeclaringClassName().replace(".", "/"),
getter.getMethodName(),
"(" + Arrays.stream(getter.getMethodParameterClassNames())
.map(ByteCodeHelper::getDescriptorForClassName)
.collect(Collectors.joining())
+ ")" + getDescriptorForClassName(getter.getMethodReturnClassName()),
false);
}
private static void visitGetViaReflection(MethodVisitor mv, Field field, int varIndexEntity, int varIndexField) {
// var field = EntityClass.class.getDeclaredField("fieldname");
mv.visitLdcInsn(Type.getObjectType(field.getDeclaringClass().getName()));
mv.visitLdcInsn(field.getName());
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CLASS.getInternalName(), "getDeclaredField", Type.getMethodDescriptor(T_FIELD, T_STRING), false);
mv.visitVarInsn(Opcodes.ASTORE, varIndexField);
// field.setAccessible(true);
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setAccessible", Type.getMethodDescriptor(T_VOID, T_BOOL), false);
// field.get*(entity);
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
var clazz = field.getType();
if (clazz == boolean.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getBoolean", Type.getMethodDescriptor(T_BOOL, T_OBJECT), false);
} else if (clazz == byte.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getByte", Type.getMethodDescriptor(T_BYTE, T_OBJECT), false);
} else if (clazz == char.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getCharacter", Type.getMethodDescriptor(T_CHAR, T_OBJECT), false);
} else if (clazz == short.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getShort", Type.getMethodDescriptor(T_SHORT, T_OBJECT), false);
} else if (clazz == int.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getInt", Type.getMethodDescriptor(T_INT, T_OBJECT), false);
} else if (clazz == long.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getLong", Type.getMethodDescriptor(T_LONG, T_OBJECT), false);
} else if (clazz == float.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getFloat", Type.getMethodDescriptor(T_FLOAT, T_OBJECT), false);
} else if (clazz == double.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getDouble", Type.getMethodDescriptor(T_DOUBLE, T_OBJECT), false);
} else {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "get", Type.getMethodDescriptor(T_OBJECT, T_OBJECT), false);
}
}
static void visitGetField(MethodVisitor mv, Field field, int varIndexEntity, int varIndexField, ClassDescription classDescription) {
//try direct member access
if (Modifier.isPublic(field.getModifiers())) {//TODO better check if publicly accessible
visitGetFieldDirectAccess(mv, field, varIndexEntity);
return;
}
//try finding getter
Optional<GetterDescription> getter = classDescription.getDeclaredGetters().stream()
.filter(e -> e.getFieldDeclaringClassName().equals(field.getDeclaringClass().getName())
&& e.getFieldName().equals(field.getName()))
.findFirst();
if (getter.isPresent()) {
visitGetter(mv, getter.get(), varIndexEntity);
return;
}
//use reflection
visitGetViaReflection(mv, field, varIndexEntity, varIndexField);
}
//endregion
//region setter
private static void visitSetFieldDirectAccess(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity) {
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
value.accept(mv);
mv.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(), Type.getInternalName(field.getType()));
}
private static void visitSetter(MethodVisitor mv, SetterDescription setter, Consumer<MethodVisitor> value, int varIndexEntity) {
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
value.accept(mv);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
setter.getMethodDeclaringClassName().replace(".", "/"),
setter.getMethodName(),
"(" + Arrays.stream(setter.getMethodParameterClassNames())
.map(ByteCodeHelper::getDescriptorForClassName)
.collect(Collectors.joining())
+ ")" + getDescriptorForClassName(setter.getMethodReturnClassName()),
false);
}
private static void visitSetViaReflection(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, int varIndexField) {
// var field = EntityClass.class.getDeclaredField("fieldname");
mv.visitLdcInsn(Type.getObjectType(field.getDeclaringClass().getName()));
mv.visitLdcInsn(field.getName());
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CLASS.getInternalName(), "getDeclaredField", Type.getMethodDescriptor(T_FIELD, T_STRING), false);
mv.visitVarInsn(Opcodes.ASTORE, varIndexField);
// field.setAccessible(true);
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setAccessible", Type.getMethodDescriptor(T_VOID, T_BOOL), false);
// field.set*(entity, value);
mv.visitVarInsn(Opcodes.ALOAD, varIndexField);
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
value.accept(mv);
var clazz = field.getType();
if (clazz == boolean.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setBoolean", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_BOOL), false);
} else if (clazz == byte.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setByte", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_BYTE), true);
} else if (clazz == char.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setCharacter", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_CHAR), true);
} else if (clazz == short.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setShort", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_SHORT), true);
} else if (clazz == int.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setInt", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_INT), true);
} else if (clazz == long.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setLong", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_LONG), true);
} else if (clazz == float.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setFloat", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_FLOAT), true);
} else if (clazz == double.class) {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setDouble", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_DOUBLE), true);
} else {
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "set", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_OBJECT), true);
}
}
static void visitSetField(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, int varIndexField, ClassDescription classDescription) {
//try direct member access
if (Modifier.isPublic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {//TODO better check if publicly accessible
visitSetFieldDirectAccess(mv, field, value, varIndexEntity);
return;
}
//try finding getter
Optional<SetterDescription> setter = classDescription.getDeclaredSetters().stream()
.filter(e -> e.getFieldDeclaringClassName().equals(field.getDeclaringClass().getName())
&& e.getFieldName().equals(field.getName()))
.findFirst();
if (setter.isPresent()) {
visitSetter(mv, setter.get(), value, varIndexEntity);
return;
}
//use reflection
visitSetViaReflection(mv, field, value, varIndexEntity, varIndexField);
}
//endregion
//region PrepareStatement
static void visitPreparedStatementSetNull(MethodVisitor mv, DbField<?> field, int varIndexStmt, int index) {
//stmt.setNull(i, Types.*);
mv.visitVarInsn(Opcodes.ALOAD, varIndexStmt);//push stmt
visitIntInsn(mv, index);//push index
var clazz = field.getType();
if (clazz == String.class) {
visitIntInsn(mv, Types.VARCHAR);//push Types.VARCHAR
} else if (clazz == boolean.class || clazz == Boolean.class) {
visitIntInsn(mv, Types.BIT);//push Types.BIT
} else if (clazz == byte.class || clazz == Byte.class) {
visitIntInsn(mv, Types.TINYINT);//push Types.TINYINT
} else if (clazz == char.class || clazz == Character.class) {
visitIntInsn(mv, Types.CHAR);//push Types.CHAR
} else if (clazz == short.class || clazz == Short.class) {
visitIntInsn(mv, Types.SMALLINT);//push Types.SMALLINT
} else if (clazz == int.class || clazz == Integer.class) {
visitIntInsn(mv, Types.INTEGER);//push Types.INTEGER
} else if (clazz == long.class || clazz == Long.class) {
visitIntInsn(mv, Types.BIGINT);//push Types.BIGINT
} else if (clazz == float.class || clazz == Float.class) {
visitIntInsn(mv, Types.FLOAT);//push Types.FLOAT
} else if (clazz == double.class || clazz == Double.class) {
visitIntInsn(mv, Types.DOUBLE);//push Types.DOUBLE
} else {
throw new UnsupportedOperationException(clazz.getName());
}
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_PREPARED_STATEMENT.getInternalName(), "setNull", "(II)V", false);
}
static void visitPreparedStatementSetAny(MethodVisitor mv, DbField<?> field, int varIndexStmt, int index, Consumer<MethodVisitor> value) {
var clazz = field.getType();
//stmt.set*(i, value);
mv.visitVarInsn(Opcodes.ALOAD, varIndexStmt);//push stmt
visitIntInsn(mv, index);//push index
value.accept(mv);
if (clazz == String.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setString", "(ILjava.lang.String;)V", true);
} else if (clazz == boolean.class || clazz == Boolean.class) {
if (clazz == Boolean.class) {//unbox
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_BOOL.getInternalName(), "booleanValue", "()Z", true);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setBoolean", "(IZ)V", true);
} else if (clazz == byte.class || clazz == Byte.class) {
if (clazz == Byte.class) {//unbox
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_BYTE.getInternalName(), "byteValue", "()B", true);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setByte", "(IB)V", true);
} else if (clazz == char.class || clazz == Character.class) {
// if (clazz == Character.class) {//unbox
// mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CHAR.getInternalName(), "charValue", "()C", true);
// }
// mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setCharacter", "(IC)V", true);
throw new UnsupportedOperationException("char");
} else if (clazz == short.class || clazz == Short.class) {
if (clazz == Short.class) {//unbox
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_SHORT.getInternalName(), "shortValue", "()S", true);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setShort", "(IS)V", true);
} else if (clazz == int.class || clazz == Integer.class) {
if (clazz == Integer.class) {//unbox
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_INT.getInternalName(), "intValue", "()I", true);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setInt", "(II)V", true);
} else if (clazz == long.class || clazz == Long.class) {
if (clazz == Long.class) {//unbox
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_LONG.getInternalName(), "longValue", "()J", true);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setLong", "(IJ)V", true);
} else if (clazz == float.class || clazz == Float.class) {
if (clazz == Float.class) {//unbox
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FLOAT.getInternalName(), "floatValue", "()F", true);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setFloat", "(IF)V", true);
} else if (clazz == double.class || clazz == Double.class) {
if (clazz == Double.class) {//unbox
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_DOUBLE.getInternalName(), "doubleValue", "()D", true);
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setDouble", "(ID)V", true);
} else {
throw new UnsupportedOperationException(clazz.getName());
}
}
//endregion
//region ResultSet
static void visitResultSetGetAny(MethodVisitor mv, DbField<?> field, int varIndexRes) {
var clazz = field.getType();
// res.get*(fieldname);
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);//push res
mv.visitLdcInsn(field.getName());//push 1
if (clazz == String.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getString", "(Ljava.lang.String;)Ljava.lang.String;", true);
} else if (clazz == boolean.class || clazz == Boolean.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getBoolean", "(Ljava.lang.String;)Z", true);
if (clazz == Boolean.class) {//box
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_BOOL.getInternalName(), "<init>", "(Z)V", true);
}
} else if (clazz == byte.class || clazz == Byte.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getByte", "(Ljava.lang.String;)B", true);
if (clazz == Byte.class) {//box
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_BYTE.getInternalName(), "<init>", "(B)V", true);
}
} else if (clazz == char.class || clazz == Character.class) {
// mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getCharacter", "(Ljava.lang.String;)C", true);
// if (clazz == Character.class) {//unbox
// mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_CHAR.getInternalName(), "<init>", "(C)Ljava.lang.Character;", true);
// }
throw new UnsupportedOperationException("char");
} else if (clazz == short.class || clazz == Short.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getShort", "(Ljava.lang.String;)S", true);
if (clazz == Short.class) {//box
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_SHORT.getInternalName(), "<init>", "(S)V", true);
}
} else if (clazz == int.class || clazz == Integer.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getInt", "(Ljava.lang.String;)I", true);
if (clazz == Integer.class) {//box
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_INT.getInternalName(), "<init>", "(I)V", true);
}
} else if (clazz == long.class || clazz == Long.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getLong", "(Ljava.lang.String;)J", true);
if (clazz == Long.class) {//box
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_LONG.getInternalName(), "<init>", "(J)V", true);
}
} else if (clazz == float.class || clazz == Float.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getFloat", "(Ljava.lang.String;)F", true);
if (clazz == Float.class) {//box
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_FLOAT.getInternalName(), "<init>", "(F)V", true);
}
} else if (clazz == double.class || clazz == Double.class) {
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getDouble", "(Ljava.lang.String;)D", true);
if (clazz == Double.class) {//box
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_DOUBLE.getInternalName(), "<init>", "(D)V", true);
}
} else {
throw new UnsupportedOperationException(clazz.getName());
}
// if (res.wasNull()) {
// POP
// PUSH NULL
// }
if (!clazz.isPrimitive()) {
var labelWasNotNull = new Label();
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);//push res
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "wasNull", "()Z", true);
mv.visitJumpInsn(Opcodes.IFEQ, labelWasNotNull);
mv.visitInsn(Opcodes.POP);//POP
mv.visitInsn(Opcodes.ACONST_NULL);//PUSH NULL
mv.visitLabel(labelWasNotNull);
}
}
//endregion
//region util
static void visitIntInsn(MethodVisitor mv, int value) {
switch (value) {
case -1 -> mv.visitInsn(Opcodes.ICONST_M1);
case 0 -> mv.visitInsn(Opcodes.ICONST_0);
case 1 -> mv.visitInsn(Opcodes.ICONST_1);
case 2 -> mv.visitInsn(Opcodes.ICONST_2);
case 3 -> mv.visitInsn(Opcodes.ICONST_3);
case 4 -> mv.visitInsn(Opcodes.ICONST_4);
case 5 -> mv.visitInsn(Opcodes.ICONST_5);
default -> {
if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (Short.MIN_VALUE <= value && value <= Short.MAX_VALUE) {
mv.visitIntInsn(Opcodes.SIPUSH, value);
} else {
mv.visitLdcInsn(value);
}
}
}
}
static void visitThrowSQLException(MethodVisitor mv, String message) {
mv.visitTypeInsn(Opcodes.NEW, T_SQLEXCEPTION.getInternalName());
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn(message);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_SQLEXCEPTION.getInternalName(), "<init>", Type.getMethodDescriptor(T_VOID, T_STRING), false);
mv.visitInsn(Opcodes.ATHROW);
}
static String getDescriptorForClassName(String className) {
return switch (className) {
case "void" -> "V";
case "boolean" -> "Z";
case "byte" -> "B";
case "char" -> "C";
case "short" -> "S";
case "int" -> "I";
case "long" -> "J";
case "float" -> "F";
case "double" -> "D";
default -> "L" + className.replace(".", "/") + ";";
};
}
//endregion
//region var access
// private void visitLoadVariable(MethodVisitor mv, String className, int varIndex) {
// switch (className) {
// case "boolean", "byte", "char", "short", "int" -> mv.visitVarInsn(Opcodes.ILOAD, varIndex);
// case "long" -> mv.visitVarInsn(Opcodes.LLOAD, varIndex);
// case "float" -> mv.visitVarInsn(Opcodes.FLOAD, varIndex);
// case "double" -> mv.visitVarInsn(Opcodes.DLOAD, varIndex);
// default -> mv.visitVarInsn(Opcodes.ALOAD, varIndex);
// }
// }
//
// private void visitStoreVariable(MethodVisitor mv, String className, int varIndex) {
// switch (className) {
// case "boolean", "byte", "char", "short", "int" -> mv.visitVarInsn(Opcodes.ISTORE, varIndex);
// case "long" -> mv.visitVarInsn(Opcodes.LSTORE, varIndex);
// case "float" -> mv.visitVarInsn(Opcodes.FSTORE, varIndex);
// case "double" -> mv.visitVarInsn(Opcodes.DSTORE, varIndex);
// default -> mv.visitVarInsn(Opcodes.ASTORE, varIndex);
// }
// }
//endregion
}

View File

@@ -0,0 +1,63 @@
package jef.platform.mysql.dbinterface;
import jef.serializable.SerializableObject;
import javax.xml.transform.Result;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
public abstract class MysqlEntityDbInterface<T extends SerializableObject> {
public void add(Connection connection, T entity) throws SQLException {
try (var stmt = queryAdd(connection)) {
bindParamsAdd(stmt, entity);
var added = stmt.executeUpdate();
if (added != 1) {
throw new SQLException("Failed to insert entity");
}
readBackGeneratedValuesAdd(stmt, entity);
}
}
protected abstract PreparedStatement queryAdd(Connection connection) throws SQLException;
// protected abstract PreparedStatement queryAdd(Connection connection) throws SQLException {
// return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
// }
// protected abstract void bindParamsAdd(PreparedStatement stmt, T entity) throws SQLException;
protected void bindParamsAdd(PreparedStatement stmt, T entity) throws SQLException {
stmt.setInt(1, 1);
stmt.setInt(2, 2);
var s = "string";
if (s != null) {
stmt.setString(3, "string");
} else {
stmt.setNull(3, Types.INTEGER);
}
stmt.setInt(4, 4);
}
protected void readBackGeneratedValuesAdd(PreparedStatement stmt, T entity) throws SQLException {
try (var res = stmt.getGeneratedKeys()) {
readBackGeneratedValuesAdd(res, entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void readBackGeneratedValuesAdd(ResultSet res, T entity) throws Exception {
var f = Exception.class.getDeclaredField("f");
f.setAccessible(true);
f.setInt(null, 12);
}
// public void update(Connection connection, T model);
//
// public void delete(Connection connection, T model);
//
// public T read(ResultSet res);
}

View File

@@ -0,0 +1,380 @@
package jef.platform.mysql.dbinterface;
import jef.asm.access.ClassAnalyzer;
import jef.asm.access.model.ClassDescription;
import jef.model.DbContext;
import jef.model.DbEntity;
import jef.model.DbField;
import jef.model.annotations.Generated;
import jef.serializable.SerializableObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
private static final boolean SET_SYNTHETIC_FLAG = false;
private static final int SYNTHETIC_FLAG = SET_SYNTHETIC_FLAG ? Opcodes.ACC_SYNTHETIC : 0;
private final Class<? extends DbContext> context;
private final DbEntity<T> entity;
private final ClassWriter writer;
private final ClassVisitor visitor;
//runtime
private ClassDescription classDescription;
//results
@Getter
private String className;
@Getter
private byte[] byteCode;
public MysqlEntityDbInterfaceGenerator(Class<? extends DbContext> context, DbEntity<T> entity) {
this.context = context;
this.entity = entity;
writer = new ClassWriter(Opcodes.ASM9);
visitor = new ClassVisitor(Opcodes.ASM9, writer) {//TODO definitely broken here
};
}
public void generate() {
classDescription = new ClassAnalyzer().analyze(entity.getType());
//follows template "<context full class name>$<entity classname>MysqlEntityDbInterface"
// className = context.getName() + "$" + entity.getType().getSimpleName() + "" + MysqlEntityDbInterface.class.getSimpleName();
className = entity.getType().getSimpleName() + "" + MysqlEntityDbInterface.class.getSimpleName();
defineClass();
defineConstructor();
// defineAddFunction();
defineQueryAddFunction();
defineBindParamsAddFunction();
defineReadBackGeneratedValuesAddPreparedStatement();
defineReadBackGeneratedValuesAddResultSet();
// defineUpdateFunction();
// defineDeleteFunction();
// defineReadFunction();
visitor.visitEnd();
byteCode = writer.toByteArray();
}
protected void defineClass() {
var classSimpleName = className.substring(Math.max(className.lastIndexOf("."), className.lastIndexOf("$")) + 1);
visitor.visit(Opcodes.V17, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | SYNTHETIC_FLAG,
classSimpleName,
Type.getDescriptor(MysqlEntityDbInterface.class).replace(";", "<" + ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()) + ">;"),
Type.getInternalName(MysqlEntityDbInterface.class), new String[]{});
// visitor.visitNestHost(Type.getInternalName(context));
// visitor.visitInnerClass(className.replace(".", "/"), Type.getInternalName(context), classSimpleName, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SUPER);
}
//public <init>() {}
private void defineConstructor() {
var mv = visitor.visitMethod(Opcodes.ACC_PUBLIC | SYNTHETIC_FLAG, "<init>", "()V", null, new String[0]);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
private ColumnsAndValueBinders prepareColumnsAndValueBindersAdd() {
var columns = new ArrayList<String>();
var valueBinders = new ArrayList<ValueBinder>();
for (DbField<?> field : entity.getFields()) {
if (!field.isDatabaseField()) {
continue;
}
Field f = field.getField();
if (f == null) {
if (field.isDatabaseField()) {//field defined but not present in entity class -> currently nothing to insert -> null
columns.add(field.getName());
valueBinders.add((mv, varIndexEntity, varIndexStmt, varIndexField, index) -> {
ByteCodeHelper.visitIntInsn(mv, index);
//stmt.setNull(i, Types.INTEGER);
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
});
}
continue;
}
columns.add(field.getName());
valueBinders.add((mv, varIndexEntity, varIndexStmt, varIndexField, index) -> {
if (field.isNotNull()) {
//stmt.set*(i, value);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, varIndexField, classDescription));
} else {
// getFieldValue(entity);
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, varIndexField, classDescription);
// if (value == null)
// stmt.setNull(i, Types.*);
// } else {
// stmt.set*(i, value);
// }
var labelAfter = new Label();
var labelElse = new Label();
mv.visitJumpInsn(Opcodes.IFNULL, labelElse);
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, varIndexField, classDescription));
mv.visitJumpInsn(Opcodes.GOTO, labelAfter);
mv.visitLabel(labelElse);
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
mv.visitLabel(labelAfter);
// mv.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
mv.visitFrame(Opcodes.F_FULL, 3, new Object[]{
ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), ByteCodeHelper.T_PREPARED_STATEMENT.getInternalName(), entity.getTypeName().replace(".", "/")
}, 0, new Object[0]);
}
});
}
return new ColumnsAndValueBinders(columns, valueBinders);
}
private interface ValueBinder {
void bind(MethodVisitor mv, int varIndexEntity, int varIndexStmt, int varIndexField, int index);
}
@AllArgsConstructor
@Getter
private static class ColumnsAndValueBinders {
private final List<String> columns;
private final List<ValueBinder> valueBinders;
}
private void defineQueryAddFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryAdd", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION),
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexConnection = 1;
var columnsAndValueBinders = prepareColumnsAndValueBindersAdd();
var columnsString = columnsAndValueBinders.getColumns().stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
var placeholdersString = columnsAndValueBinders.getColumns().stream().map(e -> "?").collect(Collectors.joining(","));
var query = "INSERT INTO `" + entity.getName() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartConnection = new Label();
var labelEndConnection = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartConnection);
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitLdcInsn(query);
if (entity.getPrimaryKey() != null) {
mv.visitInsn(Opcodes.ICONST_1); //Statement.RETURN_GENERATED_KEYS == 1
} else {
mv.visitInsn(Opcodes.ICONST_0); //no flags
}
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
mv.visitInsn(Opcodes.ARETURN);
// variable scopes
mv.visitLabel(labelEndConnection);
mv.visitLabel(labelEndThis);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
private void defineBindParamsAddFunction() {
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))),
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexStmt = 1;
var varIndexEntity = 2;
var varIndexField = 3;
//create lists for columns and value binders
var columnsAndValueBinders = prepareColumnsAndValueBindersAdd();
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartStmt = new Label();
var labelEndStmt = new Label();
var labelStartEntity = new Label();
var labelEndEntity = new Label();
var labelStartField = new Label();
var labelEndField = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartStmt);
mv.visitLabel(labelStartEntity);
mv.visitLabel(labelStartField);
//stmt.set* bind calls
var i = 1;
for (var valueBinder : columnsAndValueBinders.getValueBinders()) {
valueBinder.bind(mv, varIndexEntity, varIndexStmt, varIndexField, i++);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndStmt);
mv.visitLabel(labelEndEntity);
mv.visitLabel(labelEndField);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
mv.visitLocalVariable("field", ByteCodeHelper.T_FIELD.getDescriptor(), null, labelStartField, labelEndField, varIndexField);
mv.visitMaxs(3, 4);
mv.visitEnd();
}
private void defineReadBackGeneratedValuesAddPreparedStatement() {
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
return;
}
//stub method if no generated values
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))),
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var label = new Label();
mv.visitCode();
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, label, label, 0);
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, label, label, 1);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, label, label, 2);
mv.visitMaxs(0, 3);//TODO stack size
mv.visitEnd();
}
private void defineReadBackGeneratedValuesAddResultSet() {
if (entity.getFields().stream().noneMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
return;
}
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_RESULT_SET, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))),
null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
var varIndexThis = 0;
var varIndexRes = 1;
var varIndexEntity = 2;
var varIndexField = 3;
// variable scopes
var labelStartThis = new Label();
var labelEndThis = new Label();
var labelStartRes = new Label();
var labelEndRes = new Label();
var labelStartEntity = new Label();
var labelEndEntity = new Label();
var labelStartField = new Label();
var labelEndField = new Label();
// begin
mv.visitCode();
// variable scopes
mv.visitLabel(labelStartThis);
mv.visitLabel(labelStartRes);
mv.visitLabel(labelStartEntity);
mv.visitLabel(labelStartField);
// if (!res.next()) {
// throw new SQLException("Missing generated keys for entity but are required");
// }
var labelResHasNext = new Label();
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_RESULT_SET.getInternalName(), "next", Type.getMethodDescriptor(ByteCodeHelper.T_BOOL), true);
mv.visitJumpInsn(Opcodes.IFNE, labelResHasNext);
ByteCodeHelper.visitThrowSQLException(mv, "Missing generated keys for entity but are required");
mv.visitLabel(labelResHasNext);
for (var field : entity.getFields()) {
if (field.getGenerated() == Generated.Type.NONE) {
continue;
}
// var value = res.get*(1);
Consumer<MethodVisitor> resGetValue = (mv2) -> {
ByteCodeHelper.visitResultSetGetAny(mv2, field, varIndexRes);
};
// *SET*(entity, field, value);
ByteCodeHelper.visitSetField(mv, field.getField(), resGetValue, varIndexEntity, varIndexField, classDescription);
}
//return
mv.visitInsn(Opcodes.RETURN);
// variable scopes
mv.visitLabel(labelEndThis);
mv.visitLabel(labelEndRes);
mv.visitLabel(labelEndEntity);
mv.visitLabel(labelEndField);
// debug symbols
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
mv.visitLocalVariable("res", ByteCodeHelper.T_RESULT_SET.getDescriptor(), null, labelStartRes, labelEndRes, varIndexRes);
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
mv.visitLocalVariable("field", ByteCodeHelper.T_FIELD.getDescriptor(), null, labelStartField, labelEndField, varIndexField);
mv.visitMaxs(3, 4);
mv.visitEnd();
}
private void defineUpdateFunction() {
}
private void defineDeleteFunction() {
}
private void defineReadFunction() {
}
}

View File

@@ -0,0 +1,66 @@
package jef.platform.mysql.dbinterface;
import jef.serializable.SerializableObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class MysqlInterfaceClassLoader extends ClassLoader {
private final Map<String, byte[]> resources = new HashMap<>();
public MysqlInterfaceClassLoader(String name, ClassLoader parent) {
super(name, parent);
}
public MysqlInterfaceClassLoader(ClassLoader parent) {
super(parent);
}
public MysqlInterfaceClassLoader() {
}
public <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<T>> defineClass(String className, byte[] byteCode) {
synchronized (resources) {
var resourceName = className.replace(".", "/") + ".class";
resources.put(resourceName, byteCode);
}
return (Class<? extends MysqlEntityDbInterface<T>>) super.defineClass(className, byteCode, 0, byteCode.length);
}
@Override
public URL getResource(String name) {
return super.getResource(name);
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
return super.getResources(name);
}
@Override
public InputStream getResourceAsStream(String name) {
synchronized (resources) {
return super.getResourceAsStream(name);
}
}
@Override
public Stream<URL> resources(String name) {
return super.resources(name);
}
@Override
protected URL findResource(String moduleName, String name) throws IOException {
return super.findResource(moduleName, name);
}
@Override
protected URL findResource(String name) {
return super.findResource(name);
}
}

View File

@@ -0,0 +1,33 @@
package jef.platform.mysql.dbinterface;
import jef.model.DbContext;
import jef.model.DbEntity;
import jef.serializable.SerializableObject;
import lombok.AllArgsConstructor;
import java.util.HashMap;
import java.util.Map;
@AllArgsConstructor
public class MysqlInterfaceFactory {
private static MysqlInterfaceFactory getInstance(Class<DbContext> context) {
return new MysqlInterfaceFactory(context);
}
private final Class<DbContext> context;
private final MysqlInterfaceClassLoader classLoader = new MysqlInterfaceClassLoader();
private final Map<Class<? extends SerializableObject>, Class<? extends MysqlEntityDbInterface<? extends SerializableObject>>> cache = new HashMap<>();
public <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<T>> getInterface(DbEntity<T> entity) {
synchronized (cache) {
return (Class<? extends MysqlEntityDbInterface<T>>) cache.computeIfAbsent(entity.getType(), ignored -> generateInterface(entity));
}
}
private <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<? extends SerializableObject>> generateInterface(DbEntity<T> entity) {
var generator = new MysqlEntityDbInterfaceGenerator<T>(context, entity);
generator.generate();
var byteCode = generator.getByteCode();
return classLoader.defineClass(generator.getClassName(), byteCode);
}
}

View File

@@ -0,0 +1,172 @@
package jef.platform.mysql;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.DbContextOptions;
import jef.model.annotations.Clazz;
import jef.model.annotations.ForeignKey;
import jef.platform.base.Database;
import jef.platform.base.DatabaseOptions;
import jef.serializable.SerializableObject;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.junit.jupiter.api.Test;
import java.sql.DriverManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class MysqlSimpleIntegrativeTest {
@Test
void queryEntities() throws Exception {
//setup
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
var ctxoptions = new DbContextOptions(dboptions);
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
var sqlPlatform = new MysqlPlatform();
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
var ctx = new Ctx(db, ctxoptions);
//test
var result = ctx.getCompanies().toList();
System.out.println(result);
//assert
}
@Test
void addEntity() throws Exception {
//setup
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
var ctxoptions = new DbContextOptions(dboptions);
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
var sqlPlatform = new MysqlPlatform();
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
var ctx = new Ctx(db, ctxoptions);
//test
var entity = new Company();
entity.setName("some company");
ctx.getCompanies().add(entity);
//assert
assertNotEquals(0, entity.getId());
}
@Test
void updateEntity() throws Exception {
//setup
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
var ctxoptions = new DbContextOptions(dboptions);
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
var sqlPlatform = new MysqlPlatform();
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
var ctx = new Ctx(db, ctxoptions);
var entity = new Company();
entity.setName("some company");
ctx.getCompanies().add(entity);
assertNotEquals(0, entity.getId());
//test
entity.setName("other company");
ctx.getCompanies().update(entity);
//assert
var id = entity.getId();
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.getId()).toList().stream().findFirst().orElseThrow();
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.id).toList().stream().findFirst().orElseThrow();
// var fromdb = ctx.getCompanies().filter(e -> e.id == id).toList().stream().findFirst().orElseThrow();
var fromdb = ctx.getCompanies().toList().stream().filter(e -> e.id == id).findFirst().orElseThrow();
// var fromdb = ctx.getCompanies().toList().stream().filter(e -> e.id == 5).findFirst().orElseThrow();
assertEquals(entity, fromdb);
assertNotSame(entity, fromdb);
}
@Test
void deleteEntity() throws Exception {
//setup
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
var ctxoptions = new DbContextOptions(dboptions);
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
var sqlPlatform = new MysqlPlatform();
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
var ctx = new Ctx(db, ctxoptions);
var entity = new Company();
entity.setName("some company");
ctx.getCompanies().add(entity);
assertNotEquals(0, entity.getId());
//test
ctx.getCompanies().delete(entity);
//assert
var id = entity.getId();
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.getId()).toList().stream().findFirst().orElseThrow();//TODO get this to work too
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.id).toList().stream().findFirst().orElseThrow();
// var fromdb = ctx.getCompanies().filter(e -> e.id == id).toList().stream().findFirst().orElseThrow();
var fromdb = ctx.getCompanies().toList().stream().noneMatch(e -> e.id == id);
assertTrue(fromdb);
}
@Getter
public static class Ctx extends DbContext {
@Clazz(Company.class)
private DbSet<Company> companies;
@Clazz(Employee.class)
private DbSet<Employee> employees;
public Ctx() {
}
public Ctx(Database database, DbContextOptions options) {
super(database, options);
}
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode(callSuper = false)
public static class Company extends SerializableObject {
private int id;
private String name;
private Employee ceo;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode(callSuper = false)
public static class Employee extends SerializableObject {
private int id;
private String name;
private Employee boss;
@ForeignKey(getterOrField = "boss")
private int bossId;
private Company company;
@ForeignKey(getterOrField = "company")
private int companyId;
}
}

View File

@@ -0,0 +1,86 @@
package jef.platform.mysql.dbinterface;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.DbContextOptions;
import jef.model.ModelBuilder;
import jef.model.annotations.Clazz;
import jef.model.annotations.ForeignKey;
import jef.platform.base.Database;
import jef.serializable.SerializableObject;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
class MysqlEntityDbInterfaceGeneratorTest {
private static final String GENERATED_MIGRATIONS_DBINTERFACES = "target/test-generated-dbinterfaces/";
// private static final String GENERATED_MIGRATIONS_FOLDER_SRC = GENERATED_MIGRATIONS_DBINTERFACES + "src/";
private static final String GENERATED_MIGRATIONS_FOLDER_TARGET = GENERATED_MIGRATIONS_DBINTERFACES + "target/";
@Test
void generate() throws IOException {
var mb = ModelBuilder.from(Ctx.class);
var generator = new MysqlEntityDbInterfaceGenerator<>(Ctx.class, mb.getEntity(Employee.class));
generator.generate();
// var path = Path.of(GENERATED_MIGRATIONS_FOLDER_TARGET, "MysqlEntityDbInterfaceGeneratorTest", "MysqlEntityDbInterfaceGeneratorTest$Ctx$EmployeeMysqlEntityDbInterface.class");
var path = Path.of(GENERATED_MIGRATIONS_FOLDER_TARGET, "MysqlEntityDbInterfaceGeneratorTest", "EmployeeMysqlEntityDbInterface.class");
path.toFile().getParentFile().mkdirs();
Files.write(path, generator.getByteCode(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
}
@Getter
public static class Ctx extends DbContext {
@Clazz(Company.class)
private DbSet<Company> companies;
@Clazz(Employee.class)
private DbSet<Employee> employees;
public Ctx() {
}
public Ctx(Database database, DbContextOptions options) {
super(database, options);
}
}
// @Getter
// @Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode(callSuper = false)
public static class Company extends SerializableObject {
private int id;
private String name;
private Employee ceo;
}
// @Getter
// @Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode(callSuper = false)
public static class Employee extends SerializableObject {
private int id;
private String name;
private Employee boss;
@ForeignKey(getterOrField = "boss")
private int bossId;
private Company company;
@ForeignKey(getterOrField = "company")
private int companyId;
}
}