diff --git a/README.md b/README.md
index 695777c..e2858cd 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
## Asm
- equals function for registered primitive conversion types
- IConst0Fixer: IConst0/1 on its own => false/true
+- IConst0Fixer: IConst0/1 false/true depending on compared field type
- actually parse getter
- resolve Predicate functions (not, and, or)
- don't bind external vars in lambdas, instead make information (parameter and intercepted values) available in a context object
diff --git a/pom.xml b/pom.xml
index 30633b9..340230a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,6 +102,29 @@
${project.build.sourceEncoding}
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ true
+ jef.main.Main
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M7
+
+
+ ${project.basedir}/target/test-generated-migrations/target
+
+
+
@@ -146,7 +169,15 @@
${mockito.version}
test
-
+
+
+ mysql
+ mysql-connector-java
+ 8.0.30
+ test
+
+
+
diff --git a/src/main/java/jef/Database.java b/src/main/java/jef/Database.java
new file mode 100644
index 0000000..b2945a2
--- /dev/null
+++ b/src/main/java/jef/Database.java
@@ -0,0 +1,26 @@
+package jef;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@Getter
+@AllArgsConstructor
+public abstract class Database {
+ // private final DatabaseConfiguration config;
+ protected final Connection connection;
+ protected final DatabaseOptions options;
+
+ public abstract void migrate() throws MigrationException;
+
+// public ResultSet executeRaw(String s) throws SQLException {
+// try (var stmt = connection.prepareStatement(s)) {
+// try (var res = stmt.executeQuery()) {
+// return res;
+// }
+// }
+// }
+}
diff --git a/src/main/java/jef/DatabaseOptions.java b/src/main/java/jef/DatabaseOptions.java
new file mode 100644
index 0000000..868afc8
--- /dev/null
+++ b/src/main/java/jef/DatabaseOptions.java
@@ -0,0 +1,46 @@
+package jef;
+
+import lombok.Getter;
+
+import java.net.MalformedURLException;
+import java.util.regex.Pattern;
+
+@Getter
+public class DatabaseOptions {
+ protected final String url;
+ protected final String user;
+ protected final String password;
+
+ protected final String migrationsPackage;
+
+ protected final String host;
+ protected final String database;
+
+ public DatabaseOptions(String url, String user, String password, String migrationsPackage) {
+ this.url = url;
+ this.user = user;
+ this.password = password;
+
+ host = extractHost(url);
+ database = extractDatabase(url);
+ this.migrationsPackage = migrationsPackage;
+ }
+
+ private static String extractHost(String url) {
+ var pattern = Pattern.compile("^.*?//(.*?)/.*$");
+ var matcher = pattern.matcher(url);
+ if (!matcher.matches()) {
+ throw new RuntimeException(new MalformedURLException("Could not extract host for url: " + url));
+ }
+ return matcher.group(1);
+ }
+
+ private static String extractDatabase(String url) {
+ var pattern = Pattern.compile("^.*?//.*?/(.*?)([?/].*)?$");
+ var matcher = pattern.matcher(url);
+ if (!matcher.matches()) {
+ throw new RuntimeException(new MalformedURLException("Could not extract database for url: " + url));
+ }
+ return matcher.group(1);
+ }
+}
diff --git a/src/main/java/jef/MigrationException.java b/src/main/java/jef/MigrationException.java
new file mode 100644
index 0000000..0f83e89
--- /dev/null
+++ b/src/main/java/jef/MigrationException.java
@@ -0,0 +1,18 @@
+package jef;
+
+public class MigrationException extends Exception {
+ public MigrationException() {
+ }
+
+ public MigrationException(String message) {
+ super(message);
+ }
+
+ public MigrationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public MigrationException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/jef/main/Main.java b/src/main/java/jef/main/Main.java
new file mode 100644
index 0000000..35063de
--- /dev/null
+++ b/src/main/java/jef/main/Main.java
@@ -0,0 +1,27 @@
+package jef.main;
+
+import java.util.Arrays;
+
+public class Main {
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ printHelp();
+ System.exit(1);
+ }
+ switch (args[0].toLowerCase()) {
+ case "help":
+ printHelp();
+ case "migration":
+ MigrationCommandHandler.handleMigration(Arrays.copyOfRange(args, 1, args.length));
+
+ default:
+ printHelp();
+ }
+ }
+
+ static void printHelp() {
+ System.out.println("Usage: java -jar thisfile [options]");
+ MigrationCommandHandler.printHelp();
+ System.exit(1);
+ }
+}
diff --git a/src/main/java/jef/main/MigrationCommandHandler.java b/src/main/java/jef/main/MigrationCommandHandler.java
new file mode 100644
index 0000000..31402de
--- /dev/null
+++ b/src/main/java/jef/main/MigrationCommandHandler.java
@@ -0,0 +1,294 @@
+package jef.main;
+
+import jef.Database;
+import jef.model.DbContext;
+import jef.model.DbContextOptions;
+import jef.model.ModelBuilder;
+import jef.model.migration.creator.MigrationCreator;
+import jef.mysql.MysqlDatabase;
+import jef.util.Util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.JarURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class MigrationCommandHandler {
+ static void handleMigration(String[] args) {
+ if (args.length == 0) {
+ Main.printHelp();
+ }
+
+ try {
+ switch (args[0].toLowerCase()) {
+ case "add":
+ handleMigrationAdd(Arrays.copyOfRange(args, 1, args.length));
+ case "remove":
+ handleMigrationRemove(Arrays.copyOfRange(args, 1, args.length));
+ case "list":
+ handleMigrationList(Arrays.copyOfRange(args, 1, args.length));
+ default:
+ Main.printHelp();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+
+ System.exit(0);
+ }
+
+ private static void handleMigrationList(String[] args) {
+ }
+
+ private static void handleMigrationRemove(String[] args) {
+ }
+
+ private static void handleMigrationAdd(String[] args) throws Exception {
+ String contextName = null;
+ String migrationPackage = null;
+ String targetFolder = null;
+ String name = null;
+ var classPath = new ArrayList();
+
+ for (int i = 0; i < args.length; i++) {
+ switch (args[i].toLowerCase()) {
+ case "--cp":
+ if (i + 1 >= args.length) {
+ Main.printHelp();
+ }
+ classPath.addAll(List.of(args[++i].split(":")));//TODO does not work well on windows (:)
+ break;
+ case "--context":
+ case "-c":
+ if (i + 1 >= args.length) {
+ Main.printHelp();
+ }
+ contextName = args[++i];
+ break;
+ case "--output":
+ case "-o":
+ if (i + 1 >= args.length) {
+ Main.printHelp();
+ }
+ targetFolder = args[++i];
+ break;
+ default:
+ if (name != null) {
+ Main.printHelp();
+ }
+ name = args[i];
+ break;
+ }
+ }
+
+// if (Set.of(contextName, targetFolder, name).contains(null)) {
+// Main.printUsage();
+// }
+
+ if (name == null) {
+ System.err.println("The migration requires a name.");
+ System.exit(1);
+ }
+
+ //find data locations
+ var urls = classPath.stream().map(e -> Util.tryGet(() -> new File(e).toURI().toURL()).orElseThrow()).toArray(URL[]::new);
+ var cl = new URLClassLoader(urls, MigrationCommandHandler.class.getClassLoader());
+ var context = contextName == null ? findAnyContext(cl) : findContextByName(cl, contextName);
+ targetFolder = targetFolder == null ? System.getProperty("user.dir") : targetFolder;
+ var targetFolderFile = new File(targetFolder);
+ targetFolderFile.mkdirs();
+ migrationPackage = findMigrationPackageName(targetFolder);
+
+ //find data
+ var currentSnapshotFile = new File(targetFolderFile, "CurrentSnapshot.java");
+ var from = !currentSnapshotFile.isFile()
+ ? new ModelBuilder()
+ : (ModelBuilder) cl.loadClass((migrationPackage != null ? migrationPackage + "." : "") + "CurrentSnapshot").getConstructor().newInstance();
+ var to = ModelBuilder.from(context.orElseThrow().getClass());
+// context.orElseThrow().onModelCreate(to);
+
+ //begin
+ var date = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+ var className = "M" + date + "_" + name;
+
+ var creator = new MigrationCreator();
+ var result = creator.createMigration(from, to, className, migrationPackage, currentSnapshotFile.isFile() ? Files.readString(currentSnapshotFile.toPath()) : null);
+
+ Files.writeString(new File(targetFolder, className + ".java").toPath(), result.getMigration(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+ Files.writeString(new File(targetFolder, className + "Snapshot.java").toPath(), result.getMigrationSnapshot(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+ Files.writeString(new File(targetFolder, "CurrentSnapshot.java").toPath(), result.getCurrentSnapshot(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+ System.exit(0);
+ }
+
+ private static Optional findAnyContext(URLClassLoader cl) {
+ return findContextByName(cl, "");
+ }
+
+ private static Optional findContextByName(URLClassLoader cl, String contextName) {
+ Util.ThrowableFunction finder = (String entryName) -> {
+ var cls = cl.loadClass(entryName.substring(0, entryName.length() - ".class".length()).replace("/", "."));
+ var parent = cls;
+// var interfaces = cls.getInterfaces();
+// while (parent != Object.class && !Set.of(interfaces).contains(DbContext.class)) {
+ while (parent != Object.class && parent != DbContext.class) {
+ parent = parent.getSuperclass();
+// interfaces = cls.getInterfaces();
+ }
+// if (Set.of(interfaces).contains(DbContext.class)) {
+ if (parent == DbContext.class) {
+ var paramInitializer = new HashMap, Supplier>>();
+ paramInitializer.put(Database.class, () -> new MysqlDatabase(null, null));
+ paramInitializer.put(DbContextOptions.class, () -> new DbContextOptions(null));
+
+ var ctors = cls.getDeclaredConstructors();//TODO create a function for initializing db contexts
+ if (ctors.length == 0) {
+ System.err.println("No constructor found in " + cls.getName());
+ return null;
+ }
+
+ var ctor = Arrays.stream(ctors)
+ .filter(e -> Arrays.stream(e.getParameterTypes())
+ .allMatch(p -> Database.class.isAssignableFrom(p) || DbContextOptions.class.isAssignableFrom(p)))
+ .findFirst().orElse(null);
+ if (ctor == null) {
+ return null;
+ }
+ var args = Arrays.stream(ctor.getParameterTypes()).map(p -> paramInitializer.get(p).get()).toArray();
+ return Util.tryGet(() -> (DbContext) ctor.newInstance(args)).orElse(null);
+ }
+ return null;
+ };
+ return Arrays.stream(cl.getURLs()).map(url -> {
+ URLConnection conn;
+ try {
+ conn = url.openConnection();
+ } catch (IOException ignored) {
+ return null;
+ }
+ if (conn instanceof JarURLConnection) {
+ try (var zip = new ZipInputStream(conn.getInputStream())) {
+ ZipEntry entry;
+ while ((entry = zip.getNextEntry()) != null) {
+ if (entry.isDirectory() || !entry.getName().endsWith(contextName + ".class")) {
+ continue;
+ }
+ return finder.apply(entry.getName());
+ }
+ } catch (Throwable ignored) {
+ }
+ } else {
+ var search = new ArrayList();
+ search.add(url);
+ while (search.size() > 0) {
+ try {
+ var currentUrl = search.remove(0);
+ conn = currentUrl.openConnection();
+ try (var is = conn.getInputStream();
+ var isr = new InputStreamReader(is);
+ var br = new BufferedReader(isr)) {
+ String entry;
+ while ((entry = br.readLine()) != null) {
+// System.out.println(entry);
+ var newUri = currentUrl.toURI().toString();
+ newUri = newUri.endsWith("/") ? newUri : newUri + "/";
+ newUri += entry;
+ search.add(new URI(newUri).toURL());
+ if (!entry.endsWith(contextName + ".class")) {//entry.isDirectory() ||
+ continue;
+ }
+ return finder.apply(new URI(newUri).getPath().substring(url.toURI().getPath().length()));
+ }
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+ }
+ return null;
+ })
+ .filter(Objects::nonNull)
+ .findFirst();
+ }
+
+ private static String findMigrationPackageName(String dir) {
+ //search for java file in dir and take package name from there
+ var javaFiles = new File(dir).listFiles(file -> file.isFile() && file.getName().endsWith(".java"));
+ if (javaFiles.length > 0) {
+ try {
+ var contextFileString = Files.readAllLines(javaFiles[0].toPath()).stream().collect(Collectors.joining(" "));
+ var pattern = Pattern.compile("\\s*package\\s+(.*?);");
+ var matcher = pattern.matcher(contextFileString);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+// //try finding out from dir path
+ var path = new File(dir).getAbsolutePath();
+ var i = path.indexOf("/src/");
+ if (i >= 0) {
+ path = path.substring(i + "/src/".length());
+ if (path.startsWith("main/")) {
+ path = path.substring("main/".length());
+ if (path.startsWith("java/")) {
+ path = path.substring("java/".length());
+ }
+ }
+ }
+ i = path.indexOf("/target/");
+ if (i >= 0) {
+ path = path.substring(i + "/target/".length());
+ if (path.startsWith("generated-migrations/")) {
+ path = path.substring("generated-migrations/".length());
+ if (path.startsWith("src/")) {
+ path = path.substring("src/".length());
+ }
+ }
+ }
+ return path.replace("/", ".");
+// return null;
+ }
+
+ public static void printHelp() {
+ System.out.println("migration add --cp [--context/-c ] [--output/-o ] ");
+ System.out.println("migration remove --cp [--context/-c ] [--output/-o ] ");
+ System.out.println("migration list");
+ }
+
+// private static String findMigrationSnapshot(String dir) {
+// var javaFiles = new File(dir).listFiles(file -> file.isFile() && file.getName().endsWith(".java"));
+// if (javaFiles.length > 0) {
+// try {
+// var contextFileString = Files.readAllLines(javaFiles[0].toPath()).stream().collect(Collectors.joining(" "));
+// var pattern = Pattern.compile("\\s*package\\s+(.*?);");
+// var matcher = pattern.matcher(contextFileString);
+// if (matcher.find()) {
+// return matcher.group(1);
+// }
+// } catch (IOException ignored) {
+// }
+// }
+// }
+}
diff --git a/src/main/java/jef/model/DbContext.java b/src/main/java/jef/model/DbContext.java
index f3223b2..86e1f8e 100644
--- a/src/main/java/jef/model/DbContext.java
+++ b/src/main/java/jef/model/DbContext.java
@@ -1,7 +1,11 @@
package jef.model;
+import jef.Database;
+import jef.DbSet;
+import jef.model.annotations.Clazz;
import jef.model.constraints.ForeignKeyConstraint;
import jef.serializable.SerializableObject;
+import lombok.Getter;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -9,9 +13,40 @@ import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;
+@Getter
public abstract class DbContext {
private static final String ILLEGAL_CHARACTERS = "\"'`,.";
+ private final Database database;
+ private final DbContextOptions options;
+
+ public DbContext() {
+ database = null;
+ options = null;
+ }
+
+ public DbContext(Database database, DbContextOptions options) {
+ this.database = database;
+ this.options = options;
+
+ initInitializeDbSets();
+ }
+
+ private void initInitializeDbSets() {
+ for (Field f : getClass().getDeclaredFields()) {
+ if (f.getType() != DbSet.class) {
+ continue;
+ }
+ f.setAccessible(true);
+ Clazz anno = f.getAnnotation(Clazz.class);
+ try {
+ f.set(this, new DbSet(anno.value(), f.getName()));//TODO use table name from modelbuilder
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
public void onModelCreate(ModelBuilder mb) {
}
diff --git a/src/main/java/jef/model/DbContextOptions.java b/src/main/java/jef/model/DbContextOptions.java
index ca0c63f..3afe0f2 100644
--- a/src/main/java/jef/model/DbContextOptions.java
+++ b/src/main/java/jef/model/DbContextOptions.java
@@ -1,13 +1,15 @@
package jef.model;
+import jef.DatabaseOptions;
+import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
-import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
-@NoArgsConstructor
+@AllArgsConstructor
public class DbContextOptions {
+ private final DatabaseOptions databaseOptions;
}
diff --git a/src/main/java/jef/model/DbEntity.java b/src/main/java/jef/model/DbEntity.java
index 45453c7..cd951a5 100644
--- a/src/main/java/jef/model/DbEntity.java
+++ b/src/main/java/jef/model/DbEntity.java
@@ -27,7 +27,7 @@ import java.util.Objects;
public class DbEntity {
private String typeName;
// @Setter
-// private Class type;
+ private Class type;
private final List> fields;
private String name;
private PrimaryKeyConstraint primaryKey;
@@ -53,7 +53,7 @@ public class DbEntity {
}
DbEntity(Class type, String name, List> fields) {
-// this.type = Check.notNull(type, "type");
+ this.type = type;
this.typeName = type.getName();
this.fields = Check.notNull(fields, "fields");
this.name = Check.notNull(name, "name");
@@ -218,7 +218,6 @@ public class DbEntity {
return new DbEntityBuilder<>(mb, this);
}
-
String extractFieldName(SerializableFunction getter) {
try {
var expr = new AsmParser(getter).parse().getExpression();
diff --git a/src/main/java/jef/model/DbEntityBuilder.java b/src/main/java/jef/model/DbEntityBuilder.java
index f859c74..392235d 100644
--- a/src/main/java/jef/model/DbEntityBuilder.java
+++ b/src/main/java/jef/model/DbEntityBuilder.java
@@ -98,7 +98,7 @@ public class DbEntityBuilder {
}
public Optional> type() {
- return Util.tryGet(() -> (Class) Class.forName(typeName()));
+ return Optional.ofNullable(entity.getType()).or(() -> Util.tryGet(() -> (Class) Class.forName(typeName())));
}
public String typeName() {
diff --git a/src/main/java/jef/model/DbField.java b/src/main/java/jef/model/DbField.java
index 7716abe..e155db0 100644
--- a/src/main/java/jef/model/DbField.java
+++ b/src/main/java/jef/model/DbField.java
@@ -24,6 +24,7 @@ public class DbField {
private DbField> exposingForeignKeyOf;
private String name;
private boolean notNull = false;
+ private String sqlType;
DbField(DbEntity extends SerializableObject> entity, String name, String typeName) {
this.entity = Check.notNull(entity, "entity");
@@ -36,7 +37,7 @@ public class DbField {
}
DbField(DbEntity extends SerializableObject> entity, Class type, Field field) {
- this(entity, type, field, field.getName());
+ this(entity, type, field, Check.notNull(field, "field").getName());
}
DbField(DbEntity extends SerializableObject> entity, Class type, Field field, String name) {
@@ -80,6 +81,10 @@ public class DbField {
return this;
}
+ public void setSqlType(String sqlType) {
+ this.sqlType = sqlType;
+ }
+
@Override
public boolean equals(Object o) {
if (!equalsCommon(o)) {
diff --git a/src/main/java/jef/model/DbFieldBuilder.java b/src/main/java/jef/model/DbFieldBuilder.java
index 4ff9ba9..dc2fdaf 100644
--- a/src/main/java/jef/model/DbFieldBuilder.java
+++ b/src/main/java/jef/model/DbFieldBuilder.java
@@ -4,7 +4,6 @@ import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import lombok.RequiredArgsConstructor;
-import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
@@ -28,6 +27,11 @@ public class DbFieldBuilder {
return this;
}
+ public DbFieldBuilder sqlType(String sqlType) {
+ field.setSqlType(sqlType);
+ return this;
+ }
+
public DbFieldBuilder isNotNull() {
return isNotNull(true);
}
diff --git a/src/main/java/jef/model/EntityDefaultConstructorChecker.java b/src/main/java/jef/model/EntityDefaultConstructorChecker.java
new file mode 100644
index 0000000..edcd5c1
--- /dev/null
+++ b/src/main/java/jef/model/EntityDefaultConstructorChecker.java
@@ -0,0 +1,23 @@
+package jef.model;
+
+import jef.util.Log;
+
+import java.util.Arrays;
+
+public class EntityDefaultConstructorChecker {
+ static void checkEntities(ModelBuilder mb) {
+ for (DbEntityBuilder> entity : mb.entities()) {
+ checkEntity(entity);
+ }
+ }
+
+ static void checkEntity(DbEntityBuilder> entity) {
+ Log.debug("Checking default constructor exists for entity '" + entity.name() + "' of type " + entity.className());
+
+ //check no arg constructor
+ Class> clazz = entity.type().orElseThrow();
+ if (Arrays.stream(clazz.getDeclaredConstructors()).noneMatch(e -> e.getParameterCount() == 0)) {
+ throw new ModelException("Class '" + clazz.getSimpleName() + "' does not have a default constructor!");
+ }
+ }
+}
diff --git a/src/main/java/jef/model/ModelBuilder.java b/src/main/java/jef/model/ModelBuilder.java
index 05fd1e9..d3e60ee 100644
--- a/src/main/java/jef/model/ModelBuilder.java
+++ b/src/main/java/jef/model/ModelBuilder.java
@@ -1,16 +1,13 @@
package jef.model;
+import jef.Database;
import jef.model.annotations.processors.AnnotationProcessor;
-import jef.model.annotations.processors.ForeignKeyProcessor;
-import jef.model.annotations.processors.IndexProcessor;
-import jef.model.annotations.processors.KeyProcessor;
-import jef.model.annotations.processors.NotNullProcessor;
-import jef.model.annotations.processors.UniqueProcessor;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.PrimaryKeyConstraint;
import jef.model.constraints.UniqueKeyConstraint;
+import jef.mysql.MysqlDatabase;
import jef.serializable.SerializableObject;
import jef.util.Check;
import jef.util.Util;
@@ -47,9 +44,10 @@ public class ModelBuilder {
}
}
- private static ModelBuilder from0(Class extends DbContext> context, ModelBuilderOptions options) throws Throwable {
+ private static ModelBuilder from0(Class extends DbContext> context, ModelBuilderOptions options) {
var mb = new ModelBuilder(new ArrayList<>());
EntityInitializer.initEntities(mb, context);
+ EntityDefaultConstructorChecker.checkEntities(mb);
PrimaryKeyInitializer.initPrimaryKeys(mb);
ForeignKeyExposeInitializer.initForeignKeyExposures(mb);
ForeignKeyInitializer.initForeignKeys(mb);
@@ -58,20 +56,25 @@ public class ModelBuilder {
processor.apply(mb);
}
- Util.ThrowableFunction init;
+ Util.ThrowableBiFunction init;//TODO create a function for initializing db contexts
try {
- var ctor = context.getDeclaredConstructor(DbContextOptions.class);
+ var ctor = context.getDeclaredConstructor(Database.class, DbContextOptions.class);
init = ctor::newInstance;
} catch (NoSuchMethodException e) {
try {
- var ctor = context.getDeclaredConstructor();
- init = o -> ctor.newInstance();
+ var ctor = context.getDeclaredConstructor(DbContextOptions.class);
+ init = (d, o) -> ctor.newInstance(o);
} catch (NoSuchMethodException e2) {
- throw new RuntimeException(e);
+ try {
+ var ctor = context.getDeclaredConstructor();
+ init = (d, o) -> ctor.newInstance();
+ } catch (NoSuchMethodException e3) {
+ throw new RuntimeException(e);
+ }
}
}
try {
- DbContext instance = init.apply(options.getContextOptions());
+ DbContext instance = init.apply(new MysqlDatabase(null, null), options.getContextOptions());//TODO
instance.onModelCreate(mb);
instance.onModelValidate(mb);
} catch (Throwable e) {
@@ -143,7 +146,12 @@ public class ModelBuilder {
*/
public DbEntityBuilder entity(Class clazz) {
Check.notNull(clazz, "clazz");
- return entity(clazz.getName());
+ var entity = (DbEntity) getEntity(clazz);
+ if (entity == null) {
+ entity = new DbEntity<>(clazz);
+ entities.add(entity);
+ }
+ return new DbEntityBuilder<>(this, entity);
}
/**
@@ -199,6 +207,7 @@ public class ModelBuilder {
var nf = new DbField(entity, e.getName(), e.getTypeName());
nf.setField(e.getField());
nf.setType(e.getType());
+ nf.setSqlType(e.getSqlType());
nf.setNotNull(e.isNotNull());
nf.setModelField(e.isModelField());
nf.setDatabaseField(e.isDatabaseField());
diff --git a/src/main/java/jef/model/ModelBuilderOptions.java b/src/main/java/jef/model/ModelBuilderOptions.java
index 70a880d..363ddfa 100644
--- a/src/main/java/jef/model/ModelBuilderOptions.java
+++ b/src/main/java/jef/model/ModelBuilderOptions.java
@@ -30,6 +30,6 @@ public class ModelBuilderOptions {
KeyProcessor.INSTANCE,
ForeignKeyProcessor.INSTANCE
));
- this.contextOptions = new DbContextOptions();
+ this.contextOptions = new DbContextOptions(null);//TODO
}
}
diff --git a/src/main/java/jef/model/PrimaryKeyInitializer.java b/src/main/java/jef/model/PrimaryKeyInitializer.java
index 6cd40aa..a674788 100644
--- a/src/main/java/jef/model/PrimaryKeyInitializer.java
+++ b/src/main/java/jef/model/PrimaryKeyInitializer.java
@@ -16,7 +16,7 @@ class PrimaryKeyInitializer {
}
static void initPrimaryKeys(ModelBuilder mb, DbEntityBuilder> entity) {
- var fields = ReflectionUtil.getFieldsRecursive(entity.type().get());
+ var fields = ReflectionUtil.getFieldsRecursive(entity.type().orElseThrow());
var idFields = new ArrayList();
//search for fields with @Id annotation
diff --git a/src/main/java/jef/model/SqlTypeMapper.java b/src/main/java/jef/model/SqlTypeMapper.java
new file mode 100644
index 0000000..f9449f7
--- /dev/null
+++ b/src/main/java/jef/model/SqlTypeMapper.java
@@ -0,0 +1,21 @@
+package jef.model;
+
+import java.util.Optional;
+
+public class SqlTypeMapper {
+ public Optional map(String typeName) {
+ if (typeName == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(switch (typeName) {
+ case "java.lang.String" -> "VARCHAR(255)";//TODO add length and precision as param
+ case "int" -> "INT(11)";
+ case "float" -> "FLOAT";
+ case "double" -> "DOUBLE";
+ case "boolean" -> "INT(11)";
+ case "short" -> "INT(11)";
+ case "long" -> "BIGINT";
+ default -> null;
+ });
+ }
+}
diff --git a/src/main/java/jef/model/migration/MigrationBuilder.java b/src/main/java/jef/model/migration/MigrationBuilder.java
index f098e92..4253621 100644
--- a/src/main/java/jef/model/migration/MigrationBuilder.java
+++ b/src/main/java/jef/model/migration/MigrationBuilder.java
@@ -15,10 +15,12 @@ import jef.model.migration.operation.MigrationOperation;
import jef.model.migration.operation.RenameFieldOperation;
import jef.model.migration.operation.RenameTableOperation;
import jef.model.migration.operation.UpdateFieldOperation;
+import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
+@Getter
public class MigrationBuilder {
private final List> operations = new ArrayList<>();
diff --git a/src/main/java/jef/model/migration/creator/MigrationBuilderGenerator.java b/src/main/java/jef/model/migration/creator/MigrationBuilderGenerator.java
index bb040fa..467f266 100644
--- a/src/main/java/jef/model/migration/creator/MigrationBuilderGenerator.java
+++ b/src/main/java/jef/model/migration/creator/MigrationBuilderGenerator.java
@@ -58,19 +58,21 @@ public class MigrationBuilderGenerator {
//generate migration class file
var normalImports = imports.stream().filter(e -> !e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
var javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
- var java = "package " + packageName + ";\n"
- + "\n"
- + normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
- + javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
- + "public class " + name + " implements Migration {\n"
- + " public void up(MigrationBuilder mb) {\n"
- + " " + migrationUp.replace("\n", "\n ") + "\n"
- + " }\n"
- + "\n"
- + " public void down(MigrationBuilder mb) {\n"
- + " " + migrationDown.replace("\n", "\n ") + "\n"
- + " }\n"
- + "}\n";
+ var normalImportsString = normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
+ var javaImportsString = javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
+
+ var java = (packageName != null ? "package " + packageName + ";\n\n" : "")
+ + normalImportsString + (normalImportsString.length() > 0 ? "\n\n" : "")
+ + javaImportsString + (javaImportsString.length() > 0 ? "\n\n" : "")
+ + "public class " + name + " implements Migration {\n"
+ + " public void up(MigrationBuilder mb) {\n"
+ + " " + migrationUp.replace("\n", "\n ") + "\n"
+ + " }\n"
+ + "\n"
+ + " public void down(MigrationBuilder mb) {\n"
+ + " " + migrationDown.replace("\n", "\n ") + "\n"
+ + " }\n"
+ + "}\n";
return java;
}
@@ -99,8 +101,7 @@ public class MigrationBuilderGenerator {
}
private String getMigrationJava(MigrationOperation migrationOperation) {
- var mapper = ((Function)
- OP_TO_STRING_MAPPERS.getOrDefault(migrationOperation.getClass(), UNSUPPORTED_MIGRATION_OPERATION_FUNCTION));
+ var mapper = (Function) OP_TO_STRING_MAPPERS.getOrDefault(migrationOperation.getClass(), UNSUPPORTED_MIGRATION_OPERATION_FUNCTION);
return mapper.apply(migrationOperation);
}
diff --git a/src/main/java/jef/model/migration/creator/MigrationCreator.java b/src/main/java/jef/model/migration/creator/MigrationCreator.java
index 74a1a24..bdcac89 100644
--- a/src/main/java/jef/model/migration/creator/MigrationCreator.java
+++ b/src/main/java/jef/model/migration/creator/MigrationCreator.java
@@ -2,7 +2,7 @@ package jef.model.migration.creator;
import jef.model.DbField;
import jef.model.ModelBuilder;
-import jef.model.ModelException;
+import jef.model.SqlTypeMapper;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
@@ -30,11 +30,10 @@ import lombok.Setter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
public class MigrationCreator {
-
-
public Result createMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, String currentSnapshotJava) {
var result = new Result();
@@ -66,7 +65,7 @@ public class MigrationCreator {
}
private String generateModelBuilderJava(ModelBuilder mb, String name, String packageName) {
- return new ModelBuilderGenerator(mb, name, packageName).generate().getJava();
+ return new ModelBuilderGenerator(mb, name, packageName, new SqlTypeMapper()).generate().getJava();//TODO mapper
}
private Result generateMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, Result result) {
@@ -128,13 +127,17 @@ public class MigrationCreator {
.filter(DbField::isDatabaseField)
.map(e -> new AddFieldOperation.Builder(toEntity.getName(), e.getName())
.notNull(e.isNotNull())
- .sqlType("TODO"))
+ .sqlType(getSqlType(e)))
.toList()
));
}
}
}
+ private String getSqlType(DbField> e) {
+ return Optional.ofNullable(e.getSqlType()).or(() -> new SqlTypeMapper().map(e.getTypeName())).orElse(null);
+ }
+
private void addTableRenameGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List steps) {
for (var toEntity : toReduced.getEntities()) {
var fromEntity = fromReduced.getEntity(toEntity.getTypeName());
diff --git a/src/main/java/jef/model/migration/creator/ModelBuilderGenerator.java b/src/main/java/jef/model/migration/creator/ModelBuilderGenerator.java
index d9a7875..8e95219 100644
--- a/src/main/java/jef/model/migration/creator/ModelBuilderGenerator.java
+++ b/src/main/java/jef/model/migration/creator/ModelBuilderGenerator.java
@@ -5,6 +5,7 @@ import jef.model.DbEntity;
import jef.model.DbEntityBuilder;
import jef.model.DbField;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
@@ -17,6 +18,7 @@ import lombok.RequiredArgsConstructor;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -25,6 +27,7 @@ public class ModelBuilderGenerator {
private final ModelBuilder mb;
private final String name;
private final String packageName;
+ private final SqlTypeMapper sqlTypeMapper;
private final Set> imports = new HashSet<>();
@Getter
@@ -50,13 +53,14 @@ public class ModelBuilderGenerator {
+ indent + "DbEntityBuilder referencedEntity;\n";
for (DbEntity extends SerializableObject> entity : mb.getEntities()) {
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
- + indent + " .name(\"" + entity.getName() + "\");\n";
+ + indent + " .name(\"" + entity.getName() + "\");\n";
for (DbField> field : entity.getFields()) {
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
+ indent + " .field(\"" + field.getName() + "\", \"" + field.getTypeName() + "\")"
+ + "\n" + indent + " .sqlType(" + getSqlType(field) + ")"
+ (field.isNotNull() ? "\n" + indent + " .isNotNull()" : "")
+ "\n" + indent + " .isDatabaseField(" + field.isDatabaseField() + ")"
- + "\n" + indent + " .isModelField(" + field.isModelField() + ");";
+ + "\n" + indent + " .isModelField(" + field.isModelField() + ");\n";
}
if (entity.getPrimaryKey() != null) {
imports.add(List.class);
@@ -105,10 +109,14 @@ public class ModelBuilderGenerator {
var javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
//finalize
- java = "package " + packageName + ";\n\n"
+ java = (packageName != null ? "package " + packageName + ";\n\n" : "")
+ normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
+ javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
+ java;
return java;
}
+
+ private String getSqlType(DbField> f) {
+ return Optional.ofNullable(f.getSqlType()).or(() -> sqlTypeMapper.map(f.getTypeName())).map(e -> "\"" + e + "\"").orElse(null);
+ }
}
diff --git a/src/main/java/jef/mysql/MysqlDatabase.java b/src/main/java/jef/mysql/MysqlDatabase.java
new file mode 100644
index 0000000..9e08991
--- /dev/null
+++ b/src/main/java/jef/mysql/MysqlDatabase.java
@@ -0,0 +1,19 @@
+package jef.mysql;
+
+import jef.Database;
+import jef.DatabaseOptions;
+import jef.MigrationException;
+import jef.mysql.migration.MysqlMigrationApplier;
+
+import java.sql.Connection;
+
+public class MysqlDatabase extends Database {
+ public MysqlDatabase(Connection connection, DatabaseOptions options) {
+ super(connection, options);
+ }
+
+ @Override
+ public void migrate() throws MigrationException {
+ new MysqlMigrationApplier(connection, options).migrate();
+ }
+}
diff --git a/src/main/java/jef/mysql/migration/MigrationApplier.java b/src/main/java/jef/mysql/migration/MigrationApplier.java
new file mode 100644
index 0000000..0dc1f0f
--- /dev/null
+++ b/src/main/java/jef/mysql/migration/MigrationApplier.java
@@ -0,0 +1,109 @@
+package jef.mysql.migration;
+
+import jef.DatabaseOptions;
+import jef.MigrationException;
+import jef.model.migration.Migration;
+import jef.model.migration.MigrationBuilder;
+import jef.model.migration.operation.MigrationOperation;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+@Getter
+public abstract class MigrationApplier {
+ protected final Connection connection;
+ protected final DatabaseOptions options;
+
+ public void migrate() throws MigrationException {
+ var migrations = findMigrations(options.getMigrationsPackage()); //TODO find all migrations, support multiple db contexts
+ try {
+ if (migrationsTableExists()) {
+ getAppliedMigrations().forEach(a -> migrations.removeIf(m -> m.getClass().getSimpleName().equals(a)));
+ } else {
+ createMigrationsTable();
+ }
+ for (Migration migration : migrations) {
+ applyMigration(migration);
+ }
+ } catch (SQLException e) {
+ throw new MigrationException(e);
+ }
+ }
+
+ protected void applyMigration(Migration m) throws MigrationException {
+ var mb = new MigrationBuilder();
+ m.up(mb);
+ var operations = mb.getOperations().stream().map(MigrationOperation.Builder::build).toList();
+
+ try {
+ connection.setAutoCommit(false);
+ try {
+ for (MigrationOperation op : operations) {
+ var stmt = MysqlMigrationOperationTranslator.translate(connection, op);
+ try {
+ stmt.execute();
+ } catch (SQLException e) {
+ throw new SQLException("Failed to execute query: " + stmt, e);
+ } finally {
+ try {
+ stmt.close();
+ } catch (SQLException ignored) {
+ }
+ }
+ }
+ insertMigrationLog(m);
+ connection.commit();
+ } catch (SQLException e) {
+ connection.rollback();
+ throw e;
+ } finally {
+ connection.setAutoCommit(true);
+ }
+ } catch (SQLException e) {
+ throw new MigrationException("Failed to apply migration '" + m.getClass().getSimpleName() + "' to the database: " + e.getLocalizedMessage(), e);
+ }
+ }
+
+ protected List findMigrations(String packageName) throws MigrationException {
+ try (var is = getClass().getClassLoader().getResourceAsStream(packageName.replace(".", "/"));
+ var reader = new BufferedReader(new InputStreamReader(is))) {
+ return reader.lines()
+ .filter(line -> line.endsWith(".class"))
+ .map(line -> line.substring(0, line.length() - ".class".length()))
+ .map(line -> {
+ try {
+ var cls = getClass().getClassLoader().loadClass(packageName + (packageName.isEmpty() ? "" : ".") + line);
+ if (!Migration.class.isAssignableFrom(cls)) {
+ return null;
+ }
+ return (Migration) cls.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ } catch (Exception e) {
+ if (e instanceof RuntimeException re) {
+ e = re;
+ }
+ throw new MigrationException("Error while looking up migrations in package '" + packageName + "'", e);
+ }
+ }
+
+ protected abstract boolean migrationsTableExists() throws SQLException;
+
+ protected abstract void createMigrationsTable() throws SQLException;
+
+ protected abstract List getAppliedMigrations() throws SQLException;
+
+ protected abstract void insertMigrationLog(Migration m) throws SQLException;
+}
diff --git a/src/main/java/jef/mysql/migration/MysqlMigrationApplier.java b/src/main/java/jef/mysql/migration/MysqlMigrationApplier.java
new file mode 100644
index 0000000..61ed3cd
--- /dev/null
+++ b/src/main/java/jef/mysql/migration/MysqlMigrationApplier.java
@@ -0,0 +1,76 @@
+package jef.mysql.migration;
+
+import jef.DatabaseOptions;
+import jef.model.migration.Migration;
+import jef.model.migration.operation.AddFieldOperation;
+import jef.model.migration.operation.AddTableOperation;
+import jef.model.migration.operation.AddUniqueKeyOperation;
+import jef.model.migration.operation.MigrationOperation;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MysqlMigrationApplier extends MigrationApplier {
+ public MysqlMigrationApplier(Connection connection, DatabaseOptions options) {
+ super(connection, options);
+ }
+
+ @Override
+ protected void insertMigrationLog(Migration m) throws SQLException {
+ try (var stmt = connection.prepareStatement("INSERT INTO `__jef_migration_log` (`name`, `version`) VALUES (?, ?)")) {//TODO configurable log table name
+ stmt.setString(1, m.getClass().getSimpleName());
+ stmt.setString(2, "0.1"); //TODO insert actual library version
+ stmt.executeUpdate();
+ }
+ }
+
+ @Override
+ protected boolean migrationsTableExists() throws SQLException {
+ try (var stmt = connection.prepareStatement("SHOW TABLES");
+ var res = stmt.executeQuery()) {
+ while (res.next()) {
+ if (res.getString(1).equals("__jef_migration_log")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void createMigrationsTable() throws SQLException {
+ var table = "__jef_migration_log";
+ var ops = List.of(
+ new AddTableOperation.Builder(table, List.of(
+ new AddFieldOperation.Builder(table, "name").notNull(true).sqlType("VARCHAR(255)"),
+ new AddFieldOperation.Builder(table, "version").notNull(true).sqlType("VARCHAR(255)")
+ )),
+ new AddUniqueKeyOperation.Builder("U_" + table + "_name", table, List.of("name"))
+ );
+ for (MigrationOperation.Builder extends MigrationOperation> e : ops) {
+ try (var stmt = MysqlMigrationOperationTranslator.translate(connection, e.build())) {
+ stmt.executeUpdate();
+ }
+ }
+ }
+
+ @Override
+ protected List getAppliedMigrations() throws SQLException {
+ var ret = new ArrayList();
+ if (!migrationsTableExists()) {
+ return ret;
+ }
+
+ try (var stmt = connection.prepareStatement("SELECT `name` FROM `__jef_migration_log`");
+ var res = stmt.executeQuery()) {
+ while (res.next()) {
+ if (res.getString(1).equals("__jef_migration_log")) {
+ ret.add(res.getString("name"));
+ }
+ }
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/jef/mysql/migration/MysqlMigrationOperationTranslator.java b/src/main/java/jef/mysql/migration/MysqlMigrationOperationTranslator.java
new file mode 100644
index 0000000..cb5093d
--- /dev/null
+++ b/src/main/java/jef/mysql/migration/MysqlMigrationOperationTranslator.java
@@ -0,0 +1,149 @@
+package jef.mysql.migration;
+
+import jef.model.migration.operation.AddFieldOperation;
+import jef.model.migration.operation.AddForeignKeyOperation;
+import jef.model.migration.operation.AddIndexOperation;
+import jef.model.migration.operation.AddKeyOperation;
+import jef.model.migration.operation.AddPrimaryKeyOperation;
+import jef.model.migration.operation.AddTableOperation;
+import jef.model.migration.operation.AddUniqueKeyOperation;
+import jef.model.migration.operation.DropConstraintOperation;
+import jef.model.migration.operation.DropFieldOperation;
+import jef.model.migration.operation.DropTableOperation;
+import jef.model.migration.operation.MigrationOperation;
+import jef.model.migration.operation.RenameFieldOperation;
+import jef.model.migration.operation.RenameTableOperation;
+import jef.model.migration.operation.UpdateFieldOperation;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class MysqlMigrationOperationTranslator {
+ private static final Map, Mapper> translators;
+
+ static {
+ var map = new HashMap, Mapper>();
+ map.put(AddFieldOperation.class, MysqlMigrationOperationTranslator::translateAddFieldOperation);
+ map.put(AddForeignKeyOperation.class, MysqlMigrationOperationTranslator::translateAddForeignKeyOperation);
+ map.put(AddIndexOperation.class, MysqlMigrationOperationTranslator::translateAddIndexOperation);
+ map.put(AddKeyOperation.class, MysqlMigrationOperationTranslator::translateAddKeyOperation);
+ map.put(AddPrimaryKeyOperation.class, MysqlMigrationOperationTranslator::translateAddPrimaryKeyOperation);
+ map.put(AddTableOperation.class, MysqlMigrationOperationTranslator::translateAddTableOperation);
+ map.put(AddUniqueKeyOperation.class, MysqlMigrationOperationTranslator::translateAddUniqueKeyOperation);
+ map.put(DropConstraintOperation.class, MysqlMigrationOperationTranslator::translateDropConstraintOperation);
+ map.put(DropFieldOperation.class, MysqlMigrationOperationTranslator::translateDropFieldOperation);
+ map.put(DropTableOperation.class, MysqlMigrationOperationTranslator::translateDropTableOperation);
+ map.put(RenameFieldOperation.class, MysqlMigrationOperationTranslator::translateRenameFieldOperation);
+ map.put(RenameTableOperation.class, MysqlMigrationOperationTranslator::translateRenameTableOperation);
+ map.put(UpdateFieldOperation.class, MysqlMigrationOperationTranslator::translateUpdateFieldOperation);
+ translators = Collections.unmodifiableMap(map);
+ }
+
+ public static PreparedStatement translate(Connection connection, MigrationOperation operation) throws SQLException {
+ return Optional.ofNullable(translators.get(operation.getClass())).orElseThrow().apply(connection, operation);
+ }
+
+ private static PreparedStatement translateAddFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ AddFieldOperation op = (AddFieldOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " ADD COLUMN `" + op.getField() + "` "
+ + op.getSqlType() + (op.isNotNull() ? " NOT NULL" : "") //TODO add after field specification
+ + " LAST");
+ }
+
+ private static PreparedStatement translateAddForeignKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ AddForeignKeyOperation op = (AddForeignKeyOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " ADD CONSTRAINT `" + op.getName() + "`"
+ + " FOREIGN KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")"
+ + " REFERENCES `" + op.getReferencedTable() + "`(" + op.getReferencedFields().stream()
+ .map(e -> "`" + e + "`")
+ .collect(Collectors.joining(", ")) + ")");
+ }
+
+ private static PreparedStatement translateAddIndexOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ AddIndexOperation op = (AddIndexOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " ADD CONSTRAINT `" + op.getName() + "`"
+ + " INDEX (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
+ }
+
+ private static PreparedStatement translateAddKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ AddKeyOperation op = (AddKeyOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " ADD CONSTRAINT `" + op.getName() + "`"
+ + " KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
+ }
+
+ private static PreparedStatement translateAddPrimaryKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ AddPrimaryKeyOperation op = (AddPrimaryKeyOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " ADD " + (!op.getName().equals("PRIMARY") ? "CONSTRAINT `" + op.getName() + "`" : "")
+ + " PRIMARY KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
+// + " ADD CONSTRAINT `" + op.getName() + "`"
+// + " PRIMARY KEY (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
+ }
+
+ private static PreparedStatement translateAddTableOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ AddTableOperation op = (AddTableOperation) operation;
+ return connection.prepareStatement("CREATE TABLE `" + op.getTable() + "` ("
+ + op.getFields().stream().map(e -> {
+ var f = e.build();
+ return "`" + f.getField() + "` " + f.getSqlType() + (f.isNotNull() ? " NOT NULL" : "");
+ }).collect(Collectors.joining(", "))
+ + ")"); //TODO default collocation from database config or operation, field collation, primary key, constraints
+ }
+
+ private static PreparedStatement translateAddUniqueKeyOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ AddUniqueKeyOperation op = (AddUniqueKeyOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " ADD CONSTRAINT `" + op.getName() + "`"
+ + " UNIQUE (" + op.getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")");
+ }
+
+ private static PreparedStatement translateDropConstraintOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ DropConstraintOperation op = (DropConstraintOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " DROP CONSTRAINT `" + op.getName() + "`");
+ }
+
+ private static PreparedStatement translateDropFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ DropFieldOperation op = (DropFieldOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " DROP COLUMN `" + op.getField() + "`");
+ }
+
+ private static PreparedStatement translateDropTableOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ DropTableOperation op = (DropTableOperation) operation;
+ return connection.prepareStatement("DROP TABLE `" + op.getTable() + "`");
+ }
+
+ private static PreparedStatement translateRenameFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ RenameFieldOperation op = (RenameFieldOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " RENAME COLUMN `" + op.getOldName() + "` `" + op.getNewName() + "`");
+ }
+
+ private static PreparedStatement translateRenameTableOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ RenameTableOperation op = (RenameTableOperation) operation;
+ return connection.prepareStatement("RENAME TABLE `" + op.getOldName() + "` `" + op.getNewName() + "`");
+ }
+
+ private static PreparedStatement translateUpdateFieldOperation(Connection connection, MigrationOperation operation) throws SQLException {
+ UpdateFieldOperation op = (UpdateFieldOperation) operation;
+ return connection.prepareStatement("ALTER TABLE `" + op.getTable() + "`"
+ + " ALTER COLUMN `" + op.getField() + "` `" + op.getNewName() + "`"
+ + op.getSqlType() + (op.isNotNull() ? " NOT NULL" : ""));
+ }
+
+ @FunctionalInterface
+ private interface Mapper {
+ PreparedStatement apply(Connection connection, T operation) throws SQLException;
+ }
+}
diff --git a/src/main/java/jef/util/Util.java b/src/main/java/jef/util/Util.java
index 7a2255c..d74a4cc 100644
--- a/src/main/java/jef/util/Util.java
+++ b/src/main/java/jef/util/Util.java
@@ -20,4 +20,8 @@ public abstract class Util {
public interface ThrowableFunction {
R apply(T t) throws Throwable;
}
+ @FunctionalInterface
+ public interface ThrowableBiFunction {
+ R apply(T t, U u) throws Throwable;
+ }
}
diff --git a/src/test/java/jef/asm/OptimizedAsmParserTest.java b/src/test/java/jef/asm/OptimizedAsmParserTest.java
index 826079b..da84411 100644
--- a/src/test/java/jef/asm/OptimizedAsmParserTest.java
+++ b/src/test/java/jef/asm/OptimizedAsmParserTest.java
@@ -150,6 +150,10 @@ public class OptimizedAsmParserTest {
act = new OptimizedAsmParser((SerializablePredicate>) (TestClass e) -> e.l == 0L || e.l == 1L).parse().getExpression().toString();
Assertions.assertEquals("`l` = 0 OR `l` = 1", act);
+//
+//
+// act = new OptimizedAsmParser((SerializablePredicate>) (TestClass e) -> e.b == false || e.b == true).parse().getExpression().toString();
+// Assertions.assertEquals("`b` = 0 OR `b` = 1", act);
}
@Test
diff --git a/src/test/java/jef/model/EntityDefaultConstructorCheckerTest.java b/src/test/java/jef/model/EntityDefaultConstructorCheckerTest.java
new file mode 100644
index 0000000..848c297
--- /dev/null
+++ b/src/test/java/jef/model/EntityDefaultConstructorCheckerTest.java
@@ -0,0 +1,39 @@
+package jef.model;
+
+import jef.model.annotations.Id;
+import jef.serializable.SerializableObject;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class EntityDefaultConstructorCheckerTest {
+ @Test
+ public void test0ArgConstructor() {
+ var mb = new ModelBuilder();
+ EntityDefaultConstructorChecker.checkEntity(mb.entity(TestClass0Arg.class));
+ assertTrue(true);
+ }
+
+ @Test
+ public void testNon0ArgConstructor() {
+ var mb = new ModelBuilder();
+ var ex = assertThrows(ModelException.class, () -> EntityDefaultConstructorChecker.checkEntity(mb.entity(TestClassNon0Arg.class)));
+ assertEquals("Class 'TestClassNon0Arg' does not have a default constructor!", ex.getMessage());
+ }
+
+ @NoArgsConstructor
+ public static class TestClass0Arg extends SerializableObject {
+ @Id
+ public int i = 1;
+ }
+
+ @AllArgsConstructor
+ public static class TestClassNon0Arg extends SerializableObject {
+ @Id
+ public int i = 1;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/jef/model/ModelBuilderSimpleTest.java b/src/test/java/jef/model/ModelBuilderSimpleTest.java
index 3150e53..7ef0ae7 100644
--- a/src/test/java/jef/model/ModelBuilderSimpleTest.java
+++ b/src/test/java/jef/model/ModelBuilderSimpleTest.java
@@ -34,5 +34,4 @@ class ModelBuilderSimpleTest {
public float f;
public long l;
}
-
}
\ No newline at end of file
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddEntityTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddEntityTest.java
index 466ede0..2072942 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorAddEntityTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddEntityTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.constraints.ForeignKeyConstraint;
@@ -31,7 +32,7 @@ public class MigrationCreatorAddEntityTest extends MigrationCreatorTestBase {
to.getEntity("AddedEntity").setPrimaryKey(new PrimaryKeyConstraint(to.getEntity("AddedEntity"), List.of(to.getEntity("AddedEntity").getField("id"))));
to.getEntity("AddedEntity").addForeignKey(new ForeignKeyConstraint(to.getEntity("AddedEntity"), List.of(to.getEntity("AddedEntity").getField("addedField")), to.getEntity("AddedEntity"), List.of(to.getEntity("AddedEntity").getField("id")), ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE));
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddFieldTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddFieldTest.java
index cc355ec..bd5d30f 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorAddFieldTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddFieldTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.constraints.ForeignKeyConstraint;
@@ -27,7 +28,7 @@ public class MigrationCreatorAddFieldTest extends MigrationCreatorTestBase{
ent.getOrCreateField("addedField", int.class.getName());
ent.addForeignKey(new ForeignKeyConstraint(ent, List.of(ent.getField("addedField")), ent, List.of(ent.getField("i")), ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE));
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddForeignKeyTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddForeignKeyTest.java
index 9b96948..538416f 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorAddForeignKeyTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddForeignKeyTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.constraints.ForeignKeyConstraint;
@@ -24,7 +25,7 @@ public class MigrationCreatorAddForeignKeyTest extends MigrationCreatorTestBase
var ent = to.getEntity(TestClass.class);
ent.addForeignKey(new ForeignKeyConstraint(ent, List.of(ent.getField("i2")), ent, List.of(ent.getField("i")), ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE));
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddIndexTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddIndexTest.java
index f77a59b..b4580d2 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorAddIndexTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddIndexTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.migration.operation.AddIndexOperation;
@@ -23,7 +24,7 @@ public class MigrationCreatorAddIndexTest extends MigrationCreatorTestBase{
var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).isIndex(true);
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddKeyTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddKeyTest.java
index d251c9e..835d342 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorAddKeyTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddKeyTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.migration.operation.AddKeyOperation;
@@ -23,7 +24,7 @@ public class MigrationCreatorAddKeyTest extends MigrationCreatorTestBase{
var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).isKey(true);
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddUniqueTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddUniqueTest.java
index 0c3ddbb..980a018 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorAddUniqueTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddUniqueTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.migration.operation.AddUniqueKeyOperation;
@@ -23,7 +24,7 @@ public class MigrationCreatorAddUniqueTest extends MigrationCreatorTestBase{
var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).isUnique(true);
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorEmptyTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorEmptyTest.java
index 6f4e9f4..a5871e4 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorEmptyTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorEmptyTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.annotations.Index;
@@ -22,7 +23,7 @@ public class MigrationCreatorEmptyTest extends MigrationCreatorTestBase{
var from = ModelBuilder.from(Ctx.class);
var to = ModelBuilder.from(Ctx.class);
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "EmptyMigration", "test",new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "EmptyMigration", "test",new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
assertEquals(0, res.getStepsUp().size());
assertEquals(0, res.getStepsDown().size());
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorInitialMigrationTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorInitialMigrationTest.java
index 5affeb4..a6cb504 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorInitialMigrationTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorInitialMigrationTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.migration.operation.AddForeignKeyOperation;
@@ -24,7 +25,7 @@ public class MigrationCreatorInitialMigrationTest extends MigrationCreatorTestBa
var from = new ModelBuilder(List.of());
var to = ModelBuilder.from(Ctx.class);
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "InitialMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "InitialMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameEntityTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameEntityTest.java
index c3e0a98..b92bdfd 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameEntityTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameEntityTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.migration.operation.AddForeignKeyOperation;
@@ -25,7 +26,7 @@ public class MigrationCreatorRenameEntityTest extends MigrationCreatorTestBase {
var ent = to.entity(TestClass.class);
ent.name("d2");
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldConstraintsTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldConstraintsTest.java
index 9d4bb23..1e6a82d 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldConstraintsTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldConstraintsTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.ForeignKey;
import jef.model.annotations.Id;
@@ -31,7 +32,7 @@ public class MigrationCreatorRenameFieldConstraintsTest extends MigrationCreator
var to = ModelBuilder.from(Ctx.class);
to.getEntity(TestClass.class).getField("i").setName("i2");
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldTest.java
index db8abde..8911f19 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldTest.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldTest.java
@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet;
import jef.model.DbContext;
import jef.model.ModelBuilder;
+import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz;
import jef.model.annotations.Id;
import jef.model.migration.operation.RenameFieldOperation;
@@ -22,7 +23,7 @@ public class MigrationCreatorRenameFieldTest extends MigrationCreatorTestBase {
var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).name("d2");
var mc = new MigrationCreator();
- var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava());
+ var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test", new SqlTypeMapper()).generate().getJava());//TODO mapper
try {
validateUp(res);
validateDown(res);
diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorTestBase.java b/src/test/java/jef/model/migration/creator/MigrationCreatorTestBase.java
index 8a2a4f5..2b1dab6 100644
--- a/src/test/java/jef/model/migration/creator/MigrationCreatorTestBase.java
+++ b/src/test/java/jef/model/migration/creator/MigrationCreatorTestBase.java
@@ -18,8 +18,8 @@ import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MigrationCreatorTestBase {
- private static final File JAVA_FILES_DIR = new File("target/jef-tests/sources");
- private static final File CLASS_FILES_DIR = new File("target/jef-tests/classes");
+ private static final File JAVA_FILES_DIR = new File("target/test-generated-migrations/src");//TODO move tot a TESTUTil class or smth
+ private static final File CLASS_FILES_DIR = new File("target/test-generated-migrations/target");
public void validateMigration(MigrationCreator.Result result, ModelBuilder from, ModelBuilder to) {
validateMigrationJava(result.getMigration());
diff --git a/src/test/java/jef/mysql/migration/MysqlMigrationTest.java b/src/test/java/jef/mysql/migration/MysqlMigrationTest.java
new file mode 100644
index 0000000..42f7f3f
--- /dev/null
+++ b/src/test/java/jef/mysql/migration/MysqlMigrationTest.java
@@ -0,0 +1,157 @@
+package jef.mysql.migration;
+
+import jef.Database;
+import jef.DatabaseOptions;
+import jef.DbSet;
+import jef.model.DbContext;
+import jef.model.DbContextOptions;
+import jef.model.annotations.Clazz;
+import jef.model.annotations.ForeignKey;
+import jef.mysql.MysqlDatabase;
+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.File;
+import java.sql.DriverManager;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class MysqlMigrationTest {
+ private static final String GENERATE_MIGRATIONS_FOLDER = "target/test-generated-migrations/";
+ private static final String GENERATE_MIGRATIONS_FOLDER_SRC = GENERATE_MIGRATIONS_FOLDER + "src/";
+ private static final String GENERATE_MIGRATIONS_FOLDER_TARGET = GENERATE_MIGRATIONS_FOLDER + "target/";
+
+ @Test
+ public void test() throws Exception {
+ clearMigrationFolders();
+ generateInitialMigration();
+ compileInitialMigration();
+ Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
+ var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName());
+ var ctxoptions = new DbContextOptions(dboptions);
+ var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
+ var db = new MysqlDatabase(conn, dboptions);
+ var ctx = new Ctx(db, ctxoptions);
+ ctx.getDatabase().migrate();
+ var result = ctx.getCompanies().filter(e -> e.getName().equals("foobar")).toString();
+ System.out.println(result);
+ }
+
+ private void clearMigrationFolders() {
+ delRecursive(new File(GENERATE_MIGRATIONS_FOLDER_SRC + "MysqlMigrationTest"));
+ delRecursive(new File(GENERATE_MIGRATIONS_FOLDER_TARGET + "MysqlMigrationTest"));
+ }
+
+ private void delRecursive(File f) {
+ if (f.isDirectory()) {
+ Arrays.stream(Objects.requireNonNull(f.listFiles()))
+ .filter(e -> !List.of(".", "..").contains(e.getName()))
+ .forEach(this::delRecursive);
+ }
+ f.delete();
+ }
+
+ private void generateInitialMigration() {
+ try {
+ var javaHome = System.getProperty("java.home");
+ var isWindows = System.getProperty("os.name").toLowerCase(Locale.ROOT).equals("win");
+ var java = new File(javaHome, "bin/java" + (isWindows ? ".exe" : ""));
+ var process = new ProcessBuilder()
+ .command(java.getAbsolutePath(),
+ "-cp", "target/classes", "jef.main.Main",
+ "migration", "add", "--cp", "target/test-classes", "-c", "MysqlMigrationTest$Ctx", "-o", GENERATE_MIGRATIONS_FOLDER_SRC + "MysqlMigrationTest", "Initial")
+ .inheritIO()
+ .start();
+ process.waitFor();
+ var exitCode = process.exitValue();
+ assertEquals(0, exitCode, "Initial migration generation failed");
+ } catch (AssertionError | RuntimeException e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new RuntimeException("Error while compiling generated class", t);
+ }
+ }
+
+ private void compileInitialMigration() {
+ try {
+ var javaHome = System.getProperty("java.home");
+ var isWindows = System.getProperty("os.name").toLowerCase(Locale.ROOT).equals("win");
+ var javac = new File(javaHome, "bin/javac" + (isWindows ? ".exe" : ""));
+ var params = new ArrayList<>(List.of(
+ javac.getAbsolutePath(),
+ "-cp", "target/classes" + File.pathSeparator + "target/test-classes",
+ "-encoding", "UTF8",
+ "-g", //debug symbols
+ "-d", GENERATE_MIGRATIONS_FOLDER_TARGET //target
+ ));
+ Arrays.stream(new File(GENERATE_MIGRATIONS_FOLDER_SRC + "MysqlMigrationTest").listFiles(File::isFile)).map(File::getPath).forEach(params::add);
+ var process = new ProcessBuilder()
+ .command(params.toArray(String[]::new))
+ .inheritIO()
+ .start();
+ process.waitFor(10, TimeUnit.SECONDS);
+ var exitCode = process.exitValue();
+ assertEquals(0, exitCode, "Initial migration compilation failed");
+ } catch (AssertionError | RuntimeException e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new RuntimeException("Error while compiling generated class", t);
+ }
+ }
+
+ @Getter
+ public static class Ctx extends DbContext {
+ @Clazz(Company.class)
+ private DbSet companies;
+ @Clazz(Employee.class)
+ private DbSet employees;
+
+ 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;
+ }
+}