added command line to create migrations

This commit is contained in:
wea_ondara
2022-11-23 13:50:54 +01:00
parent 17d71eb2f3
commit 915fc4a87b
43 changed files with 1183 additions and 61 deletions

View File

@@ -10,6 +10,7 @@
## Asm ## Asm
- equals function for registered primitive conversion types - equals function for registered primitive conversion types
- IConst0Fixer: IConst0/1 on its own => false/true - IConst0Fixer: IConst0/1 on its own => false/true
- IConst0Fixer: IConst0/1 false/true depending on compared field type
- actually parse getter - actually parse getter
- resolve Predicate functions (not, and, or) - 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 - don't bind external vars in lambdas, instead make information (parameter and intercepted values) available in a context object

33
pom.xml
View File

@@ -102,6 +102,29 @@
<encoding>${project.build.sourceEncoding}</encoding> <encoding>${project.build.sourceEncoding}</encoding>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>jef.main.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>${project.basedir}/target/test-generated-migrations/target</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
@@ -146,7 +169,15 @@
<version>${mockito.version}</version> <version>${mockito.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- <dependency>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>test</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>javax.persistence</groupId>--> <!-- <groupId>javax.persistence</groupId>-->
<!-- <artifactId>javax.persistence-api</artifactId>--> <!-- <artifactId>javax.persistence-api</artifactId>-->
<!-- <version>2.2</version>--> <!-- <version>2.2</version>-->

View File

@@ -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;
// }
// }
// }
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 <command> [options]");
MigrationCommandHandler.printHelp();
System.exit(1);
}
}

View File

@@ -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<String>();
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<DbContext> findAnyContext(URLClassLoader cl) {
return findContextByName(cl, "");
}
private static Optional<DbContext> findContextByName(URLClassLoader cl, String contextName) {
Util.ThrowableFunction<String, DbContext> 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<Class<?>, 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<URL>();
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 <classpath> [--context/-c <context class name>] [--output/-o <folder for migrations>] <name>");
System.out.println("migration remove --cp <classpath> [--context/-c <context class name>] [--output/-o <folder for migrations>] <name>");
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) {
// }
// }
// }
}

View File

@@ -1,7 +1,11 @@
package jef.model; package jef.model;
import jef.Database;
import jef.DbSet;
import jef.model.annotations.Clazz;
import jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
import lombok.Getter;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
@@ -9,9 +13,40 @@ import java.util.HashMap;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Getter
public abstract class DbContext { public abstract class DbContext {
private static final String ILLEGAL_CHARACTERS = "\"'`,."; 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) { public void onModelCreate(ModelBuilder mb) {
} }

View File

@@ -1,13 +1,15 @@
package jef.model; package jef.model;
import jef.DatabaseOptions;
import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
@Builder @Builder
@NoArgsConstructor @AllArgsConstructor
public class DbContextOptions { public class DbContextOptions {
private final DatabaseOptions databaseOptions;
} }

View File

@@ -27,7 +27,7 @@ import java.util.Objects;
public class DbEntity<T extends SerializableObject> { public class DbEntity<T extends SerializableObject> {
private String typeName; private String typeName;
// @Setter // @Setter
// private Class<T> type; private Class<T> type;
private final List<DbField<?>> fields; private final List<DbField<?>> fields;
private String name; private String name;
private PrimaryKeyConstraint primaryKey; private PrimaryKeyConstraint primaryKey;
@@ -53,7 +53,7 @@ public class DbEntity<T extends SerializableObject> {
} }
DbEntity(Class<T> type, String name, List<DbField<?>> fields) { DbEntity(Class<T> type, String name, List<DbField<?>> fields) {
// this.type = Check.notNull(type, "type"); this.type = type;
this.typeName = type.getName(); this.typeName = type.getName();
this.fields = Check.notNull(fields, "fields"); this.fields = Check.notNull(fields, "fields");
this.name = Check.notNull(name, "name"); this.name = Check.notNull(name, "name");
@@ -218,7 +218,6 @@ public class DbEntity<T extends SerializableObject> {
return new DbEntityBuilder<>(mb, this); return new DbEntityBuilder<>(mb, this);
} }
<R> String extractFieldName(SerializableFunction<T, R> getter) { <R> String extractFieldName(SerializableFunction<T, R> getter) {
try { try {
var expr = new AsmParser(getter).parse().getExpression(); var expr = new AsmParser(getter).parse().getExpression();

View File

@@ -98,7 +98,7 @@ public class DbEntityBuilder<T extends SerializableObject> {
} }
public Optional<Class<T>> type() { public Optional<Class<T>> type() {
return Util.tryGet(() -> (Class<T>) Class.forName(typeName())); return Optional.ofNullable(entity.getType()).or(() -> Util.tryGet(() -> (Class<T>) Class.forName(typeName())));
} }
public String typeName() { public String typeName() {

View File

@@ -24,6 +24,7 @@ public class DbField<T> {
private DbField<?> exposingForeignKeyOf; private DbField<?> exposingForeignKeyOf;
private String name; private String name;
private boolean notNull = false; private boolean notNull = false;
private String sqlType;
DbField(DbEntity<? extends SerializableObject> entity, String name, String typeName) { DbField(DbEntity<? extends SerializableObject> entity, String name, String typeName) {
this.entity = Check.notNull(entity, "entity"); this.entity = Check.notNull(entity, "entity");
@@ -36,7 +37,7 @@ public class DbField<T> {
} }
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) { DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) {
this(entity, type, field, field.getName()); this(entity, type, field, Check.notNull(field, "field").getName());
} }
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) { DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) {
@@ -80,6 +81,10 @@ public class DbField<T> {
return this; return this;
} }
public void setSqlType(String sqlType) {
this.sqlType = sqlType;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (!equalsCommon(o)) { if (!equalsCommon(o)) {

View File

@@ -4,7 +4,6 @@ import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint; import jef.model.constraints.KeyConstraint;
import jef.model.constraints.UniqueKeyConstraint; import jef.model.constraints.UniqueKeyConstraint;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -28,6 +27,11 @@ public class DbFieldBuilder<T> {
return this; return this;
} }
public DbFieldBuilder<T> sqlType(String sqlType) {
field.setSqlType(sqlType);
return this;
}
public DbFieldBuilder<T> isNotNull() { public DbFieldBuilder<T> isNotNull() {
return isNotNull(true); return isNotNull(true);
} }

View File

@@ -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!");
}
}
}

View File

@@ -1,16 +1,13 @@
package jef.model; package jef.model;
import jef.Database;
import jef.model.annotations.processors.AnnotationProcessor; 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.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint; import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint; import jef.model.constraints.KeyConstraint;
import jef.model.constraints.PrimaryKeyConstraint; import jef.model.constraints.PrimaryKeyConstraint;
import jef.model.constraints.UniqueKeyConstraint; import jef.model.constraints.UniqueKeyConstraint;
import jef.mysql.MysqlDatabase;
import jef.serializable.SerializableObject; import jef.serializable.SerializableObject;
import jef.util.Check; import jef.util.Check;
import jef.util.Util; 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<>()); var mb = new ModelBuilder(new ArrayList<>());
EntityInitializer.initEntities(mb, context); EntityInitializer.initEntities(mb, context);
EntityDefaultConstructorChecker.checkEntities(mb);
PrimaryKeyInitializer.initPrimaryKeys(mb); PrimaryKeyInitializer.initPrimaryKeys(mb);
ForeignKeyExposeInitializer.initForeignKeyExposures(mb); ForeignKeyExposeInitializer.initForeignKeyExposures(mb);
ForeignKeyInitializer.initForeignKeys(mb); ForeignKeyInitializer.initForeignKeys(mb);
@@ -58,20 +56,25 @@ public class ModelBuilder {
processor.apply(mb); processor.apply(mb);
} }
Util.ThrowableFunction<DbContextOptions, DbContext> init; Util.ThrowableBiFunction<Database, DbContextOptions, DbContext> init;//TODO create a function for initializing db contexts
try { try {
var ctor = context.getDeclaredConstructor(DbContextOptions.class); var ctor = context.getDeclaredConstructor(Database.class, DbContextOptions.class);
init = ctor::newInstance; init = ctor::newInstance;
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
try { try {
var ctor = context.getDeclaredConstructor(); var ctor = context.getDeclaredConstructor(DbContextOptions.class);
init = o -> ctor.newInstance(); init = (d, o) -> ctor.newInstance(o);
} catch (NoSuchMethodException e2) { } 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 { try {
DbContext instance = init.apply(options.getContextOptions()); DbContext instance = init.apply(new MysqlDatabase(null, null), options.getContextOptions());//TODO
instance.onModelCreate(mb); instance.onModelCreate(mb);
instance.onModelValidate(mb); instance.onModelValidate(mb);
} catch (Throwable e) { } catch (Throwable e) {
@@ -143,7 +146,12 @@ public class ModelBuilder {
*/ */
public <T extends SerializableObject> DbEntityBuilder<T> entity(Class<T> clazz) { public <T extends SerializableObject> DbEntityBuilder<T> entity(Class<T> clazz) {
Check.notNull(clazz, "clazz"); Check.notNull(clazz, "clazz");
return entity(clazz.getName()); var entity = (DbEntity<T>) 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()); var nf = new DbField(entity, e.getName(), e.getTypeName());
nf.setField(e.getField()); nf.setField(e.getField());
nf.setType(e.getType()); nf.setType(e.getType());
nf.setSqlType(e.getSqlType());
nf.setNotNull(e.isNotNull()); nf.setNotNull(e.isNotNull());
nf.setModelField(e.isModelField()); nf.setModelField(e.isModelField());
nf.setDatabaseField(e.isDatabaseField()); nf.setDatabaseField(e.isDatabaseField());

View File

@@ -30,6 +30,6 @@ public class ModelBuilderOptions {
KeyProcessor.INSTANCE, KeyProcessor.INSTANCE,
ForeignKeyProcessor.INSTANCE ForeignKeyProcessor.INSTANCE
)); ));
this.contextOptions = new DbContextOptions(); this.contextOptions = new DbContextOptions(null);//TODO
} }
} }

View File

@@ -16,7 +16,7 @@ class PrimaryKeyInitializer {
} }
static void initPrimaryKeys(ModelBuilder mb, DbEntityBuilder<?> entity) { 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<Field>(); var idFields = new ArrayList<Field>();
//search for fields with @Id annotation //search for fields with @Id annotation

View File

@@ -0,0 +1,21 @@
package jef.model;
import java.util.Optional;
public class SqlTypeMapper {
public Optional<String> 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;
});
}
}

View File

@@ -15,10 +15,12 @@ import jef.model.migration.operation.MigrationOperation;
import jef.model.migration.operation.RenameFieldOperation; import jef.model.migration.operation.RenameFieldOperation;
import jef.model.migration.operation.RenameTableOperation; import jef.model.migration.operation.RenameTableOperation;
import jef.model.migration.operation.UpdateFieldOperation; import jef.model.migration.operation.UpdateFieldOperation;
import lombok.Getter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Getter
public class MigrationBuilder { public class MigrationBuilder {
private final List<MigrationOperation.Builder<?>> operations = new ArrayList<>(); private final List<MigrationOperation.Builder<?>> operations = new ArrayList<>();

View File

@@ -58,19 +58,21 @@ public class MigrationBuilderGenerator {
//generate migration class file //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 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 javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
var java = "package " + packageName + ";\n" var normalImportsString = normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
+ "\n" var javaImportsString = javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\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" var java = (packageName != null ? "package " + packageName + ";\n\n" : "")
+ "public class " + name + " implements Migration {\n" + normalImportsString + (normalImportsString.length() > 0 ? "\n\n" : "")
+ " public void up(MigrationBuilder mb) {\n" + javaImportsString + (javaImportsString.length() > 0 ? "\n\n" : "")
+ " " + migrationUp.replace("\n", "\n ") + "\n" + "public class " + name + " implements Migration {\n"
+ " }\n" + " public void up(MigrationBuilder mb) {\n"
+ "\n" + " " + migrationUp.replace("\n", "\n ") + "\n"
+ " public void down(MigrationBuilder mb) {\n" + " }\n"
+ " " + migrationDown.replace("\n", "\n ") + "\n" + "\n"
+ " }\n" + " public void down(MigrationBuilder mb) {\n"
+ "}\n"; + " " + migrationDown.replace("\n", "\n ") + "\n"
+ " }\n"
+ "}\n";
return java; return java;
} }
@@ -99,8 +101,7 @@ public class MigrationBuilderGenerator {
} }
private String getMigrationJava(MigrationOperation migrationOperation) { private String getMigrationJava(MigrationOperation migrationOperation) {
var mapper = ((Function<MigrationOperation, String>) var mapper = (Function<MigrationOperation, String>) OP_TO_STRING_MAPPERS.getOrDefault(migrationOperation.getClass(), UNSUPPORTED_MIGRATION_OPERATION_FUNCTION);
OP_TO_STRING_MAPPERS.getOrDefault(migrationOperation.getClass(), UNSUPPORTED_MIGRATION_OPERATION_FUNCTION));
return mapper.apply(migrationOperation); return mapper.apply(migrationOperation);
} }

View File

@@ -2,7 +2,7 @@ package jef.model.migration.creator;
import jef.model.DbField; import jef.model.DbField;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.ModelException; import jef.model.SqlTypeMapper;
import jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint; import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint; import jef.model.constraints.KeyConstraint;
@@ -30,11 +30,10 @@ import lombok.Setter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class MigrationCreator { public class MigrationCreator {
public Result createMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, String currentSnapshotJava) { public Result createMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, String currentSnapshotJava) {
var result = new Result(); var result = new Result();
@@ -66,7 +65,7 @@ public class MigrationCreator {
} }
private String generateModelBuilderJava(ModelBuilder mb, String name, String packageName) { 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) { private Result generateMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, Result result) {
@@ -128,13 +127,17 @@ public class MigrationCreator {
.filter(DbField::isDatabaseField) .filter(DbField::isDatabaseField)
.map(e -> new AddFieldOperation.Builder(toEntity.getName(), e.getName()) .map(e -> new AddFieldOperation.Builder(toEntity.getName(), e.getName())
.notNull(e.isNotNull()) .notNull(e.isNotNull())
.sqlType("TODO")) .sqlType(getSqlType(e)))
.toList() .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<MigrationOperation.Builder> steps) { private void addTableRenameGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) { for (var toEntity : toReduced.getEntities()) {
var fromEntity = fromReduced.getEntity(toEntity.getTypeName()); var fromEntity = fromReduced.getEntity(toEntity.getTypeName());

View File

@@ -5,6 +5,7 @@ import jef.model.DbEntity;
import jef.model.DbEntityBuilder; import jef.model.DbEntityBuilder;
import jef.model.DbField; import jef.model.DbField;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint; import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint; import jef.model.constraints.KeyConstraint;
@@ -17,6 +18,7 @@ import lombok.RequiredArgsConstructor;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -25,6 +27,7 @@ public class ModelBuilderGenerator {
private final ModelBuilder mb; private final ModelBuilder mb;
private final String name; private final String name;
private final String packageName; private final String packageName;
private final SqlTypeMapper sqlTypeMapper;
private final Set<Class<?>> imports = new HashSet<>(); private final Set<Class<?>> imports = new HashSet<>();
@Getter @Getter
@@ -50,13 +53,14 @@ public class ModelBuilderGenerator {
+ indent + "DbEntityBuilder referencedEntity;\n"; + indent + "DbEntityBuilder referencedEntity;\n";
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) { for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n" java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
+ indent + " .name(\"" + entity.getName() + "\");\n"; + indent + " .name(\"" + entity.getName() + "\");\n";
for (DbField<?> field : entity.getFields()) { for (DbField<?> field : entity.getFields()) {
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n" java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
+ indent + " .field(\"" + field.getName() + "\", \"" + field.getTypeName() + "\")" + indent + " .field(\"" + field.getName() + "\", \"" + field.getTypeName() + "\")"
+ "\n" + indent + " .sqlType(" + getSqlType(field) + ")"
+ (field.isNotNull() ? "\n" + indent + " .isNotNull()" : "") + (field.isNotNull() ? "\n" + indent + " .isNotNull()" : "")
+ "\n" + indent + " .isDatabaseField(" + field.isDatabaseField() + ")" + "\n" + indent + " .isDatabaseField(" + field.isDatabaseField() + ")"
+ "\n" + indent + " .isModelField(" + field.isModelField() + ");"; + "\n" + indent + " .isModelField(" + field.isModelField() + ");\n";
} }
if (entity.getPrimaryKey() != null) { if (entity.getPrimaryKey() != null) {
imports.add(List.class); 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(); var javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
//finalize //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" + 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" + javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
+ java; + java;
return java; return java;
} }
private String getSqlType(DbField<?> f) {
return Optional.ofNullable(f.getSqlType()).or(() -> sqlTypeMapper.map(f.getTypeName())).map(e -> "\"" + e + "\"").orElse(null);
}
} }

View File

@@ -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();
}
}

View File

@@ -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<Migration> 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<String> getAppliedMigrations() throws SQLException;
protected abstract void insertMigrationLog(Migration m) throws SQLException;
}

View File

@@ -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<String> getAppliedMigrations() throws SQLException {
var ret = new ArrayList<String>();
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;
}
}

View File

@@ -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<Class<? extends MigrationOperation>, Mapper<MigrationOperation>> translators;
static {
var map = new HashMap<Class<? extends MigrationOperation>, Mapper<MigrationOperation>>();
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<T extends MigrationOperation> {
PreparedStatement apply(Connection connection, T operation) throws SQLException;
}
}

View File

@@ -20,4 +20,8 @@ public abstract class Util {
public interface ThrowableFunction<T, R> { public interface ThrowableFunction<T, R> {
R apply(T t) throws Throwable; R apply(T t) throws Throwable;
} }
@FunctionalInterface
public interface ThrowableBiFunction<T, U, R> {
R apply(T t, U u) throws Throwable;
}
} }

View File

@@ -150,6 +150,10 @@ public class OptimizedAsmParserTest {
act = new OptimizedAsmParser((SerializablePredicate<?>) (TestClass e) -> e.l == 0L || e.l == 1L).parse().getExpression().toString(); act = new OptimizedAsmParser((SerializablePredicate<?>) (TestClass e) -> e.l == 0L || e.l == 1L).parse().getExpression().toString();
Assertions.assertEquals("`l` = 0 OR `l` = 1", act); 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 @Test

View File

@@ -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;
}
}

View File

@@ -34,5 +34,4 @@ class ModelBuilderSimpleTest {
public float f; public float f;
public long l; public long l;
} }
} }

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.constraints.ForeignKeyConstraint; 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").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)); 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 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
@@ -27,7 +28,7 @@ public class MigrationCreatorAddFieldTest extends MigrationCreatorTestBase{
ent.getOrCreateField("addedField", int.class.getName()); 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)); 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 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.constraints.ForeignKeyConstraint; import jef.model.constraints.ForeignKeyConstraint;
@@ -24,7 +25,7 @@ public class MigrationCreatorAddForeignKeyTest extends MigrationCreatorTestBase
var ent = to.getEntity(TestClass.class); 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)); 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 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.migration.operation.AddIndexOperation; import jef.model.migration.operation.AddIndexOperation;
@@ -23,7 +24,7 @@ public class MigrationCreatorAddIndexTest extends MigrationCreatorTestBase{
var ent = to.entity(TestClass.class); var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).isIndex(true); ent.field("d", double.class.getName()).isIndex(true);
var mc = new MigrationCreator(); 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.migration.operation.AddKeyOperation; import jef.model.migration.operation.AddKeyOperation;
@@ -23,7 +24,7 @@ public class MigrationCreatorAddKeyTest extends MigrationCreatorTestBase{
var ent = to.entity(TestClass.class); var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).isKey(true); ent.field("d", double.class.getName()).isKey(true);
var mc = new MigrationCreator(); 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.migration.operation.AddUniqueKeyOperation; import jef.model.migration.operation.AddUniqueKeyOperation;
@@ -23,7 +24,7 @@ public class MigrationCreatorAddUniqueTest extends MigrationCreatorTestBase{
var ent = to.entity(TestClass.class); var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).isUnique(true); ent.field("d", double.class.getName()).isUnique(true);
var mc = new MigrationCreator(); 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.annotations.Index; import jef.model.annotations.Index;
@@ -22,7 +23,7 @@ public class MigrationCreatorEmptyTest extends MigrationCreatorTestBase{
var from = ModelBuilder.from(Ctx.class); var from = ModelBuilder.from(Ctx.class);
var to = ModelBuilder.from(Ctx.class); var to = ModelBuilder.from(Ctx.class);
var mc = new MigrationCreator(); 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 { try {
assertEquals(0, res.getStepsUp().size()); assertEquals(0, res.getStepsUp().size());
assertEquals(0, res.getStepsDown().size()); assertEquals(0, res.getStepsDown().size());

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.migration.operation.AddForeignKeyOperation; import jef.model.migration.operation.AddForeignKeyOperation;
@@ -24,7 +25,7 @@ public class MigrationCreatorInitialMigrationTest extends MigrationCreatorTestBa
var from = new ModelBuilder(List.of()); var from = new ModelBuilder(List.of());
var to = ModelBuilder.from(Ctx.class); var to = ModelBuilder.from(Ctx.class);
var mc = new MigrationCreator(); 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.migration.operation.AddForeignKeyOperation; import jef.model.migration.operation.AddForeignKeyOperation;
@@ -25,7 +26,7 @@ public class MigrationCreatorRenameEntityTest extends MigrationCreatorTestBase {
var ent = to.entity(TestClass.class); var ent = to.entity(TestClass.class);
ent.name("d2"); ent.name("d2");
var mc = new MigrationCreator(); 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.ForeignKey; import jef.model.annotations.ForeignKey;
import jef.model.annotations.Id; import jef.model.annotations.Id;
@@ -31,7 +32,7 @@ public class MigrationCreatorRenameFieldConstraintsTest extends MigrationCreator
var to = ModelBuilder.from(Ctx.class); var to = ModelBuilder.from(Ctx.class);
to.getEntity(TestClass.class).getField("i").setName("i2"); to.getEntity(TestClass.class).getField("i").setName("i2");
var mc = new MigrationCreator(); 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -3,6 +3,7 @@ package jef.model.migration.creator;
import jef.DbSet; import jef.DbSet;
import jef.model.DbContext; import jef.model.DbContext;
import jef.model.ModelBuilder; import jef.model.ModelBuilder;
import jef.model.SqlTypeMapper;
import jef.model.annotations.Clazz; import jef.model.annotations.Clazz;
import jef.model.annotations.Id; import jef.model.annotations.Id;
import jef.model.migration.operation.RenameFieldOperation; import jef.model.migration.operation.RenameFieldOperation;
@@ -22,7 +23,7 @@ public class MigrationCreatorRenameFieldTest extends MigrationCreatorTestBase {
var ent = to.entity(TestClass.class); var ent = to.entity(TestClass.class);
ent.field("d", double.class.getName()).name("d2"); ent.field("d", double.class.getName()).name("d2");
var mc = new MigrationCreator(); 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 { try {
validateUp(res); validateUp(res);
validateDown(res); validateDown(res);

View File

@@ -18,8 +18,8 @@ import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
public class MigrationCreatorTestBase { public class MigrationCreatorTestBase {
private static final File JAVA_FILES_DIR = new File("target/jef-tests/sources"); 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/jef-tests/classes"); private static final File CLASS_FILES_DIR = new File("target/test-generated-migrations/target");
public void validateMigration(MigrationCreator.Result result, ModelBuilder from, ModelBuilder to) { public void validateMigration(MigrationCreator.Result result, ModelBuilder from, ModelBuilder to) {
validateMigrationJava(result.getMigration()); validateMigrationJava(result.getMigration());

View File

@@ -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<Company> companies;
@Clazz(Employee.class)
private DbSet<Employee> 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;
}
}