added command line to create migrations
This commit is contained in:
@@ -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
33
pom.xml
@@ -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>-->
|
||||||
|
|||||||
26
src/main/java/jef/Database.java
Normal file
26
src/main/java/jef/Database.java
Normal 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;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
46
src/main/java/jef/DatabaseOptions.java
Normal file
46
src/main/java/jef/DatabaseOptions.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/java/jef/MigrationException.java
Normal file
18
src/main/java/jef/MigrationException.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/jef/main/Main.java
Normal file
27
src/main/java/jef/main/Main.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
294
src/main/java/jef/main/MigrationCommandHandler.java
Normal file
294
src/main/java/jef/main/MigrationCommandHandler.java
Normal 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) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/main/java/jef/model/EntityDefaultConstructorChecker.java
Normal file
23
src/main/java/jef/model/EntityDefaultConstructorChecker.java
Normal 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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
try {
|
||||||
|
var ctor = context.getDeclaredConstructor();
|
||||||
|
init = (d, o) -> ctor.newInstance();
|
||||||
|
} catch (NoSuchMethodException e3) {
|
||||||
throw new RuntimeException(e);
|
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());
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
21
src/main/java/jef/model/SqlTypeMapper.java
Normal file
21
src/main/java/jef/model/SqlTypeMapper.java
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<>();
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,12 @@ 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" : "")
|
||||||
|
+ normalImportsString + (normalImportsString.length() > 0 ? "\n\n" : "")
|
||||||
|
+ javaImportsString + (javaImportsString.length() > 0 ? "\n\n" : "")
|
||||||
+ "public class " + name + " implements Migration {\n"
|
+ "public class " + name + " implements Migration {\n"
|
||||||
+ " public void up(MigrationBuilder mb) {\n"
|
+ " public void up(MigrationBuilder mb) {\n"
|
||||||
+ " " + migrationUp.replace("\n", "\n ") + "\n"
|
+ " " + migrationUp.replace("\n", "\n ") + "\n"
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -54,9 +57,10 @@ public class ModelBuilderGenerator {
|
|||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/main/java/jef/mysql/MysqlDatabase.java
Normal file
19
src/main/java/jef/mysql/MysqlDatabase.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/main/java/jef/mysql/migration/MigrationApplier.java
Normal file
109
src/main/java/jef/mysql/migration/MigrationApplier.java
Normal 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;
|
||||||
|
}
|
||||||
76
src/main/java/jef/mysql/migration/MysqlMigrationApplier.java
Normal file
76
src/main/java/jef/mysql/migration/MysqlMigrationApplier.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,5 +34,4 @@ class ModelBuilderSimpleTest {
|
|||||||
public float f;
|
public float f;
|
||||||
public long l;
|
public long l;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
157
src/test/java/jef/mysql/migration/MysqlMigrationTest.java
Normal file
157
src/test/java/jef/mysql/migration/MysqlMigrationTest.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user