Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
831a734668 |
No files matched your search
@@ -8,15 +8,13 @@
|
|||||||
- references to platform specific impls
|
- references to platform specific impls
|
||||||
|
|
||||||
## Asm
|
## Asm
|
||||||
|
- method references
|
||||||
|
- equals function for primitive and string + String::equalsIgnoreCase
|
||||||
- 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
|
||||||
- expose more stuff as result (e.g. accessed members/functions, ...)
|
|
||||||
- cleanup exception handling
|
|
||||||
- cleanup if else trees
|
|
||||||
|
|
||||||
## ModelBuilder
|
## ModelBuilder
|
||||||
- add registrable conversion types e.g. UUID, Date, Timestamp, Calendar, LocalTime, Instant,...
|
- add registrable conversion types e.g. UUID, Date, Timestamp, Calendar, LocalTime, Instant,...
|
||||||
@@ -24,9 +22,6 @@
|
|||||||
- field length
|
- field length
|
||||||
- field precision
|
- field precision
|
||||||
- sql type
|
- sql type
|
||||||
- ignore() entity/field for db
|
|
||||||
- object inlining
|
|
||||||
- entity() with callback
|
|
||||||
|
|
||||||
## Annotations
|
## Annotations
|
||||||
- db type
|
- db type
|
||||||
|
|||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>jef</artifactId>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<version>0.1</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>cli</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<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>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<artifactId>migration-creator</artifactId>
|
|
||||||
<version>${project.parent.version}</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
package jef.main;
|
|
||||||
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.migration.creator.MigrationCreator;
|
|
||||||
import jef.platform.dummy.DummyPlatform;
|
|
||||||
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.OpenOption;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
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;
|
|
||||||
String platformType = 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;
|
|
||||||
case "--platform":
|
|
||||||
case "-p":
|
|
||||||
if (i + 1 >= args.length) {
|
|
||||||
Main.printHelp();
|
|
||||||
}
|
|
||||||
platformType = 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);
|
|
||||||
var sqlPlatform = new DummyPlatform();//TODO find out from migrations or -p param
|
|
||||||
|
|
||||||
//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(sqlPlatform, from, to, className, migrationPackage, currentSnapshotFile.isFile() ? Files.readString(currentSnapshotFile.toPath()) : null);
|
|
||||||
var result = creator.createMigration();
|
|
||||||
|
|
||||||
var fileOptions = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE};
|
|
||||||
Files.writeString(new File(targetFolder, result.getMigrationClassName() + ".java").toPath(), result.getMigration(), fileOptions);
|
|
||||||
Files.writeString(new File(targetFolder, result.getMigrationSnapshotClassName() + ".java").toPath(), result.getMigrationSnapshot(), fileOptions);
|
|
||||||
Files.writeString(new File(targetFolder, result.getCurrentSnapshotClassName() + ".java").toPath(), result.getCurrentSnapshot(), fileOptions);
|
|
||||||
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 = (Class<? extends DbContext>) cl.loadClass(entryName.substring(0, entryName.length() - ".class".length()).replace("/", "."));
|
|
||||||
var parent = (Class<?>) cls;
|
|
||||||
while (parent != Object.class && parent != DbContext.class) {
|
|
||||||
parent = parent.getSuperclass();
|
|
||||||
}
|
|
||||||
if (parent == DbContext.class) {
|
|
||||||
return Util.tryGet(() -> cls.getDeclaredConstructor().newInstance()).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,36 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<artifactId>jef</artifactId>
|
|
||||||
<version>0.1</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>core</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin><!-- generate tests jar so that test utils can be used in downstream projects -->
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<version>3.2.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<goals>
|
|
||||||
<goal>test-jar</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package jef.asm;
|
|
||||||
|
|
||||||
import jef.expressions.Expression;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
public class AsmParseResult {
|
|
||||||
private final Expression expression;
|
|
||||||
private final Set<Field> accessedFields;
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package jef.asm;
|
|
||||||
|
|
||||||
import jef.serializable.SerializableFunction;
|
|
||||||
import jef.serializable.SerializablePredicate;
|
|
||||||
import jef.util.Check;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.lang.invoke.SerializedLambda;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class AsmParser {
|
|
||||||
private final Serializable lambda;
|
|
||||||
private final Method method;
|
|
||||||
|
|
||||||
public AsmParser(SerializablePredicate<?> predicate) {
|
|
||||||
this.lambda = Check.notNull(predicate, "predicate");
|
|
||||||
this.method = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AsmParser(SerializableFunction<?, ?> function) {
|
|
||||||
this.lambda = Check.notNull(function, "function");
|
|
||||||
this.method = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AsmParser(Method method) {
|
|
||||||
this.lambda = null;
|
|
||||||
this.method = Check.notNull(method, "method");
|
|
||||||
}
|
|
||||||
|
|
||||||
public AsmParseResult parse() throws AsmParseException {
|
|
||||||
try {
|
|
||||||
if (this.lambda != null) {
|
|
||||||
return parseLambdaExpression();
|
|
||||||
} else if (this.method != null) {
|
|
||||||
return parseMethodExpression();
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Illegal state");
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new AsmParseException("PredicateParser: failed to parse expression: " + e.getLocalizedMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AsmParseResult parseLambdaExpression() throws Exception {
|
|
||||||
var cls = (Class) lambda.getClass();
|
|
||||||
var loader = cls.getClassLoader();
|
|
||||||
var writeReplace = cls.getDeclaredMethod("writeReplace");
|
|
||||||
writeReplace.setAccessible(true);
|
|
||||||
var serlambda = (SerializedLambda) writeReplace.invoke(lambda);
|
|
||||||
Object[] args = IntStream.range(0, serlambda.getCapturedArgCount()).mapToObj(serlambda::getCapturedArg).toArray();
|
|
||||||
|
|
||||||
var classname = serlambda.getImplClass();
|
|
||||||
var lambdaname = serlambda.getImplMethodName();
|
|
||||||
cls = Class.forName(classname.replace("/", "."));
|
|
||||||
var is = loader.getResourceAsStream(cls.getName().replace(".", "/") + ".class");
|
|
||||||
|
|
||||||
return parseCommon(is, lambdaname, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AsmParseResult parseMethodExpression() throws Exception {
|
|
||||||
var cls = method.getDeclaringClass();
|
|
||||||
var loader = cls.getClassLoader();
|
|
||||||
InputStream is = loader.getResourceAsStream(cls.getName().replace(".", "/") + ".class");
|
|
||||||
Object[] args = new Object[0];//TODO capturing args here? or maybe not supported since this will only be user by getter evaluation
|
|
||||||
|
|
||||||
return parseCommon(is, method.getName(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AsmParseResult parseCommon(InputStream classIs, String methodname, Object[] args) throws Exception {
|
|
||||||
var cr = new ClassReader(classIs);
|
|
||||||
var visiter = new FilterClassVisitor(Opcodes.ASM9, methodname, args);
|
|
||||||
cr.accept(visiter, 0);
|
|
||||||
|
|
||||||
return visiter.getResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package jef.asm;
|
|
||||||
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
|
||||||
import org.objectweb.asm.MethodVisitor;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
class FilterClassVisitor extends ClassVisitor {
|
|
||||||
private final int api;
|
|
||||||
private final String lambdaname;
|
|
||||||
private final Object[] args;
|
|
||||||
|
|
||||||
private String className;
|
|
||||||
private FilterMethodVisitor mv;
|
|
||||||
|
|
||||||
protected FilterClassVisitor(int api, String lambdaname, Object[] args) {
|
|
||||||
super(api);
|
|
||||||
this.api = api;
|
|
||||||
this.lambdaname = lambdaname;
|
|
||||||
this.args = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
|
||||||
super.visit(version, access, name, signature, superName, interfaces);
|
|
||||||
this.className = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
|
||||||
if (!name.equals(lambdaname)) {
|
|
||||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mv != null) {
|
|
||||||
throw new IllegalStateException("multiple lambda with same name found: " + lambdaname);
|
|
||||||
}
|
|
||||||
return mv = new FilterMethodVisitor(api, className, descriptor, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitEnd() {
|
|
||||||
super.visitEnd();
|
|
||||||
|
|
||||||
//finish
|
|
||||||
if (mv == null) {
|
|
||||||
throw new IllegalStateException("lambda not found: " + lambdaname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AsmParseResult getResult() {
|
|
||||||
return Optional.ofNullable(mv).map(e -> e.getResult()).orElse(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package jef.asm.access;
|
|
||||||
|
|
||||||
import jef.asm.access.model.ClassDescription;
|
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class ClassAnalyzer {
|
|
||||||
public ClassDescription analyze(Class clazz) {
|
|
||||||
return analyzeClass(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClassDescription analyzeClass(Class clazz) {
|
|
||||||
try (var is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace(".", "/") + ".class")) {
|
|
||||||
var result = new ClassDescription[1];
|
|
||||||
var byteCode = is.readAllBytes();
|
|
||||||
var reader = new ClassReader(byteCode);
|
|
||||||
var visitor = new ClassAnalyzerVisitor(Opcodes.ASM9, description -> result[0] = description);
|
|
||||||
reader.accept(visitor, 0);
|
|
||||||
return result[0];
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package jef.asm.access;
|
|
||||||
|
|
||||||
import jef.asm.access.model.ClassDescription;
|
|
||||||
import jef.asm.access.model.ConstructorDescription;
|
|
||||||
import jef.asm.access.model.FieldDescription;
|
|
||||||
import jef.asm.access.model.GetterDescription;
|
|
||||||
import jef.asm.access.model.SetterDescription;
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
|
||||||
import org.objectweb.asm.FieldVisitor;
|
|
||||||
import org.objectweb.asm.MethodVisitor;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
class ClassAnalyzerVisitor extends ClassVisitor {
|
|
||||||
private final Consumer<ClassDescription> callback;
|
|
||||||
|
|
||||||
private String className;
|
|
||||||
private String superClassName;
|
|
||||||
private final List<FieldDescription> declaredFields = new ArrayList<>();
|
|
||||||
private final List<ConstructorDescription> declaredConstructor = new ArrayList<>();
|
|
||||||
private final List<GetterDescription> declaredGetters = new ArrayList<>();
|
|
||||||
private final List<SetterDescription> declaredSetters = new ArrayList<>();
|
|
||||||
|
|
||||||
protected ClassAnalyzerVisitor(int api, Consumer<ClassDescription> callback) {
|
|
||||||
super(api);
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ClassAnalyzerVisitor(int api, ClassVisitor classVisitor, Consumer<ClassDescription> callback) {
|
|
||||||
super(api, classVisitor);
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
|
||||||
className = name.replace("/", ".");
|
|
||||||
superClassName = superName.replace("/", ".");
|
|
||||||
super.visit(version, access, name, signature, superName, interfaces);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
|
||||||
declaredFields.add(new FieldDescription(className, name));
|
|
||||||
return super.visitField(access, name, descriptor, signature, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
|
||||||
var type = Type.getMethodType(descriptor);
|
|
||||||
var returnClassName = type.getReturnType().getClassName();
|
|
||||||
var parameterClassNames = Arrays.stream(type.getArgumentTypes()).map(Type::getClassName).toArray(String[]::new);
|
|
||||||
return new MethodAnalyzerVisitor(Opcodes.ASM9, className, name, access, returnClassName, parameterClassNames, entityAccess -> {
|
|
||||||
declaredConstructor.addAll(entityAccess.getDeclaredConstructors());
|
|
||||||
declaredGetters.addAll(entityAccess.getDeclaredGetters());
|
|
||||||
declaredSetters.addAll(entityAccess.getDeclaredSetters());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitEnd() {
|
|
||||||
super.visitEnd();
|
|
||||||
callback.accept(new ClassDescription(className, superClassName, declaredFields, declaredConstructor, declaredGetters, declaredSetters));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,330 +0,0 @@
|
|||||||
package jef.asm.access;
|
|
||||||
|
|
||||||
import jef.asm.access.model.ClassDescription;
|
|
||||||
import jef.asm.access.model.ConstructorDescription;
|
|
||||||
import jef.asm.access.model.ConstructorParameterDescription;
|
|
||||||
import jef.asm.access.model.GetterDescription;
|
|
||||||
import jef.asm.access.model.SetterDescription;
|
|
||||||
import jef.asm.access.model.SuperConstructorParameterDescription;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.objectweb.asm.Handle;
|
|
||||||
import org.objectweb.asm.Label;
|
|
||||||
import org.objectweb.asm.MethodVisitor;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Stack;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class MethodAnalyzerVisitor extends MethodVisitor {//TODO verify param count
|
|
||||||
private final Consumer<ClassDescription> callback;
|
|
||||||
private final String declaringClassName;
|
|
||||||
private final String methodName;
|
|
||||||
private final int access;
|
|
||||||
private final String returnClassName;
|
|
||||||
private final String[] parameterClassNames;
|
|
||||||
|
|
||||||
private List<ConstructorParameterDescription> constructorParameterDescriptions = new ArrayList<>();
|
|
||||||
private List<SuperConstructorParameterDescription> superConstructorParameterDescriptions = new ArrayList<>();
|
|
||||||
private Stack<Var> varStack = new Stack<>();
|
|
||||||
private String fieldDeclaringClassName = null;
|
|
||||||
private String fieldName = null;
|
|
||||||
|
|
||||||
private ConstructorSteps constructorStep = ConstructorSteps.INIT;
|
|
||||||
private GetterSteps getterStep = GetterSteps.INIT;
|
|
||||||
private SetterSteps setterStep = SetterSteps.INIT;
|
|
||||||
|
|
||||||
enum ConstructorSteps {
|
|
||||||
INIT,
|
|
||||||
CODE,
|
|
||||||
PRE_SUPER_INIT_ALOAD_0,
|
|
||||||
PRE_SUPER_INIT_VAR_LOAD,
|
|
||||||
SUPER_INIT,
|
|
||||||
ALOAD_0,
|
|
||||||
VAR_LOAD,
|
|
||||||
PUT_FIELD,
|
|
||||||
RETURN,
|
|
||||||
INVALID
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GetterSteps {
|
|
||||||
INIT,
|
|
||||||
CODE,
|
|
||||||
ALOAD_0,
|
|
||||||
GET_FIELD,
|
|
||||||
RETURN,
|
|
||||||
INVALID
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SetterSteps {
|
|
||||||
INIT,
|
|
||||||
CODE,
|
|
||||||
ALOAD_0,
|
|
||||||
VAR_LOAD,
|
|
||||||
PUT_FIELD,
|
|
||||||
RETURN,
|
|
||||||
INVALID
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MethodAnalyzerVisitor(int api, String declaringClassName, String methodName, int access, String returnClassName, String[] parameterClassNames, Consumer<ClassDescription> callback) {
|
|
||||||
super(api);
|
|
||||||
this.declaringClassName = declaringClassName;
|
|
||||||
this.methodName = methodName;
|
|
||||||
this.access = access;
|
|
||||||
this.returnClassName = returnClassName;
|
|
||||||
if (hasThis()) {
|
|
||||||
this.parameterClassNames = new String[parameterClassNames.length + 1];
|
|
||||||
System.arraycopy(parameterClassNames, 0, this.parameterClassNames, 1, parameterClassNames.length);
|
|
||||||
this.parameterClassNames[0] = declaringClassName;
|
|
||||||
} else {
|
|
||||||
this.parameterClassNames = parameterClassNames;
|
|
||||||
}
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitCode() {
|
|
||||||
constructorStep = constructorStep == ConstructorSteps.INIT ? ConstructorSteps.CODE : ConstructorSteps.INVALID;
|
|
||||||
getterStep = getterStep == GetterSteps.INIT ? GetterSteps.CODE : GetterSteps.INVALID;
|
|
||||||
setterStep = setterStep == SetterSteps.INIT ? SetterSteps.CODE : SetterSteps.INVALID;
|
|
||||||
super.visitCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitFrame(type, numLocal, local, numStack, stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitInsn(int opcode) {
|
|
||||||
if (Set.of(Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN).contains(opcode)) {
|
|
||||||
getterStep = getterStep == GetterSteps.GET_FIELD ? GetterSteps.RETURN : GetterSteps.INVALID;
|
|
||||||
} else {
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
}
|
|
||||||
if (opcode == Opcodes.RETURN) {
|
|
||||||
constructorStep = constructorStep == ConstructorSteps.PUT_FIELD ? ConstructorSteps.RETURN : ConstructorSteps.INVALID;
|
|
||||||
setterStep = setterStep == SetterSteps.PUT_FIELD ? SetterSteps.RETURN : SetterSteps.INVALID;
|
|
||||||
} else {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
}
|
|
||||||
super.visitInsn(opcode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitIntInsn(int opcode, int operand) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitIntInsn(opcode, operand);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitVarInsn(int opcode, int varIndex) {
|
|
||||||
if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
|
||||||
System.out.println(methodName + ": load_" + varIndex + ": " + (varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null));
|
|
||||||
if (varIndex == 5) {
|
|
||||||
int x = 0;
|
|
||||||
}
|
|
||||||
varStack.push(new Var(varIndex < parameterClassNames.length ? parameterClassNames[varIndex] : null, varIndex));
|
|
||||||
}
|
|
||||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
|
||||||
if (constructorStep == ConstructorSteps.CODE) {
|
|
||||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_ALOAD_0;
|
|
||||||
} else if (constructorStep == ConstructorSteps.SUPER_INIT || constructorStep == ConstructorSteps.PUT_FIELD) {
|
|
||||||
constructorStep = ConstructorSteps.ALOAD_0;
|
|
||||||
} else {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
}
|
|
||||||
} else if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
|
||||||
if (constructorStep == ConstructorSteps.PRE_SUPER_INIT_ALOAD_0) {
|
|
||||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD;
|
|
||||||
} else if (constructorStep == ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD) {
|
|
||||||
constructorStep = ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD;
|
|
||||||
} else if (constructorStep == ConstructorSteps.ALOAD_0) {
|
|
||||||
constructorStep = ConstructorSteps.VAR_LOAD;
|
|
||||||
} else {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
}
|
|
||||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
|
||||||
getterStep = getterStep == GetterSteps.CODE ? GetterSteps.ALOAD_0 : GetterSteps.INVALID;
|
|
||||||
} else {
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
}
|
|
||||||
if (opcode == Opcodes.ALOAD && varIndex == 0) {
|
|
||||||
setterStep = setterStep == SetterSteps.CODE ? SetterSteps.ALOAD_0 : SetterSteps.INVALID;
|
|
||||||
} else if (Set.of(Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.ALOAD).contains(opcode)) {
|
|
||||||
setterStep = setterStep == SetterSteps.ALOAD_0 ? SetterSteps.VAR_LOAD : SetterSteps.INVALID;
|
|
||||||
} else {
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
}
|
|
||||||
super.visitVarInsn(opcode, varIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasThis() {
|
|
||||||
return (access & Opcodes.ACC_STATIC) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitTypeInsn(int opcode, String type) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitTypeInsn(opcode, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
|
|
||||||
Var lastLoadedVar = null;
|
|
||||||
if (opcode == Opcodes.PUTFIELD) {
|
|
||||||
lastLoadedVar = varStack.pop();
|
|
||||||
varStack.pop();
|
|
||||||
constructorStep = constructorStep == ConstructorSteps.VAR_LOAD ? ConstructorSteps.PUT_FIELD : ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = setterStep == SetterSteps.VAR_LOAD ? SetterSteps.PUT_FIELD : SetterSteps.INVALID;
|
|
||||||
} else if (opcode == Opcodes.GETFIELD) {
|
|
||||||
lastLoadedVar = varStack.pop();
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = getterStep == GetterSteps.ALOAD_0 ? GetterSteps.GET_FIELD : GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
} else {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
}
|
|
||||||
this.fieldDeclaringClassName = owner.replace("/", ".");
|
|
||||||
this.fieldName = name;
|
|
||||||
this.constructorParameterDescriptions.add(new ConstructorParameterDescription(lastLoadedVar.getIndex() - 1, lastLoadedVar.getClassName(), this.fieldDeclaringClassName, this.fieldName)); //lastLoadedVar - 1 because arg 0 of an instance function is always this
|
|
||||||
super.visitFieldInsn(opcode, owner, name, descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
|
|
||||||
var args = new ArrayList<Var>();
|
|
||||||
for (var i = 0; i < Type.getArgumentTypes(descriptor).length; i++) {
|
|
||||||
args.add(0, varStack.pop());
|
|
||||||
}
|
|
||||||
varStack.pop(); // also pop ALOAD0
|
|
||||||
if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>") //TODO does it have to be called '<init>' or is this just convention
|
|
||||||
&& Set.of(ConstructorSteps.PRE_SUPER_INIT_ALOAD_0, ConstructorSteps.PRE_SUPER_INIT_VAR_LOAD).contains(constructorStep)) {
|
|
||||||
for (int i = 0; i < args.size(); i++) {
|
|
||||||
superConstructorParameterDescriptions.add(new SuperConstructorParameterDescription(args.get(i).getIndex(), i));
|
|
||||||
}
|
|
||||||
constructorStep = ConstructorSteps.SUPER_INIT;
|
|
||||||
} else {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
}
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitJumpInsn(int opcode, Label label) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitJumpInsn(opcode, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitLabel(Label label) {
|
|
||||||
super.visitLabel(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitLdcInsn(Object value) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitLdcInsn(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitIincInsn(int varIndex, int increment) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitIincInsn(varIndex, increment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitTableSwitchInsn(min, max, dflt, labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitLookupSwitchInsn(dflt, keys, labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitMultiANewArrayInsn(String descriptor, int numDimensions) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitMultiANewArrayInsn(descriptor, numDimensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
|
||||||
constructorStep = ConstructorSteps.INVALID;
|
|
||||||
getterStep = GetterSteps.INVALID;
|
|
||||||
setterStep = SetterSteps.INVALID;
|
|
||||||
super.visitTryCatchBlock(start, end, handler, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
|
||||||
super.visitLocalVariable(name, descriptor, signature, start, end, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitMaxs(int maxStack, int maxLocals) {
|
|
||||||
super.visitMaxs(maxStack, maxLocals);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visitEnd() {
|
|
||||||
super.visitEnd();
|
|
||||||
List<ConstructorDescription> constructors = constructorStep == ConstructorSteps.RETURN ? List.of(new ConstructorDescription(constructorParameterDescriptions, superConstructorParameterDescriptions)) : List.of();
|
|
||||||
List<GetterDescription> getters = getterStep == GetterSteps.RETURN ? List.of(new GetterDescription(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
|
|
||||||
List<SetterDescription> setters = setterStep == SetterSteps.RETURN ? List.of(new SetterDescription(declaringClassName, methodName, returnClassName, Arrays.stream(parameterClassNames).skip(hasThis() ? 1 : 0).toArray(String[]::new), fieldDeclaringClassName, fieldName)) : List.of();
|
|
||||||
var access = new ClassDescription(null, null, null, constructors, getters, setters);
|
|
||||||
callback.accept(access);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
private static class Var {
|
|
||||||
private final String className;
|
|
||||||
private final int index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package jef.asm.access.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
public class ClassDescription {
|
|
||||||
private final String className;
|
|
||||||
private final String superClassName;
|
|
||||||
private final List<FieldDescription> declaredFields;
|
|
||||||
private final List<ConstructorDescription> declaredConstructors;
|
|
||||||
private final List<GetterDescription> declaredGetters;
|
|
||||||
private final List<SetterDescription> declaredSetters;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ClassDescription{" +
|
|
||||||
"className='" + className + '\'' +
|
|
||||||
", superClassName='" + superClassName + '\'' +
|
|
||||||
", declaredFields=" + declaredFields +
|
|
||||||
", declaredConstructors=" + declaredConstructors +
|
|
||||||
", declaredGetters=" + declaredGetters +
|
|
||||||
", declaredSetters=" + declaredSetters +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toStringFriendly() {
|
|
||||||
return "ClassAccessDescription{\n"
|
|
||||||
+ " className: " + className + '\n'
|
|
||||||
+ " superClassName: " + superClassName + '\n'
|
|
||||||
+ " declaredFields: " + declaredFields + '\n'
|
|
||||||
+ " declaredConstructors:\n" + declaredConstructors.stream().map(e -> " " + e.toString() + '\n').collect(Collectors.joining())
|
|
||||||
+ " declaredGetters:\n" + declaredGetters.stream().map(e -> " " + e.toString() + '\n').collect(Collectors.joining())
|
|
||||||
+ " declaredSetters:\n" + declaredSetters.stream().map(e -> " " + e.toString()+ '\n').collect(Collectors.joining())
|
|
||||||
+ '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package jef.asm.access.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public class ConstructorDescription {
|
|
||||||
private final List<ConstructorParameterDescription> constructorParameterDescriptions;
|
|
||||||
private final List<SuperConstructorParameterDescription> superConstructorParameterDescriptions;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package jef.asm.access.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public class ConstructorParameterDescription {
|
|
||||||
private final int parameterIndex;
|
|
||||||
private final String parameterClassName;
|
|
||||||
private final String declaringClassName;
|
|
||||||
private final String fieldName;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package jef.asm.access.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public class FieldDescription {
|
|
||||||
private final String fieldDeclaringClassName;
|
|
||||||
private final String fieldName;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package jef.asm.access.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public class GetterDescription {
|
|
||||||
private final String methodDeclaringClassName;
|
|
||||||
private final String methodName;
|
|
||||||
private final String methodReturnClassName;
|
|
||||||
private final String[] methodParameterClassNames;
|
|
||||||
private final String fieldDeclaringClassName;
|
|
||||||
private final String fieldName;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package jef.asm.access.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public class SetterDescription {
|
|
||||||
private final String methodDeclaringClassName;
|
|
||||||
private final String methodName;
|
|
||||||
private final String methodReturnClassName;
|
|
||||||
private final String[] methodParameterClassNames;
|
|
||||||
private final String fieldDeclaringClassName;
|
|
||||||
private final String fieldName;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package jef.asm.access.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public class SuperConstructorParameterDescription {
|
|
||||||
private final int constructorParameterIndex;
|
|
||||||
private final int superConstructorParameterIndex;
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package jef.expressions;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
@EqualsAndHashCode
|
|
||||||
public class ConstantExpression implements Expression {
|
|
||||||
public static final ConstantExpression V0 = new ConstantExpression(0);
|
|
||||||
public static final ConstantExpression V1 = new ConstantExpression(1);
|
|
||||||
public static final ConstantExpression V2 = new ConstantExpression(2);
|
|
||||||
public static final ConstantExpression V3 = new ConstantExpression(3);
|
|
||||||
public static final ConstantExpression V4 = new ConstantExpression(4);
|
|
||||||
public static final ConstantExpression V5 = new ConstantExpression(5);
|
|
||||||
|
|
||||||
protected final Object value;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type getType() {
|
|
||||||
return Type.CONSTANT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Priority getPriority() {
|
|
||||||
return Priority.CONSTANT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if (value instanceof String) {
|
|
||||||
return "\"" + value + "\"";
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package jef.expressions;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
@EqualsAndHashCode
|
|
||||||
public class FunctionExpression implements Expression {
|
|
||||||
public static final String TOLOWER = "TOLOWER";
|
|
||||||
private final String function;
|
|
||||||
private final List<Expression> parameters;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type getType() {
|
|
||||||
return Type.FUNCTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Priority getPriority() {
|
|
||||||
return Priority.UNARY_PRE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return function + "(" + parameters.stream().map(Expression::toString).collect(Collectors.joining(", ")) + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class DbContextOptions {
|
|
||||||
private final DatabaseOptions databaseOptions;
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.asm.AsmParser;
|
|
||||||
import jef.serializable.SerializableFunction;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.util.Check;
|
|
||||||
import jef.util.Util;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class DbEntityBuilder<T extends SerializableObject> {
|
|
||||||
private final ModelBuilder modelBuilder;
|
|
||||||
private final DbEntity<T> entity;
|
|
||||||
|
|
||||||
public DbEntityBuilder<T> name(String name) {
|
|
||||||
entity.setName(name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <R> DbFieldBuilder<R> field(SerializableFunction<T, R> getter) {
|
|
||||||
try {
|
|
||||||
var prop = entity.getField(getter);
|
|
||||||
if (prop == null) {
|
|
||||||
var name = entity.extractFieldName(getter);
|
|
||||||
var field = ReflectionUtil.getFieldsRecursive(type().get()).stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
|
|
||||||
if (field == null) {
|
|
||||||
throw new RuntimeException("Field not found: " + name);
|
|
||||||
}
|
|
||||||
prop = new DbField<>(entity, (Class<R>) field.getType(), field);
|
|
||||||
entity.addField(prop);
|
|
||||||
}
|
|
||||||
return new DbFieldBuilder<>(prop);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Invalid expression", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <R> DbFieldBuilder<R> field(Field field) {
|
|
||||||
var prop = (DbField<R>) entity.getFields().stream().filter(e -> e.getField().equals(field)).findFirst().orElse(null);
|
|
||||||
if (prop == null) {
|
|
||||||
prop = new DbField<>(entity, (Class<R>) field.getType(), field);
|
|
||||||
entity.addField(prop);
|
|
||||||
}
|
|
||||||
return new DbFieldBuilder<>(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <R> DbFieldBuilder<R> field(String name, String typeName) {
|
|
||||||
var prop = (DbField<R>) entity.getFields().stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
|
|
||||||
if (prop == null) {
|
|
||||||
prop = new DbField<>(entity, name, typeName);
|
|
||||||
entity.addField(prop);
|
|
||||||
}
|
|
||||||
return new DbFieldBuilder<>(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
//keys
|
|
||||||
public KeyBuilder<T> hasOne(SerializableFunction<T, ?> getter) {
|
|
||||||
Check.notNull(getter, "getter");
|
|
||||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
|
||||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
|
||||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyBuilder<T> hasOne(String... getterOrFieldNames) {
|
|
||||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
|
||||||
var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
|
||||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
|
||||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyBuilder<T> hasMany(SerializableFunction<T, ?> getter) {
|
|
||||||
Check.notNull(getter, "getter");
|
|
||||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
|
||||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
|
||||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyBuilder<T> hasMany(String... getterOrFieldNames) {
|
|
||||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
|
||||||
var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
|
||||||
var fields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
|
||||||
return new KeyBuilder<>(modelBuilder, entity, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
//getters
|
|
||||||
public String name() {
|
|
||||||
return entity.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<DbFieldBuilder<?>> fields() {
|
|
||||||
return (List) entity.getFields().stream().map(e -> new DbFieldBuilder(e)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<Class<T>> type() {
|
|
||||||
return Optional.ofNullable(entity.getType()).or(() -> Util.tryGet(() -> (Class<T>) Class.forName(typeName())));
|
|
||||||
}
|
|
||||||
|
|
||||||
public String typeName() {
|
|
||||||
return entity.getTypeName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String className() {
|
|
||||||
var s = entity.getTypeName().replace("$", ".").split("\\.");
|
|
||||||
return s[s.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbEntity<T> getEntity() { //TODO make this libaray private somehow
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return entity.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.model.annotations.Generated;
|
|
||||||
import jef.model.constraints.IndexConstraint;
|
|
||||||
import jef.model.constraints.KeyConstraint;
|
|
||||||
import jef.model.constraints.UniqueKeyConstraint;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DbFieldBuilder<T> {
|
|
||||||
private final DbField<T> field;
|
|
||||||
|
|
||||||
public DbFieldBuilder<T> name(String name) {
|
|
||||||
field.setName(name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbFieldBuilder<T> isDatabaseField(boolean isDatabase) {
|
|
||||||
field.setDatabaseField(isDatabase);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbFieldBuilder<T> isModelField(boolean isModel) {
|
|
||||||
field.setModelField(isModel);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbFieldBuilder<T> sqlType(String sqlType) {
|
|
||||||
field.setSqlType(sqlType);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbFieldBuilder<T> isNotNull() {
|
|
||||||
return isNotNull(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbFieldBuilder<T> isNotNull(boolean notnull) {
|
|
||||||
field.setNotNull(notnull);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbFieldBuilder<T> generated(Generated.Type type) {
|
|
||||||
field.setGenerated(type);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isUnique() {
|
|
||||||
isUnique(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isUnique(boolean unique) {
|
|
||||||
var entity = field.getEntity();
|
|
||||||
var constr = entity.getUniqueKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == field).findFirst();
|
|
||||||
if (!constr.isPresent() && unique) {
|
|
||||||
entity.addUniqueKey(new UniqueKeyConstraint(entity, new ArrayList<>(List.of(field))));
|
|
||||||
} else if (constr.isPresent() && !unique) {
|
|
||||||
entity.dropUniqueKey(constr.get());
|
|
||||||
} //else do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isKey() {
|
|
||||||
isKey(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isKey(boolean key) {
|
|
||||||
var entity = field.getEntity();
|
|
||||||
var constr = entity.getKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == field).findFirst();
|
|
||||||
if (!constr.isPresent() && key) {
|
|
||||||
entity.addKey(new KeyConstraint(entity, new ArrayList<>(List.of(field))));
|
|
||||||
} else if (constr.isPresent() && !key) {
|
|
||||||
entity.dropKey(constr.get());
|
|
||||||
} //else do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isIndex() {
|
|
||||||
isIndex(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isIndex(boolean index) {
|
|
||||||
var entity = field.getEntity();
|
|
||||||
var constr = entity.getIndexes().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == field).findFirst();
|
|
||||||
if (!constr.isPresent() && index) {
|
|
||||||
entity.addIndex(new IndexConstraint(entity, new ArrayList<>(List.of(field))));
|
|
||||||
} else if (constr.isPresent() && !index) {
|
|
||||||
entity.dropIndex(constr.get());
|
|
||||||
} //else do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbField<T> getField() {//TODO make this library private
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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,83 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.Transient;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.util.Log;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
class EntityInitializer {
|
|
||||||
static void initEntities(ModelBuilder mb, Class<? extends DbContext> context) {
|
|
||||||
for (Field ctxField : context.getDeclaredFields()) {
|
|
||||||
if (!DbSet.class.isAssignableFrom(ctxField.getType())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Clazz clazzAnnotation = ctxField.getAnnotation(Clazz.class);
|
|
||||||
if (clazzAnnotation == null) {
|
|
||||||
throw new ModelException("DbSet " + ctxField.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
|
|
||||||
}
|
|
||||||
if (!SerializableObject.class.isAssignableFrom(clazzAnnotation.value())) {
|
|
||||||
throw new ModelException("DbSet " + ctxField.getName() + " has a class in its " + Clazz.class.getSimpleName() + " annotation that does not inherit from " + SerializableObject.class.getSimpleName() + ": " + clazzAnnotation.value().getSimpleName());
|
|
||||||
}
|
|
||||||
var dbSetClazz = (Class<? extends SerializableObject>) clazzAnnotation.value();
|
|
||||||
initEntity(mb, dbSetClazz, ctxField.getName(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void initEntity(ModelBuilder mb, Class<? extends SerializableObject> clazz, String name, boolean overrideName) {
|
|
||||||
var existingEntity = mb.getEntity(clazz);
|
|
||||||
if (existingEntity != null) {
|
|
||||||
//allow DbSets to override the name
|
|
||||||
if (overrideName) {
|
|
||||||
existingEntity.setName(name);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.debug("Initializing entity '" + name + "' of type " + clazz.getName());
|
|
||||||
var entity = mb.entity(clazz);
|
|
||||||
entity.name(name);
|
|
||||||
|
|
||||||
var fields = ReflectionUtil.getFieldsRecursive(clazz);
|
|
||||||
for (var f : fields) {
|
|
||||||
if (f.getAnnotationsByType(Transient.class).length > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Log.debug("Initializing field '" + f.getName() + "' with type " + f.getType().getName());
|
|
||||||
if (Collection.class.isAssignableFrom(f.getType())) {
|
|
||||||
//find a Collection field with the same Model
|
|
||||||
//e.g. class Entity { @Clazz(Entity2.class) List<Entity2> ent; @Clazz(Entity2.class) Set<Entity2> ent2; }
|
|
||||||
var clazzAnnotation = f.getAnnotation(Clazz.class);
|
|
||||||
if (clazzAnnotation == null) {
|
|
||||||
throw new ModelException("Field " + clazz.getSimpleName() + "::" + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
|
|
||||||
}
|
|
||||||
var fClazz = clazzAnnotation.value();
|
|
||||||
if (!SerializableObject.class.isAssignableFrom(fClazz)) {
|
|
||||||
throw new ModelException("Field " + clazz.getSimpleName() + "::" + f.getName() + " has a class in its " + Clazz.class.getSimpleName() + " annotation that does not inherit from " + SerializableObject.class.getSimpleName() + ": " + fClazz.getSimpleName());
|
|
||||||
}
|
|
||||||
var foundCollection = entity.fields().stream()
|
|
||||||
.filter(e -> Collection.class.isAssignableFrom(e.getField().getType())
|
|
||||||
&& e.getField().isModelField()
|
|
||||||
&& e.getField().getField().getAnnotationsByType(Clazz.class).length > 0
|
|
||||||
&& e.getField().getField().getAnnotationsByType(Clazz.class)[0].value() == fClazz)
|
|
||||||
.findFirst();
|
|
||||||
if (foundCollection.isPresent()) {
|
|
||||||
throw new ModelException("Model " + entity.className() + " contains multiple 1 to N relations with type " + fClazz.getSimpleName());
|
|
||||||
}
|
|
||||||
entity.field(f);
|
|
||||||
initEntity(mb, (Class<? extends SerializableObject>) fClazz, fClazz.getSimpleName(), false);
|
|
||||||
} else if (SerializableObject.class.isAssignableFrom(f.getType())) {
|
|
||||||
entity.field(f);
|
|
||||||
initEntity(mb, (Class<? extends SerializableObject>) f.getType(), f.getType().getSimpleName(), false);
|
|
||||||
} else {
|
|
||||||
var dbField = entity.field(f);
|
|
||||||
if (f.getType().isPrimitive()) {
|
|
||||||
dbField.isNotNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.model.constraints.ForeignKeyConstraint;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ForeignKeyBuilder<T extends SerializableObject, R extends SerializableObject> {
|
|
||||||
private final ForeignKeyConstraint foreignKey;
|
|
||||||
|
|
||||||
public ForeignKeyBuilder<T, R> onUpdate(ForeignKeyConstraint.Action onUpdate) {
|
|
||||||
foreignKey.setOnUpdate(onUpdate);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForeignKeyBuilder<T, R> onDelete(ForeignKeyConstraint.Action onDelete) {
|
|
||||||
foreignKey.setOnDelete(onDelete);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.asm.OptimizedAsmParser;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.util.Util;
|
|
||||||
|
|
||||||
class ForeignKeyExposeInitializer {
|
|
||||||
static void initForeignKeyExposures(ModelBuilder mb) {
|
|
||||||
var entities = mb.entities();
|
|
||||||
for (int i = 0; i < entities.size(); i++) {
|
|
||||||
var entity = entities.get(i);
|
|
||||||
initForeignKeyExposures(mb, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void initForeignKeyExposures(ModelBuilder mb, DbEntityBuilder<?> entityBuilder) {
|
|
||||||
var fields = entityBuilder.fields();
|
|
||||||
for (var f : fields) {
|
|
||||||
if (SerializableObject.class.isAssignableFrom(f.getField().getType())) {
|
|
||||||
var idFields = fields.stream()
|
|
||||||
.filter(e -> {
|
|
||||||
//ignore self
|
|
||||||
if (e.getField().getName().equals(f.getField().getName())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
//match with name in annotation
|
|
||||||
var anno = e.getField().getField().getAnnotation(ForeignKey.class);
|
|
||||||
if (anno == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (anno.entity() != SerializableObject.class) { // not a foreign key exposure, ignore
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var fieldname = anno.getterOrField();
|
|
||||||
|
|
||||||
//if getter, extract getter field
|
|
||||||
var method = Util.tryGet(() -> entityBuilder.type().orElseThrow().getMethod(anno.getterOrField()));
|
|
||||||
if (method.isPresent()) {
|
|
||||||
var res = new OptimizedAsmParser(method.get()).parse();
|
|
||||||
fieldname = res.getAccessedFields().stream().findFirst().orElseThrow().getName();
|
|
||||||
}
|
|
||||||
if (!SerializableObject.class.isAssignableFrom(entityBuilder.getEntity().getField(fieldname).getType())) {
|
|
||||||
throw new ModelException("The getter/field in " + entityBuilder.className() + "::" + f.getField().getField().getName() + " is not a " + SerializableObject.class.getSimpleName() + " (via @ForeignKey in TestClass::testFk)");
|
|
||||||
}
|
|
||||||
return f.getField().getName().equals(fieldname);
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
idFields.forEach(e -> {
|
|
||||||
e.getField().setExposingForeignKeyOf(f.getField());
|
|
||||||
e.getField().setDatabaseField(idFields.stream().findFirst().orElseThrow() == e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.model.annotations.Transient;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.util.Log;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
class ForeignKeyInitializer {
|
|
||||||
static void initForeignKeys(ModelBuilder mb) {
|
|
||||||
var entities = mb.entities();
|
|
||||||
for (int i = 0; i < entities.size(); i++) {
|
|
||||||
var entity = entities.get(i);
|
|
||||||
initForeignKeys(mb, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void initForeignKeys(ModelBuilder mb, DbEntityBuilder<?> entityBuilder) {
|
|
||||||
var entity = entityBuilder.getEntity();
|
|
||||||
var fields = ReflectionUtil.getFieldsRecursive(entityBuilder.type().orElseThrow(() -> new IllegalStateException("Class not found: " + entityBuilder.typeName())));
|
|
||||||
for (var f : fields) {
|
|
||||||
if (f.getAnnotationsByType(Transient.class).length > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (Collection.class.isAssignableFrom(f.getType())) {
|
|
||||||
do1toN(mb, entityBuilder, entity, f);
|
|
||||||
} else if (SerializableObject.class.isAssignableFrom(f.getType())) {//1 to 1 relation
|
|
||||||
do1to1(mb, entityBuilder, entity, f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void do1toN(ModelBuilder mb, DbEntityBuilder<?> entityBuilder, DbEntity<?> entity, Field f) {
|
|
||||||
Clazz clazzAnnotation = f.getAnnotation(Clazz.class);
|
|
||||||
if (clazzAnnotation == null) {
|
|
||||||
throw new ModelException("Collection " + entityBuilder.className() + "." + f.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
|
|
||||||
}
|
|
||||||
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.value());
|
|
||||||
if (otherEntity == null) {
|
|
||||||
//all types must be initialized by EntityInitializer
|
|
||||||
throw new IllegalStateException(ForeignKeyInitializer.class.getSimpleName() + ": Entity of type " + clazzAnnotation.value() + " does not exist");
|
|
||||||
// EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) clazzAnnotation.value(), f.getName());
|
|
||||||
// otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.value());
|
|
||||||
// PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity.builder(mb));
|
|
||||||
}
|
|
||||||
var primary = entityBuilder.getEntity().getPrimaryKey();
|
|
||||||
if (primary == null) {
|
|
||||||
throw new ModelException("Entity " + entityBuilder.className() + " is missing a primary key and therefore cannot be referenced by " + otherEntity.builder(mb).className());
|
|
||||||
}
|
|
||||||
var otherEntityF = otherEntity;
|
|
||||||
var otherFields = primary.getFields().stream()
|
|
||||||
.map(e -> {
|
|
||||||
//find list<object> in other entity (N to N relation)
|
|
||||||
if (otherEntityF != entity) { //ignore on recursive models
|
|
||||||
var otherEntityListField = otherEntityF.getFields().stream()
|
|
||||||
.filter(oef -> Collection.class.isAssignableFrom(oef.getType())
|
|
||||||
&& oef.getField().getAnnotationsByType(Clazz.class).length > 0
|
|
||||||
&& oef.getField().getAnnotationsByType(Clazz.class)[0].value().getName().equals(entity.getTypeName())).findFirst();
|
|
||||||
if (otherEntityListField.isPresent()) {
|
|
||||||
throw new ModelException("N to N relations need to explicitly defined via a mapping model ("
|
|
||||||
+ otherEntityF.builder(mb).className() + "::" + otherEntityListField.get().getTypeName() + " and "
|
|
||||||
+ entityBuilder.className() + "::" + f.getType().getName() + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//find object in other entity (1 to N relation)
|
|
||||||
var otherEntityObjectField = otherEntityF.getFields().stream().filter(oef -> oef.getTypeName().equals(entity.getTypeName())).findFirst();
|
|
||||||
if (otherEntityObjectField.isPresent()) {
|
|
||||||
return new FieldSearchResult(otherEntityObjectField.get(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//find objectId in other entity (1 to N relation)
|
|
||||||
// var idFieldName = entity.getType().getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT)
|
|
||||||
// + entity.getType().getSimpleName().substring(1)
|
|
||||||
// + e.getName().substring(0, 1).toUpperCase(Locale.ROOT)
|
|
||||||
// + e.getName().substring(1);
|
|
||||||
var idFieldName = f.getName()
|
|
||||||
+ e.getName().substring(0, 1).toUpperCase(Locale.ROOT)
|
|
||||||
+ e.getName().substring(1);
|
|
||||||
var otherEntityIdField = otherEntityF.getFields().stream().filter(oef -> oef.getName().equals(idFieldName)).findFirst();
|
|
||||||
if (otherEntityIdField.isPresent()) {
|
|
||||||
return new FieldSearchResult(otherEntityIdField.get(), otherEntityIdField.get().getField().getAnnotationsByType(ForeignKey.class).length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// //add shadow field
|
|
||||||
// var entityField = new DbField<>(otherEntityF, f.getDeclaringClass(), null, f.getName());
|
|
||||||
// entityField.setNotNull(true);
|
|
||||||
// entityField = otherEntityF.addIfAbsent(entityField);
|
|
||||||
|
|
||||||
//add shadow id field
|
|
||||||
var entityIdField = new DbField<>(otherEntityF, entityBuilder.type().orElseThrow(), null, idFieldName);
|
|
||||||
entityIdField.setNotNull(true);
|
|
||||||
entityIdField.setDatabaseField(true);
|
|
||||||
entityIdField = otherEntityF.addIfAbsent(entityIdField);
|
|
||||||
// if (entityIdField.getExposingForeignKeyOf() == null) {
|
|
||||||
// entityIdField.setExposingForeignKeyOf(entityField);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return new FieldSearchResult(entityIdField, true);
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
if (otherFields.stream().anyMatch(FieldSearchResult::isCreateForeignKey)) {
|
|
||||||
otherEntity.builder(mb)
|
|
||||||
.hasOne(otherFields.stream().map(e -> e.getField().getName()).toArray(String[]::new))
|
|
||||||
.withMany(entity.getTypeName(), primary.getFields().stream().map(DbField::getName).toArray(String[]::new));
|
|
||||||
// otherEntity.addForeignKey(new ForeignKeyConstraint(otherEntity, (List) otherFields.stream().map(FieldSearchResult::getField).toList(),
|
|
||||||
// entity, primary.getFields(), ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void do1to1(ModelBuilder mb, DbEntityBuilder<?> entityBuilder, DbEntity<?> entity, Field f) {
|
|
||||||
Log.debug("Initializing foreign key for " + entityBuilder.className() + "::" + f.getName());
|
|
||||||
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
|
|
||||||
if (otherEntity == null) {
|
|
||||||
//all types must be initialized by EntityInitializer
|
|
||||||
throw new IllegalStateException(ForeignKeyInitializer.class.getSimpleName() + ": Entity of type " + f.getType().getName() + " does not exist");
|
|
||||||
// EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) f.getType(), f.getName());
|
|
||||||
// otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
|
|
||||||
// PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity.builder(mb));
|
|
||||||
}
|
|
||||||
var primary = otherEntity.getPrimaryKey();
|
|
||||||
if (primary == null) {
|
|
||||||
throw new ModelException("Entity " + otherEntity.builder(mb).className() + " is missing a primary key and therefore cannot be referenced by " + entityBuilder.className());
|
|
||||||
}
|
|
||||||
var otherEntityF = otherEntity;
|
|
||||||
var otherFields = primary.getFields().stream()
|
|
||||||
.map(otherPrimaryField -> {
|
|
||||||
// //find list<object> in other entity (N to 1 relation)
|
|
||||||
// var entityListField = entity.getFields().stream()
|
|
||||||
// .filter(oef -> Collection.class.isAssignableFrom(oef.getType())
|
|
||||||
// && oef.getField().getAnnotationsByType(Clazz.class).length > 0
|
|
||||||
// && oef.getField().getAnnotationsByType(Clazz.class)[0].value().getName().equals(otherEntityF.getTypeName())).toList();
|
|
||||||
//// if (entityListField.size() == 1) {
|
|
||||||
//// return new FieldSearchResult(entityListField.get(0), true);
|
|
||||||
//// }
|
|
||||||
|
|
||||||
//find object in other entity (1 to 1 relation)
|
|
||||||
// var entityObjectFields = entity.getFields().stream().filter(oef -> oef.getTypeName().equals(otherEntityF.getTypeName())).toList();
|
|
||||||
// if (entityObjectField.isPresent()) {
|
|
||||||
// return new FieldSearchResult(entityObjectField.get(), false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
//find objectId in other entity (1 to 1 relation)
|
|
||||||
//search for exposing foreign key field first
|
|
||||||
var entityIdField = entity.getFields().stream()
|
|
||||||
.filter(oef -> oef.getExposingForeignKeyOf() != null && oef.getExposingForeignKeyOf().getField().equals(f))
|
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
//search for field with name object field name + primary field name
|
|
||||||
var idFieldName = f.getName()
|
|
||||||
+ otherPrimaryField.getName().substring(0, 1).toUpperCase(Locale.ROOT)
|
|
||||||
+ otherPrimaryField.getName().substring(1);
|
|
||||||
if (!entityIdField.isPresent()) {
|
|
||||||
entityIdField = entity.getFields().stream().filter(oef -> oef.getName().equals(idFieldName)).findFirst();
|
|
||||||
}
|
|
||||||
if (entityIdField.isPresent()) {
|
|
||||||
return new FieldSearchResult(entityIdField.get(), true);//entityIdField.get().getField().getAnnotationsByType(ForeignKey.class).length == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var field = new DbField<>(entity, otherPrimaryField.getType(), null, idFieldName);
|
|
||||||
field.setExposingForeignKeyOf(entity.getField(f.getName()));
|
|
||||||
field = entity.addIfAbsent(field);
|
|
||||||
// if (entityListField.size() == 1) {
|
|
||||||
//// field.setForeignKeyObjectField(entityListField.get(0));
|
|
||||||
//// entityListField.get(0).setForeignKeyObjectField(field);
|
|
||||||
// } else
|
|
||||||
// if (entityObjectFields.size() == 1) {
|
|
||||||
// field.setExposingForeignKeyOf(entityObjectFields.get(0));
|
|
||||||
// entityObjectFields.get(0).setForeignKeyObjectField(field);
|
|
||||||
// }
|
|
||||||
return new FieldSearchResult(field, true);
|
|
||||||
// return new DbField<>(entity, e.getType(), null, f.getName() + e.getName().substring(0, 1).toUpperCase(Locale.ROOT) + e.getName().substring(1))
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
if (otherFields.stream().anyMatch(FieldSearchResult::isCreateForeignKey)) {
|
|
||||||
entityBuilder
|
|
||||||
.hasOne(otherFields.stream().map(e -> e.getField().getName()).toArray(String[]::new))
|
|
||||||
.withOne(otherEntity.getTypeName(), primary.getFields().stream().map(DbField::getName).toArray(String[]::new));
|
|
||||||
// entity.addForeignKey(new ForeignKeyConstraint(entity, (List) otherFields.stream().map(FieldSearchResult::getField).toList(),
|
|
||||||
// otherEntity, primary.getFields(), ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
private static class FieldSearchResult {
|
|
||||||
private final DbField<?> field;
|
|
||||||
private final boolean createForeignKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.asm.AsmParser;
|
|
||||||
import jef.model.constraints.ForeignKeyConstraint;
|
|
||||||
import jef.model.constraints.IndexConstraint;
|
|
||||||
import jef.model.constraints.KeyConstraint;
|
|
||||||
import jef.model.constraints.PrimaryKeyConstraint;
|
|
||||||
import jef.model.constraints.UniqueKeyConstraint;
|
|
||||||
import jef.serializable.SerializableFunction;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.util.Check;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class KeyBuilder<T extends SerializableObject> {
|
|
||||||
private final ModelBuilder modelBuilder;
|
|
||||||
private final DbEntity<T> entity;
|
|
||||||
private final List<DbField<?>> fields;
|
|
||||||
|
|
||||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withOne(SerializableFunction<T, R> getter) {
|
|
||||||
Check.notNull(getter, "getter");
|
|
||||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
|
||||||
// if (set.isEmpty()) throw new IllegalArgumentException("no fields found");
|
|
||||||
// else if (set.size() > 1) throw new IllegalArgumentException("more than 1 field found");
|
|
||||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
|
||||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
|
||||||
// var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
|
||||||
// && fk.getFields().equals(this.fields)
|
|
||||||
// && fk.getReferencedEntity() == refEntities.get(0)
|
|
||||||
// && fk.getReferencedFields().equals(refFields))
|
|
||||||
// .findFirst().orElse(null);
|
|
||||||
// if (existingFk == null) {
|
|
||||||
// existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
|
||||||
// this.entity.addForeignKey(existingFk);
|
|
||||||
// }
|
|
||||||
// return new ForeignKeyBuilder<>(existingFk);
|
|
||||||
// var rClassName = getter.getClass().
|
|
||||||
return withOne("", set.stream().map(Field::getName).toArray(String[]::new));
|
|
||||||
}
|
|
||||||
|
|
||||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withOne(String referencedClass, String... getterOrFieldNames) {
|
|
||||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
|
||||||
// var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
|
||||||
// if (set.isEmpty()) throw new IllegalArgumentException("no fields found");
|
|
||||||
// else if (set.size() > 1) throw new IllegalArgumentException("more than 1 field found");
|
|
||||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
|
||||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
|
||||||
var refEntities = List.of(modelBuilder.getEntity(referencedClass));
|
|
||||||
var refFields = IntStream.range(0, getterOrFieldNames.length)
|
|
||||||
.mapToObj(i -> {
|
|
||||||
// var field = fields.get(i);
|
|
||||||
// if (field.getExposingForeignKeyOf() != null) {
|
|
||||||
// field = field.getExposingForeignKeyOf();
|
|
||||||
// }
|
|
||||||
return refEntities.get(0).getField(getterOrFieldNames[i]);
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
// var refEntities = refFields.stream().map(e -> e.getEntity()).toList();
|
|
||||||
var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
|
||||||
&& fk.getFields().equals(this.fields)
|
|
||||||
&& fk.getReferencedEntity() == refEntities.get(0)
|
|
||||||
&& fk.getReferencedFields().equals(refFields))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (existingFk == null) {
|
|
||||||
existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), (List) refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
|
||||||
this.entity.addForeignKey(existingFk);
|
|
||||||
}
|
|
||||||
return new ForeignKeyBuilder<>(existingFk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withMany(SerializableFunction<T, R> getter) {
|
|
||||||
Check.notNull(getter, "getter");
|
|
||||||
var set = new AsmParser(getter).parse().getAccessedFields();
|
|
||||||
// if (set.isEmpty()) throw new IllegalArgumentException("no fields found");
|
|
||||||
// else if (set.size() > 1) throw new IllegalArgumentException("more than 1 field found");
|
|
||||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getField())).toList();
|
|
||||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
|
||||||
// var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
|
||||||
// && fk.getFields().equals(this.fields)
|
|
||||||
// && fk.getReferencedEntity() == refEntities.get(0)
|
|
||||||
// && fk.getReferencedFields().equals(refFields))
|
|
||||||
// .findFirst().orElse(null);
|
|
||||||
// if (existingFk == null) {
|
|
||||||
// existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
|
||||||
// this.entity.addForeignKey(existingFk);
|
|
||||||
// }
|
|
||||||
// return new ForeignKeyBuilder<>(existingFk);
|
|
||||||
return withMany("", set.stream().map(Field::getName).toArray(String[]::new));
|
|
||||||
}
|
|
||||||
|
|
||||||
public <R extends SerializableObject> ForeignKeyBuilder<T, R> withMany(String referencedClass, String... getterOrFieldNames) {
|
|
||||||
Check.notNull(getterOrFieldNames, "getterOrFieldNames");
|
|
||||||
// var set = new HashSet<>(Arrays.asList(getterOrFieldNames));
|
|
||||||
// var refFields = entity.getFields().stream().filter(e -> set.contains(e.getName())).toList();
|
|
||||||
// var refEntities = refFields.stream().map(e -> modelBuilder.getEntity(e.getTypeName())).toList();
|
|
||||||
var refEntities = List.of(modelBuilder.getEntity(referencedClass));
|
|
||||||
var refFields = IntStream.range(0, getterOrFieldNames.length)
|
|
||||||
.mapToObj(i -> {
|
|
||||||
// var field = fields.get(i);
|
|
||||||
// if (field.getExposingForeignKeyOf() != null) {
|
|
||||||
// field = field.getExposingForeignKeyOf();
|
|
||||||
// }
|
|
||||||
// var type = (Class<? extends SerializableObject>) field.getType();
|
|
||||||
// return modelBuilder.getEntity(type).getField(getterOrFieldNames[i]);
|
|
||||||
return refEntities.get(0).getField(getterOrFieldNames[i]);
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
// var refEntities = refFields.stream().map(e -> e.getEntity()).toList();
|
|
||||||
var existingFk = entity.getForeignKeys().stream().filter(fk -> fk.getEntity() == this.entity
|
|
||||||
&& fk.getFields().equals(this.fields)
|
|
||||||
&& fk.getReferencedEntity() == refEntities.get(0)
|
|
||||||
&& fk.getReferencedFields().equals(refFields))
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (existingFk == null) {
|
|
||||||
existingFk = new ForeignKeyConstraint(this.entity, this.fields, refEntities.get(0), (List) refFields, ForeignKeyConstraint.Action.RESTRICT, ForeignKeyConstraint.Action.CASCADE);
|
|
||||||
this.entity.addForeignKey(existingFk);
|
|
||||||
}
|
|
||||||
return new ForeignKeyBuilder<>(existingFk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyBuilder<T> notNull(boolean notNull) {
|
|
||||||
//TODO
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isUnique() {
|
|
||||||
isUnique(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isUnique(boolean unique) {
|
|
||||||
var key = new UniqueKeyConstraint(entity, fields);
|
|
||||||
if (unique) {
|
|
||||||
entity.addUniqueKey(key);
|
|
||||||
} else {
|
|
||||||
entity.dropUniqueKey(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isPrimaryKey() {
|
|
||||||
isPrimaryKey(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isPrimaryKey(boolean primary) {
|
|
||||||
if (primary) {
|
|
||||||
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, fields));
|
|
||||||
} else {
|
|
||||||
entity.setPrimaryKey(null);
|
|
||||||
|
|
||||||
//drop referencing foreign keys
|
|
||||||
modelBuilder.entities().forEach(e -> {
|
|
||||||
var remove = e.getEntity().getForeignKeys().stream().filter(fk -> fk.getReferencedEntity() == this.entity).toList();
|
|
||||||
remove.forEach(fk -> e.getEntity().dropForeignKey(fk));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isKey() {
|
|
||||||
isKey(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isKey(boolean key) {
|
|
||||||
var k = new KeyConstraint(entity, fields);
|
|
||||||
if (key) {
|
|
||||||
entity.addKey(k);
|
|
||||||
} else {
|
|
||||||
entity.dropKey(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isIndex() {
|
|
||||||
isIndex(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void isIndex(boolean index) {
|
|
||||||
var i = new IndexConstraint(entity, fields);
|
|
||||||
if (index) {
|
|
||||||
entity.addIndex(i);
|
|
||||||
} else {
|
|
||||||
entity.dropIndex(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.model.annotations.processors.AnnotationProcessor;
|
|
||||||
import jef.model.annotations.processors.ForeignKeyProcessor;
|
|
||||||
import jef.model.annotations.processors.GeneratedProcessor;
|
|
||||||
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 lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class ModelBuilderOptions {
|
|
||||||
private List<AnnotationProcessor> annotationProcessors;
|
|
||||||
private DbContextOptions contextOptions;
|
|
||||||
|
|
||||||
public ModelBuilderOptions() {
|
|
||||||
this.annotationProcessors = new ArrayList<>(List.of(
|
|
||||||
NotNullProcessor.INSTANCE,
|
|
||||||
UniqueProcessor.INSTANCE,
|
|
||||||
IndexProcessor.INSTANCE,
|
|
||||||
KeyProcessor.INSTANCE,
|
|
||||||
ForeignKeyProcessor.INSTANCE,
|
|
||||||
GeneratedProcessor.INSTANCE
|
|
||||||
));
|
|
||||||
this.contextOptions = new DbContextOptions(null);//TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package jef.model.annotations;
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Documented
|
|
||||||
@Target({ElementType.FIELD})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface Generated {
|
|
||||||
Type value() default Type.IDENTITY;
|
|
||||||
|
|
||||||
enum Type {
|
|
||||||
NONE,//no value is generated
|
|
||||||
IDENTITY,//value is generated on insert
|
|
||||||
COMPUTED,//value is generated on insert and update
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package jef.model.annotations.processors;
|
|
||||||
|
|
||||||
import jef.model.DbEntityBuilder;
|
|
||||||
import jef.model.DbFieldBuilder;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.annotations.Generated;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
|
|
||||||
public class GeneratedProcessor implements AnnotationProcessor {
|
|
||||||
public static final GeneratedProcessor INSTANCE = new GeneratedProcessor();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(ModelBuilder mb) {
|
|
||||||
for (DbEntityBuilder<? extends SerializableObject> entity : mb.entities()) {
|
|
||||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
|
||||||
if (field.getField().getField() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var annotation = field.getField().getField().getAnnotation(Generated.class);
|
|
||||||
if (annotation == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
field.generated(annotation.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package jef.operations;
|
|
||||||
|
|
||||||
import jef.Queryable;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
|
|
||||||
public interface Operation<T extends SerializableObject> extends Queryable<T> {//TODO is this class required
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package jef.platform;
|
|
||||||
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.platform.base.migration.MigrationApplier;
|
|
||||||
import jef.platform.base.migration.MigrationOperationTranslator;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
|
|
||||||
public interface SqlPlatform {
|
|
||||||
MigrationOperationTranslator getTranslator();
|
|
||||||
SqlTypeMapper getTypeMapper();
|
|
||||||
MigrationApplier getMigrationApplier(Connection connection, DatabaseOptions options);
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package jef.platform.base;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import jef.expressions.Expression;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbFieldBuilder;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public abstract class Database {
|
|
||||||
protected final Connection connection;
|
|
||||||
protected final DatabaseOptions options;
|
|
||||||
|
|
||||||
public abstract void migrate() throws MigrationException;
|
|
||||||
|
|
||||||
public abstract <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException;
|
|
||||||
|
|
||||||
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) throws SQLException {
|
|
||||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
|
||||||
var entity = modelBuilder.entity(clazz);
|
|
||||||
var fields = entity.fields();
|
|
||||||
try (var stmt = connection.prepareStatement(query)) {
|
|
||||||
return queryRaw(stmt, clazz, fields);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <T extends SerializableObject> List<T> queryRaw(PreparedStatement stmt, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
|
|
||||||
try (var result = stmt.executeQuery()) {
|
|
||||||
var ret = new ArrayList<T>();
|
|
||||||
while (result.next()) {
|
|
||||||
ret.add(readRow(result, clazz, fields));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <T extends SerializableObject> T readRow(ResultSet res, Class<T> clazz, List<DbFieldBuilder<?>> fields) throws SQLException {
|
|
||||||
//TODO replace with by runtime generated class
|
|
||||||
try {
|
|
||||||
var model = (T) clazz.getConstructor().newInstance();
|
|
||||||
for (DbFieldBuilder<?> field : fields) {
|
|
||||||
if (!field.getField().isDatabaseField()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Field f = field.getField().getField();
|
|
||||||
if (f == null) {
|
|
||||||
System.out.println("not class member for " + field.getField().getName());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
f.setAccessible(true);
|
|
||||||
if (field.getField().getType() == String.class) {
|
|
||||||
f.set(model, res.getString(f.getName()));
|
|
||||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
|
||||||
f.set(model, res.getByte(f.getName()));
|
|
||||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
|
||||||
// f.set(model, res.getString(f.getName()));//TODO handle char
|
|
||||||
throw new UnsupportedOperationException("char");
|
|
||||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
|
||||||
f.set(model, res.getShort(f.getName()));
|
|
||||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
|
||||||
f.set(model, res.getInt(f.getName()));
|
|
||||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
|
||||||
f.set(model, res.getLong(f.getName()));
|
|
||||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
|
||||||
f.set(model, res.getFloat(f.getName()));
|
|
||||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
|
||||||
f.set(model, res.getDouble(f.getName()));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return model;
|
|
||||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {//TODO better handling
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException;
|
|
||||||
|
|
||||||
public abstract <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException;
|
|
||||||
|
|
||||||
public abstract <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException;
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package jef.platform.base;
|
|
||||||
|
|
||||||
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 migrationsTableName;//TODO verify sql injection
|
|
||||||
|
|
||||||
protected final String host;//TODO get rid of this
|
|
||||||
protected final String database;//TODO get rid of this
|
|
||||||
|
|
||||||
public DatabaseOptions(String url, String user, String password, String migrationsPackage, String migrationsTableName) {
|
|
||||||
this.url = url;
|
|
||||||
this.user = user;
|
|
||||||
this.password = password;
|
|
||||||
|
|
||||||
host = extractHost(url);
|
|
||||||
database = extractDatabase(url);
|
|
||||||
this.migrationsPackage = migrationsPackage;
|
|
||||||
this.migrationsTableName = migrationsTableName == null || migrationsTableName.isBlank() ? "__jef_migration_log" : migrationsTableName;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package jef.platform.base;
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
package jef.platform.base.migration;
|
|
||||||
|
|
||||||
import jef.model.migration.Migration;
|
|
||||||
import jef.model.migration.MigrationBuilder;
|
|
||||||
import jef.model.migration.operation.AddFieldOperation;
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@EqualsAndHashCode//needed for test
|
|
||||||
public class M00010101000000_MigrationsLogTableMigration implements Migration {
|
|
||||||
private final SqlTypeMapper typeMapper;
|
|
||||||
private final String tableName;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void up(MigrationBuilder migrationBuilder) {//TODO add timestamp later
|
|
||||||
migrationBuilder.addTable(tableName, List.of(
|
|
||||||
new AddFieldOperation.Builder(tableName, "migration").notNull(true).sqlType(typeMapper.map(String.class.getName()).orElseThrow()),
|
|
||||||
new AddFieldOperation.Builder(tableName, "version").notNull(true).sqlType(typeMapper.map(String.class.getName()).orElseThrow())
|
|
||||||
), null);
|
|
||||||
migrationBuilder.addUniqueKey("U_" + tableName + "_migration", tableName, List.of("migration"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void down(MigrationBuilder migrationBuilder) {
|
|
||||||
migrationBuilder.dropConstraint("U_" + tableName + "_migration", tableName);
|
|
||||||
migrationBuilder.dropTable(tableName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package jef.platform.base.migration;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import jef.model.migration.Migration;
|
|
||||||
import jef.model.migration.MigrationBuilder;
|
|
||||||
import jef.model.migration.operation.MigrationOperation;
|
|
||||||
import jef.platform.SqlPlatform;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
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;
|
|
||||||
protected final SqlPlatform sqlPlatform;
|
|
||||||
|
|
||||||
public void migrate() throws MigrationException {
|
|
||||||
var migrations = new ArrayList<>(findMigrations(options.getMigrationsPackage())); //TODO find all migrations, support multiple db contexts
|
|
||||||
try {
|
|
||||||
ensureMigrationsTableExists();
|
|
||||||
getAppliedMigrations().forEach(a -> migrations.removeIf(m -> m.getClass().getSimpleName().equals(a)));
|
|
||||||
for (Migration migration : migrations) {
|
|
||||||
applyMigration(migration);
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new MigrationException("Error while applying migrations: " + e.getLocalizedMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void applyMigration(Migration m) throws MigrationException {//TODO verify transactions work
|
|
||||||
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) {
|
|
||||||
applyMigrationOperation(op);
|
|
||||||
}
|
|
||||||
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 void applyMigrationOperation(MigrationOperation operation) throws SQLException {
|
|
||||||
PreparedStatement stmt;
|
|
||||||
try {
|
|
||||||
stmt = sqlPlatform.getTranslator().translate(connection, operation);//TODO may return an object containing the prep stmt and a string rep of the query
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new SQLException("Failed to build query: " + e.getLocalizedMessage(), e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
stmt.execute();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new SQLException("Failed to execute query: " + stmt, e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
stmt.close();
|
|
||||||
} catch (SQLException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Migration> findMigrations(String packageName) throws MigrationException {//TODO rewrite this function after adding annotations to migrations (@Context, @Name, ...), also get rid of the packageName restristrict and just read the classfiles with asm
|
|
||||||
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 void ensureMigrationsTableExists() throws MigrationException {
|
|
||||||
try {
|
|
||||||
if (!migrationsTableExists()) {
|
|
||||||
createMigrationsTable();
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new MigrationException("Failed to create migrations log table", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract boolean migrationsTableExists() throws SQLException;
|
|
||||||
|
|
||||||
protected void createMigrationsTable() throws MigrationException {
|
|
||||||
applyMigration(new M00010101000000_MigrationsLogTableMigration(sqlPlatform.getTypeMapper(), options.getMigrationsTableName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract List<String> getAppliedMigrations() throws SQLException;//TODO dbset to load later
|
|
||||||
|
|
||||||
protected abstract void insertMigrationLog(Migration m) throws SQLException;//TODO dbset to insert later
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package jef.platform.base.migration;
|
|
||||||
|
|
||||||
import jef.model.migration.operation.MigrationOperation;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
public interface MigrationOperationTranslator {
|
|
||||||
PreparedStatement translate(Connection connection, MigrationOperation op) throws SQLException;
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package jef.platform.dummy;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import jef.expressions.Expression;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.platform.base.Database;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DummyDatabase extends Database {
|
|
||||||
public DummyDatabase() {
|
|
||||||
super(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void migrate() throws MigrationException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> List<T> queryRaw(DbContext context, Class<T> clazz, String query) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T entity) throws SQLException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package jef.platform.dummy;
|
|
||||||
|
|
||||||
import jef.platform.SqlPlatform;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
import jef.platform.base.migration.MigrationApplier;
|
|
||||||
import jef.platform.base.migration.MigrationOperationTranslator;
|
|
||||||
import jef.platform.dummy.migration.DummyMigrationApplier;
|
|
||||||
import jef.platform.dummy.migration.DummyMigrationOperationTranslator;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
|
|
||||||
public class DummyPlatform implements SqlPlatform {
|
|
||||||
public static final DummyPlatform INSTANCE = new DummyPlatform();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MigrationOperationTranslator getTranslator() {
|
|
||||||
return DummyMigrationOperationTranslator.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SqlTypeMapper getTypeMapper() {
|
|
||||||
return DummyTypeMapper.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MigrationApplier getMigrationApplier(Connection connection, DatabaseOptions options) {
|
|
||||||
return DummyMigrationApplier.INSTANCE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package jef.platform.dummy;
|
|
||||||
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class DummyTypeMapper extends SqlTypeMapper {
|
|
||||||
public static final DummyTypeMapper INSTANCE = new DummyTypeMapper();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<String> map(String typeName) {
|
|
||||||
return Optional.ofNullable(typeName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package jef.platform.dummy.migration;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import jef.model.migration.Migration;
|
|
||||||
import jef.platform.base.migration.MigrationApplier;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DummyMigrationApplier extends MigrationApplier {
|
|
||||||
public static final DummyMigrationApplier INSTANCE = new DummyMigrationApplier();
|
|
||||||
|
|
||||||
private DummyMigrationApplier() {
|
|
||||||
super(null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void migrate() throws MigrationException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void applyMigration(Migration m) throws MigrationException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean migrationsTableExists() throws SQLException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void createMigrationsTable() throws MigrationException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<String> getAppliedMigrations() throws SQLException {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void insertMigrationLog(Migration m) throws SQLException {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
package jef.platform.dummy.migration;
|
|
||||||
|
|
||||||
import jef.model.migration.operation.MigrationOperation;
|
|
||||||
import jef.platform.base.migration.MigrationOperationTranslator;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class DummyMigrationOperationTranslator implements MigrationOperationTranslator {
|
|
||||||
public static final DummyMigrationOperationTranslator INSTANCE = new DummyMigrationOperationTranslator();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PreparedStatement translate(Connection connection, MigrationOperation op) throws SQLException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package jef.util;
|
|
||||||
|
|
||||||
public class Log {
|
|
||||||
public static void debug(String s) {
|
|
||||||
System.out.println(s);//TODO implement logging
|
|
||||||
}
|
|
||||||
|
|
||||||
private Log() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package jef.util;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public abstract class Util {
|
|
||||||
public static <T> Optional<T> tryGet(ThrowableSupplier<T> s) {
|
|
||||||
try {
|
|
||||||
return Optional.ofNullable(s.get());
|
|
||||||
} catch (Throwable t) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ThrowableSupplier<T> {
|
|
||||||
T get() throws Throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ThrowableFunction<T, R> {
|
|
||||||
R apply(T t) throws Throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ThrowableBiFunction<T, U, R> {
|
|
||||||
R apply(T t, U u) throws Throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ThrowableBiConsumer<T, U> {
|
|
||||||
void accept(T t, U u) throws Throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Equality<T> {
|
|
||||||
boolean check(T t1, T t2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface NullableEquality<T> {
|
|
||||||
default boolean check(T t1, T t2) {
|
|
||||||
if (t1 == t2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (t1 != null) {
|
|
||||||
return compare(t1, t2);
|
|
||||||
} else {
|
|
||||||
return compare(t2, t1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean compare(/*NonNull*/T t1, T t2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package jef.asm.access;
|
|
||||||
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
|
|
||||||
class ClassAnalyzerTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void analyze() {
|
|
||||||
var analyzer = new ClassAnalyzer();
|
|
||||||
var result = analyzer.analyze(Employee.class);
|
|
||||||
System.out.println(result.toStringFriendly());
|
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-45
@@ -1,45 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.Id;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
|
||||||
|
|
||||||
class EntityInitializerCollectionAnnotationClassInheritTest {
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
assertEquals("Field TestClass2::nested is missing the Clazz annotation",
|
|
||||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(TestClass2.class)
|
|
||||||
private DbSet<TestClass2> objects2;
|
|
||||||
@Clazz(TestClass.class)
|
|
||||||
private DbSet<TestClass> objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass2 extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i2 = 1;
|
|
||||||
public List<TestClass> nested;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i = 1;
|
|
||||||
public Object o = new Object();
|
|
||||||
public double d;
|
|
||||||
public float f;
|
|
||||||
public long l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.Id;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
|
||||||
|
|
||||||
class EntityInitializerCollectionAnnotationMissingTest {
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
assertEquals("Field TestClass2::nested has a class in its Clazz annotation that does not inherit from SerializableObject: int",
|
|
||||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(TestClass2.class)
|
|
||||||
private DbSet<TestClass2> objects2;
|
|
||||||
@Clazz(TestClass.class)
|
|
||||||
private DbSet<TestClass> objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass2 extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i2 = 1;
|
|
||||||
@Clazz(int.class)
|
|
||||||
public List<TestClass> nested;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i = 1;
|
|
||||||
public Object o = new Object();
|
|
||||||
public double d;
|
|
||||||
public float f;
|
|
||||||
public long l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.Id;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
|
||||||
|
|
||||||
class EntityInitializerDbSetAnnotationClassInheritTest {
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
assertEquals("DbSet objects1 has a class in its Clazz annotation that does not inherit from SerializableObject: int",
|
|
||||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(int.class)
|
|
||||||
private DbSet<TestClass> objects1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i = 1;
|
|
||||||
public Object o = new Object();
|
|
||||||
public double d;
|
|
||||||
public float f;
|
|
||||||
public long l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package jef.model;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.Id;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
class KeyBuilderPrimaryDropRelatedForeignKeyDropTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
var mb = ModelBuilder.from(Ctx.class);
|
|
||||||
|
|
||||||
assertEquals(1, mb.getEntity(TestClass3.class).getForeignKeys().size());
|
|
||||||
assertEquals(1, mb.getEntity(TestClass2.class).getForeignKeys().size());
|
|
||||||
assertEquals(0, mb.getEntity(TestClass.class).getForeignKeys().size());
|
|
||||||
|
|
||||||
mb.entity(TestClass.class).hasOne(e -> e.i).isPrimaryKey(false);
|
|
||||||
|
|
||||||
assertEquals(1, mb.getEntity(TestClass3.class).getForeignKeys().size());
|
|
||||||
assertEquals(0, mb.getEntity(TestClass2.class).getForeignKeys().size());
|
|
||||||
assertEquals(0, mb.getEntity(TestClass.class).getForeignKeys().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(TestClass.class)
|
|
||||||
private DbSet<TestClass> objects1;
|
|
||||||
@Clazz(TestClass2.class)
|
|
||||||
private DbSet<TestClass2> objects2;
|
|
||||||
@Clazz(TestClass3.class)
|
|
||||||
private DbSet<TestClass2> objects3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass3 extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i3 = 1;
|
|
||||||
private TestClass2 nested2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass2 extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i2 = 1;
|
|
||||||
private TestClass nested;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i = 1;
|
|
||||||
public Object o = new Object();
|
|
||||||
public double d;
|
|
||||||
public float f;
|
|
||||||
public long l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-74
@@ -1,74 +0,0 @@
|
|||||||
package jef.model.annotations.processors;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.model.annotations.Id;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
class ForeignKeyProcessorExposeViaMethodTest {
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
var mb = ModelBuilder.from(Ctx.class);
|
|
||||||
|
|
||||||
assertEquals(2, mb.getEntities().size());
|
|
||||||
assertEquals("objects1", mb.getEntity(TestClass.class).getName());
|
|
||||||
assertEquals("objects2", mb.getEntity(TestClass2.class).getName());
|
|
||||||
assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class)));
|
|
||||||
assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class)));
|
|
||||||
assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class)));
|
|
||||||
assertTrue(mb.getEntity(TestClass2.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class)));
|
|
||||||
|
|
||||||
//fields ------------------------
|
|
||||||
//TestClass
|
|
||||||
assertEquals(3, mb.getEntity(TestClass.class).getFields().size());
|
|
||||||
assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i") && e.isModelField() && e.isDatabaseField()).count());
|
|
||||||
assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("test") && e.isModelField() && !e.isDatabaseField()).count());
|
|
||||||
assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField") && e.isModelField() && e.isDatabaseField()).count());
|
|
||||||
|
|
||||||
//TestClass2
|
|
||||||
assertEquals(1, mb.getEntity(TestClass2.class).getFields().size());
|
|
||||||
assertEquals(1, mb.getEntity(TestClass2.class).getFields().stream().filter(e -> e.getName().equals("i") && e.isModelField() && e.isDatabaseField()).count());
|
|
||||||
// /fields ------------------------
|
|
||||||
|
|
||||||
//keys ------------------------
|
|
||||||
assertEquals(1, mb.getEntity(TestClass.class).getForeignKeys().size());
|
|
||||||
assertEquals(mb.getEntity(TestClass.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getEntity());
|
|
||||||
assertEquals(mb.getEntity(TestClass2.class), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedEntity());
|
|
||||||
assertEquals(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("testField")).toList(),
|
|
||||||
mb.getEntity(TestClass.class).getForeignKeys().get(0).getFields());
|
|
||||||
assertEquals(mb.getEntity(TestClass2.class).getPrimaryKey().getFields(), mb.getEntity(TestClass.class).getForeignKeys().get(0).getReferencedFields());
|
|
||||||
// /keys ------------------------
|
|
||||||
|
|
||||||
//exposing fields
|
|
||||||
assertEquals(mb.getEntity(TestClass.class).getField("test"), mb.getEntity(TestClass.class).getField("testField").getExposingForeignKeyOf());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(TestClass.class)
|
|
||||||
private DbSet<TestClass> objects1;
|
|
||||||
@Clazz(TestClass2.class)
|
|
||||||
private DbSet<TestClass2> objects2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass extends SerializableObject {
|
|
||||||
public int i = 1;
|
|
||||||
private TestClass2 test;
|
|
||||||
@ForeignKey(getterOrField = "getTest")
|
|
||||||
public int testField;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass2 extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
package jef.model.annotations.processors;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.ModelException;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.model.annotations.Id;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
|
||||||
|
|
||||||
class ForeignKeyProcessorInvalidFieldTypeTest {
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
assertEquals("The getter/field in TestClass::test is not a SerializableObject (via @ForeignKey in TestClass::testFk)",
|
|
||||||
assertThrowsExactly(ModelException.class, () -> ModelBuilder.from(Ctx.class)).getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(TestClass.class)
|
|
||||||
private DbSet<TestClass> objects1;
|
|
||||||
@Clazz(TestClass2.class)
|
|
||||||
private DbSet<TestClass2> objects2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass extends SerializableObject {
|
|
||||||
public int i = 1;
|
|
||||||
public Object o = new Object();
|
|
||||||
public double d;
|
|
||||||
public float f;
|
|
||||||
public long l;
|
|
||||||
private TestClass2 test;
|
|
||||||
@ForeignKey(getterOrField = "l")
|
|
||||||
public int testFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass2 extends SerializableObject {
|
|
||||||
@Id
|
|
||||||
public int i = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
package jef.platform.base.migration;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import jef.model.migration.Migration;
|
|
||||||
import jef.model.migration.MigrationBuilder;
|
|
||||||
import jef.model.migration.operation.MigrationOperation;
|
|
||||||
import jef.platform.SqlPlatform;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
|
||||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.doNothing;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.doThrow;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
||||||
import static org.mockito.Mockito.withSettings;
|
|
||||||
|
|
||||||
class MigrationApplierTest {
|
|
||||||
private String packageName;
|
|
||||||
private DatabaseOptions options;
|
|
||||||
private Connection connection;
|
|
||||||
private SqlPlatform platform;
|
|
||||||
private MigrationApplier applier;
|
|
||||||
private Object[] allMocks;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setup() {
|
|
||||||
//setup
|
|
||||||
packageName = "package";
|
|
||||||
options = mock(DatabaseOptions.class);
|
|
||||||
doReturn(packageName).when(options).getMigrationsPackage();
|
|
||||||
|
|
||||||
connection = mock(Connection.class);
|
|
||||||
platform = mock(SqlPlatform.class);
|
|
||||||
applier = mock(MigrationApplier.class, withSettings().useConstructor(connection, options, platform).defaultAnswer(CALLS_REAL_METHODS)); //spy
|
|
||||||
|
|
||||||
allMocks = List.of(options, connection, platform, applier).toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrate_success() throws MigrationException, SQLException {
|
|
||||||
interface Migration1 extends Migration {
|
|
||||||
}
|
|
||||||
interface Migration2 extends Migration {
|
|
||||||
}
|
|
||||||
var migration1 = mock(Migration1.class);
|
|
||||||
var migration2 = mock(Migration2.class);
|
|
||||||
doReturn(List.of(migration1, migration2)).when(applier).findMigrations(packageName);
|
|
||||||
|
|
||||||
doNothing().when(applier).ensureMigrationsTableExists();
|
|
||||||
doReturn(List.of(migration1.getClass().getSimpleName())).when(applier).getAppliedMigrations();
|
|
||||||
doNothing().when(applier).applyMigration(any(Migration.class));
|
|
||||||
|
|
||||||
//test
|
|
||||||
applier.migrate();
|
|
||||||
|
|
||||||
//assert
|
|
||||||
verify(applier, times(1)).migrate();
|
|
||||||
verify(options, times(1)).getMigrationsPackage();
|
|
||||||
verify(applier, times(1)).findMigrations(packageName);
|
|
||||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
|
||||||
verify(applier, times(1)).getAppliedMigrations();
|
|
||||||
verify(applier, times(1)).applyMigration(migration2);
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(migration1, migration2);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrate_findMigrationsThrowsMigrationException() throws MigrationException {
|
|
||||||
//setup
|
|
||||||
var exception = new MigrationException("reason");
|
|
||||||
doThrow(exception).when(applier).findMigrations(packageName);
|
|
||||||
|
|
||||||
//test
|
|
||||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertSame(exception, ex);
|
|
||||||
|
|
||||||
verify(applier, times(1)).migrate();
|
|
||||||
verify(options, times(1)).getMigrationsPackage();
|
|
||||||
verify(applier, times(1)).findMigrations(packageName);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrate_ensureMigrationsTableExistsThrowsMigrationException() throws MigrationException {
|
|
||||||
//setup
|
|
||||||
doReturn(List.of()).when(applier).findMigrations(packageName);
|
|
||||||
var exception = new MigrationException("reason");
|
|
||||||
doThrow(exception).when(applier).ensureMigrationsTableExists();
|
|
||||||
|
|
||||||
//test
|
|
||||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertSame(exception, ex);
|
|
||||||
|
|
||||||
verify(applier, times(1)).migrate();
|
|
||||||
verify(options, times(1)).getMigrationsPackage();
|
|
||||||
verify(applier, times(1)).findMigrations(packageName);
|
|
||||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrate_getAppliedMigrationsThrowsSqlException() throws MigrationException, SQLException {
|
|
||||||
//setup
|
|
||||||
doReturn(List.of()).when(applier).findMigrations(packageName);
|
|
||||||
doNothing().when(applier).ensureMigrationsTableExists();
|
|
||||||
var exception = new SQLException("reason");
|
|
||||||
doThrow(exception).when(applier).getAppliedMigrations();
|
|
||||||
|
|
||||||
//test
|
|
||||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertEquals("Error while applying migrations: reason", ex.getMessage());
|
|
||||||
assertSame(exception, ex.getCause());
|
|
||||||
|
|
||||||
verify(applier, times(1)).migrate();
|
|
||||||
verify(options, times(1)).getMigrationsPackage();
|
|
||||||
verify(applier, times(1)).findMigrations(packageName);
|
|
||||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
|
||||||
verify(applier, times(1)).getAppliedMigrations();
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrate_applyMigrationThrowsMigrationException() throws MigrationException, SQLException {
|
|
||||||
//setup
|
|
||||||
var migration = mock(Migration.class);
|
|
||||||
doReturn(List.of(migration)).when(applier).findMigrations(packageName);
|
|
||||||
|
|
||||||
doNothing().when(applier).ensureMigrationsTableExists();
|
|
||||||
doReturn(List.of()).when(applier).getAppliedMigrations();
|
|
||||||
|
|
||||||
var exception = new MigrationException("reason");
|
|
||||||
doThrow(exception).when(applier).applyMigration(migration);
|
|
||||||
|
|
||||||
//test
|
|
||||||
var ex = assertThrows(MigrationException.class, () -> applier.migrate());
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertSame(exception, ex);
|
|
||||||
|
|
||||||
verify(applier, times(1)).migrate();
|
|
||||||
verify(options, times(1)).getMigrationsPackage();
|
|
||||||
verify(applier, times(1)).findMigrations(packageName);
|
|
||||||
verify(applier, times(1)).ensureMigrationsTableExists();
|
|
||||||
verify(applier, times(1)).getAppliedMigrations();
|
|
||||||
verify(applier, times(1)).applyMigration(migration);
|
|
||||||
verifyNoMoreInteractions(migration);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void applyMigration_success() throws SQLException, MigrationException {
|
|
||||||
//setup
|
|
||||||
var migration = mock(Migration.class);
|
|
||||||
var operationBuilder = new MigrationOperation.Builder[]{null};
|
|
||||||
var operation = new MigrationOperation[]{null};
|
|
||||||
doAnswer(invok -> {
|
|
||||||
operationBuilder[0] = ((MigrationBuilder) invok.getArguments()[0]).addTable("test", List.of(), null);
|
|
||||||
operation[0] = operationBuilder[0].build();
|
|
||||||
return null;
|
|
||||||
}).when(migration).up(any(MigrationBuilder.class));
|
|
||||||
doNothing().when(applier).applyMigrationOperation(argThat(e -> e.equals(operation[0])));//eq(operation[0]) does not work as var would have to be set already and isn't
|
|
||||||
|
|
||||||
//test
|
|
||||||
applier.applyMigration(migration);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
verify(applier, times(1)).applyMigration(migration);
|
|
||||||
verify(migration, times(1)).up(any(MigrationBuilder.class));
|
|
||||||
verify(connection, times(1)).setAutoCommit(false);
|
|
||||||
verify(applier, times(1)).applyMigrationOperation(operation[0]);
|
|
||||||
verify(applier, times(1)).insertMigrationLog(migration);
|
|
||||||
verify(connection, times(1)).commit();
|
|
||||||
verify(connection, times(1)).setAutoCommit(true);
|
|
||||||
verifyNoMoreInteractions(migration);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void applyMigration_applyMigrationOperationThrowsSqlException() throws SQLException, MigrationException {
|
|
||||||
//setup
|
|
||||||
var migration = mock(Migration.class);
|
|
||||||
var operationBuilder = new MigrationOperation.Builder[]{null};
|
|
||||||
var operation = new MigrationOperation[]{null};
|
|
||||||
doAnswer(invok -> {
|
|
||||||
operationBuilder[0] = ((MigrationBuilder) invok.getArguments()[0]).addTable("test", List.of(), null);
|
|
||||||
operation[0] = operationBuilder[0].build();
|
|
||||||
return null;
|
|
||||||
}).when(migration).up(any(MigrationBuilder.class));
|
|
||||||
var sqlException = new SQLException("reason");
|
|
||||||
doThrow(sqlException).when(applier).applyMigrationOperation(argThat(e -> e.equals(operation[0])));//eq(operation[0]) does not work as var would have to be set already and isn't
|
|
||||||
|
|
||||||
//test
|
|
||||||
var thrown = assertThrows(MigrationException.class, () -> applier.applyMigration(migration));
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertTrue(thrown.getMessage().matches("Failed to apply migration '(.*?)' to the database: reason"));
|
|
||||||
assertSame(sqlException, thrown.getCause());
|
|
||||||
|
|
||||||
verify(applier, times(1)).applyMigration(migration);
|
|
||||||
verify(migration, times(1)).up(any(MigrationBuilder.class));
|
|
||||||
verify(connection, times(1)).setAutoCommit(false);
|
|
||||||
verify(applier, times(1)).applyMigrationOperation(operation[0]);
|
|
||||||
verify(connection, times(1)).rollback();
|
|
||||||
verify(connection, times(1)).setAutoCommit(true);
|
|
||||||
verifyNoMoreInteractions(migration);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void applyMigration_insertMigrationLogThrowsSqlException() throws SQLException, MigrationException {
|
|
||||||
//setup
|
|
||||||
var migration = mock(Migration.class);
|
|
||||||
var operationBuilder = new MigrationOperation.Builder[]{null};
|
|
||||||
var operation = new MigrationOperation[]{null};
|
|
||||||
doAnswer(invok -> {
|
|
||||||
operationBuilder[0] = ((MigrationBuilder) invok.getArguments()[0]).addTable("test", List.of(), null);
|
|
||||||
operation[0] = operationBuilder[0].build();
|
|
||||||
return null;
|
|
||||||
}).when(migration).up(any(MigrationBuilder.class));
|
|
||||||
var sqlException = new SQLException("reason");
|
|
||||||
doNothing().when(applier).applyMigrationOperation(argThat(e -> e.equals(operation[0])));//eq(operation[0]) does not work as var would have to be set already and isn't
|
|
||||||
doThrow(sqlException).when(applier).insertMigrationLog(migration);
|
|
||||||
|
|
||||||
//test
|
|
||||||
var thrown = assertThrows(MigrationException.class, () -> applier.applyMigration(migration));
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertTrue(thrown.getMessage().matches("Failed to apply migration '(.*?)' to the database: reason"));
|
|
||||||
assertSame(sqlException, thrown.getCause());
|
|
||||||
|
|
||||||
verify(applier, times(1)).applyMigration(migration);
|
|
||||||
verify(migration, times(1)).up(any(MigrationBuilder.class));
|
|
||||||
verify(connection, times(1)).setAutoCommit(false);
|
|
||||||
verify(applier, times(1)).applyMigrationOperation(operation[0]);
|
|
||||||
verify(applier, times(1)).insertMigrationLog(migration);
|
|
||||||
verify(connection, times(1)).rollback();
|
|
||||||
verify(connection, times(1)).setAutoCommit(true);
|
|
||||||
verifyNoMoreInteractions(migration);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void applyMigrationOperation_success() throws SQLException {
|
|
||||||
//setup
|
|
||||||
var translator = mock(MigrationOperationTranslator.class);
|
|
||||||
doReturn(translator).when(platform).getTranslator();
|
|
||||||
var operation = mock(MigrationOperation.class);
|
|
||||||
var stmt = mock(PreparedStatement.class);
|
|
||||||
doReturn(stmt).when(translator).translate(connection, operation);
|
|
||||||
doReturn("query").when(stmt).toString();
|
|
||||||
|
|
||||||
//test
|
|
||||||
applier.applyMigrationOperation(operation);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
verify(applier, times(1)).applyMigrationOperation(operation);
|
|
||||||
verify(platform, times(1)).getTranslator();
|
|
||||||
verify(translator, times(1)).translate(connection, operation);
|
|
||||||
verify(stmt, times(1)).execute();
|
|
||||||
verify(stmt, times(1)).close();
|
|
||||||
verifyNoMoreInteractions(translator, operation, stmt);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void applyMigrationOperation_translateThrowsSQLException() throws SQLException {
|
|
||||||
//setup
|
|
||||||
var translator = mock(MigrationOperationTranslator.class);
|
|
||||||
doReturn(translator).when(platform).getTranslator();
|
|
||||||
var operation = mock(MigrationOperation.class);
|
|
||||||
var stmt = mock(PreparedStatement.class);
|
|
||||||
var sqlException = new SQLException("reason");
|
|
||||||
doThrow(sqlException).when(translator).translate(connection, operation);
|
|
||||||
|
|
||||||
//test
|
|
||||||
var thrown = assertThrows(SQLException.class, () -> applier.applyMigrationOperation(operation));
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertEquals("Failed to build query: reason", thrown.getMessage());
|
|
||||||
assertSame(sqlException, thrown.getCause());
|
|
||||||
|
|
||||||
verify(applier, times(1)).applyMigrationOperation(operation);
|
|
||||||
verify(platform, times(1)).getTranslator();
|
|
||||||
verify(translator, times(1)).translate(connection, operation);
|
|
||||||
verifyNoMoreInteractions(translator, operation, stmt);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void applyMigrationOperation_stmtExecuteThrowsSQLException() throws SQLException {
|
|
||||||
//setup
|
|
||||||
var translator = mock(MigrationOperationTranslator.class);
|
|
||||||
doReturn(translator).when(platform).getTranslator();
|
|
||||||
var operation = mock(MigrationOperation.class);
|
|
||||||
var stmt = mock(PreparedStatement.class);
|
|
||||||
doReturn(stmt).when(translator).translate(connection, operation);
|
|
||||||
doReturn("query").when(stmt).toString();
|
|
||||||
var sqlException = new SQLException("reason");
|
|
||||||
doThrow(sqlException).when(stmt).execute();
|
|
||||||
|
|
||||||
//test
|
|
||||||
var thrown = assertThrows(SQLException.class, () -> applier.applyMigrationOperation(operation));
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertEquals("Failed to execute query: query", thrown.getMessage());
|
|
||||||
assertSame(sqlException, thrown.getCause());
|
|
||||||
|
|
||||||
verify(applier, times(1)).applyMigrationOperation(operation);
|
|
||||||
verify(platform, times(1)).getTranslator();
|
|
||||||
verify(translator, times(1)).translate(connection, operation);
|
|
||||||
verify(stmt, times(1)).execute();
|
|
||||||
verify(stmt, times(1)).close();
|
|
||||||
verifyNoMoreInteractions(translator, operation, stmt);
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void findMigrations() {
|
|
||||||
//setup
|
|
||||||
|
|
||||||
|
|
||||||
//test
|
|
||||||
|
|
||||||
|
|
||||||
//assert
|
|
||||||
|
|
||||||
}//TODO after function rewrite
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void createMigrationsTable_success() throws MigrationException {
|
|
||||||
//setup
|
|
||||||
var mapper = mock(SqlTypeMapper.class);
|
|
||||||
doReturn(mapper).when(platform).getTypeMapper();
|
|
||||||
|
|
||||||
var migrationTableName = "migrationtable";
|
|
||||||
doReturn(migrationTableName).when(options).getMigrationsTableName();
|
|
||||||
doNothing().when(applier).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
|
||||||
|
|
||||||
//test
|
|
||||||
applier.createMigrationsTable();
|
|
||||||
|
|
||||||
//assert
|
|
||||||
verify(applier, times(1)).createMigrationsTable();
|
|
||||||
verify(platform, times(1)).getTypeMapper();
|
|
||||||
verify(options, times(1)).getMigrationsTableName();
|
|
||||||
verify(applier, times(1)).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void createMigrationsTable_applyMigrationThrowsMigrationException() throws MigrationException {
|
|
||||||
//setup
|
|
||||||
var mapper = mock(SqlTypeMapper.class);
|
|
||||||
doReturn(mapper).when(platform).getTypeMapper();
|
|
||||||
|
|
||||||
var migrationTableName = "migrationtable";
|
|
||||||
doReturn(migrationTableName).when(options).getMigrationsTableName();
|
|
||||||
|
|
||||||
var exception = new MigrationException("reason");
|
|
||||||
doThrow(exception).when(applier).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
|
||||||
|
|
||||||
//test
|
|
||||||
var thrown = assertThrows(MigrationException.class, () -> applier.createMigrationsTable());
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertSame(exception, thrown);
|
|
||||||
|
|
||||||
verify(applier, times(1)).createMigrationsTable();
|
|
||||||
verify(platform, times(1)).getTypeMapper();
|
|
||||||
verify(options, times(1)).getMigrationsTableName();
|
|
||||||
verify(applier, times(1)).applyMigration(new M00010101000000_MigrationsLogTableMigration(mapper, migrationTableName));
|
|
||||||
verifyNoMoreInteractions(allMocks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package jef.platform.dummy;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
class DummyDatabaseTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrate() throws MigrationException {
|
|
||||||
new DummyDatabase().migrate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package jef.platform.dummy;
|
|
||||||
|
|
||||||
import jef.platform.dummy.migration.DummyMigrationApplier;
|
|
||||||
import jef.platform.dummy.migration.DummyMigrationOperationTranslator;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
|
||||||
|
|
||||||
class DummyPlatformTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getTranslator() {
|
|
||||||
//test
|
|
||||||
var translator = new DummyPlatform().getTranslator();
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertSame(DummyMigrationOperationTranslator.INSTANCE, translator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getTypeMapper() {
|
|
||||||
assertSame(DummyTypeMapper.INSTANCE, new DummyPlatform().getTypeMapper());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getMigrationApplier() {
|
|
||||||
assertSame(DummyMigrationApplier.INSTANCE, DummyPlatform.INSTANCE.getMigrationApplier(null, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package jef.platform.dummy;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
class DummyTypeMapperTest {
|
|
||||||
@Test
|
|
||||||
void map() {
|
|
||||||
//setup
|
|
||||||
var type = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
//test
|
|
||||||
Optional<String> opt = DummyTypeMapper.INSTANCE.map(type);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertTrue(opt.isPresent());
|
|
||||||
assertSame(type, opt.orElseThrow());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package jef.platform.dummy.migration;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import jef.model.migration.Migration;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
class DummyMigrationApplierTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrate() throws MigrationException {
|
|
||||||
DummyMigrationApplier.INSTANCE.migrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void applyMigration() throws MigrationException {
|
|
||||||
//setup
|
|
||||||
Migration migration = mock(Migration.class);
|
|
||||||
|
|
||||||
//test
|
|
||||||
DummyMigrationApplier.INSTANCE.applyMigration(migration);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void findMigrations() throws MigrationException {
|
|
||||||
var foundMethodOverride = new Throwable();
|
|
||||||
try {
|
|
||||||
DummyMigrationApplier.class.getDeclaredMethod("findMigrations", String.class);//throws NoSuchMethodException, i.e. not overriden
|
|
||||||
throw foundMethodOverride;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
assertNotSame(foundMethodOverride, t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void migrationsTableExists() throws SQLException {
|
|
||||||
assertFalse(DummyMigrationApplier.INSTANCE.migrationsTableExists());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void createMigrationsTable() throws MigrationException {
|
|
||||||
DummyMigrationApplier.INSTANCE.createMigrationsTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void getAppliedMigrations() throws SQLException {
|
|
||||||
assertEquals(0, DummyMigrationApplier.INSTANCE.getAppliedMigrations().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void insertMigrationLog() throws SQLException {
|
|
||||||
//setup
|
|
||||||
Migration migration = mock(Migration.class);
|
|
||||||
|
|
||||||
//test
|
|
||||||
DummyMigrationApplier.INSTANCE.insertMigrationLog(migration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
package jef.platform.dummy.migration;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
|
|
||||||
class DummyMigrationOperationTranslatorTest {
|
|
||||||
@Test
|
|
||||||
void translate() throws SQLException {
|
|
||||||
assertNull(DummyMigrationOperationTranslator.INSTANCE.translate(null, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
package jef.query;
|
|
||||||
|
|
||||||
import jef.expressions.AndExpression;
|
|
||||||
import jef.expressions.BinaryExpression;
|
|
||||||
import jef.expressions.ConstantExpression;
|
|
||||||
import jef.expressions.FieldExpression;
|
|
||||||
import jef.expressions.IntermediateFieldExpression;
|
|
||||||
import jef.expressions.LimitExpression;
|
|
||||||
import jef.expressions.NullExpression;
|
|
||||||
import jef.expressions.OrExpression;
|
|
||||||
import jef.expressions.OrderExpression;
|
|
||||||
import jef.expressions.ParameterExpression;
|
|
||||||
import jef.expressions.SelectExpression;
|
|
||||||
import jef.expressions.TableExpression;
|
|
||||||
import jef.expressions.TernaryExpression;
|
|
||||||
import jef.expressions.UnaryExpression;
|
|
||||||
import jef.expressions.WhereExpression;
|
|
||||||
import jef.platform.base.query.QueryBuilder;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
class QueryBuilderTest {
|
|
||||||
@Test
|
|
||||||
void visitAnd() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitAnd(new AndExpression(ConstantExpression.V0, ConstantExpression.V1));
|
|
||||||
Assertions.assertEquals("0 AND 1", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitBinary() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitBinary(new BinaryExpression(ConstantExpression.V0, ConstantExpression.V1, BinaryExpression.Operator.EQ));
|
|
||||||
Assertions.assertEquals("0 = 1", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitConstant() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitConstant(ConstantExpression.V0);
|
|
||||||
Assertions.assertEquals("0", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitField() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitField(new FieldExpression("schema", "table", "name", null));
|
|
||||||
Assertions.assertEquals("`schema`.`table`.`name`", v.getQuery());
|
|
||||||
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitField(new FieldExpression(null, "table", "name", null));
|
|
||||||
Assertions.assertEquals("`table`.`name`", v.getQuery());
|
|
||||||
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitField(new FieldExpression("schema", null, "name", null));
|
|
||||||
Assertions.assertEquals("`name`", v.getQuery());
|
|
||||||
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitField(new FieldExpression(null, null, "name", null));
|
|
||||||
Assertions.assertEquals("`name`", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitIntermediateField() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitIntermediateField(new IntermediateFieldExpression("value", null));
|
|
||||||
Assertions.assertEquals("value", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitLimit() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitLimit(new LimitExpression(ConstantExpression.V0, 3L, 5L));
|
|
||||||
Assertions.assertEquals("0 OFFSET 3 LIMIT 5", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitNull() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitNull(NullExpression.INSTANCE);
|
|
||||||
Assertions.assertEquals("NULL", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitOr() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitOr(new OrExpression(ConstantExpression.V0, ConstantExpression.V1));
|
|
||||||
Assertions.assertEquals("0 OR 1", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitOrder() {
|
|
||||||
//multiple sorts
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
|
||||||
new OrderExpression.Sort(new FieldExpression(null, null, "name1", null), OrderExpression.SortDirection.ASCENDING),
|
|
||||||
new OrderExpression.Sort(new FieldExpression(null, null, "name2", null), OrderExpression.SortDirection.DESCENDING))));
|
|
||||||
Assertions.assertEquals("0 ORDER BY `name1` ASC, `name2` DESC", v.getQuery());
|
|
||||||
|
|
||||||
//with schema and table
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
|
||||||
new OrderExpression.Sort(new FieldExpression("schema", "table", "name", null), OrderExpression.SortDirection.ASCENDING))));
|
|
||||||
Assertions.assertEquals("0 ORDER BY `schema`.`table`.`name` ASC", v.getQuery());
|
|
||||||
|
|
||||||
//with table
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
|
||||||
new OrderExpression.Sort(new FieldExpression(null, "table", "name", null), OrderExpression.SortDirection.ASCENDING))));
|
|
||||||
Assertions.assertEquals("0 ORDER BY `table`.`name` ASC", v.getQuery());
|
|
||||||
|
|
||||||
//with schema
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
|
||||||
new OrderExpression.Sort(new FieldExpression("schema", null, "name", null), OrderExpression.SortDirection.ASCENDING))));
|
|
||||||
Assertions.assertEquals("0 ORDER BY `name` ASC", v.getQuery());
|
|
||||||
|
|
||||||
//without schema and table
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitOrder(new OrderExpression(ConstantExpression.V0, List.of(
|
|
||||||
new OrderExpression.Sort(new FieldExpression(null, null, "name", null), OrderExpression.SortDirection.ASCENDING))));
|
|
||||||
Assertions.assertEquals("0 ORDER BY `name` ASC", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitParameter() {
|
|
||||||
//isInput
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitParameter(new ParameterExpression(0, null, true, null));
|
|
||||||
Assertions.assertEquals("param #0", v.getQuery());
|
|
||||||
|
|
||||||
//value == null
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitParameter(new ParameterExpression(0, null, false, null));
|
|
||||||
Assertions.assertEquals("NULL", v.getQuery());
|
|
||||||
|
|
||||||
//value == null
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitParameter(new ParameterExpression(0, Arrays.asList(null, "value"), false, null));
|
|
||||||
Assertions.assertEquals("(NULL, value)", v.getQuery());
|
|
||||||
|
|
||||||
//value == null
|
|
||||||
var v2 = new QueryBuilder();
|
|
||||||
Assertions.assertThrows(UnsupportedOperationException.class, () -> v2.visitParameter(new ParameterExpression(0, "value", false, null)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitSelect() {
|
|
||||||
//select from non table expr
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitSelect(new SelectExpression(List.of(
|
|
||||||
new FieldExpression(null, null, "name", null),
|
|
||||||
new FieldExpression(null, null, "name2", null)
|
|
||||||
), ConstantExpression.V0, "alias"));
|
|
||||||
Assertions.assertEquals("SELECT `name`, `name2` FROM (0) alias", v.getQuery());
|
|
||||||
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitSelect(new SelectExpression(List.of(
|
|
||||||
new FieldExpression(null, null, "name", null),
|
|
||||||
new FieldExpression(null, null, "name2", null)
|
|
||||||
), new TableExpression("table"), "alias"));
|
|
||||||
Assertions.assertEquals("SELECT `name`, `name2` FROM `table` alias", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitTable() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitTable(new TableExpression("table"));
|
|
||||||
Assertions.assertEquals("`table`", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitTernary() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitTernary(new TernaryExpression(ConstantExpression.V0, ConstantExpression.V1, ConstantExpression.V2));
|
|
||||||
Assertions.assertEquals("IF(0, 1, 2)", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitUnary() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitUnary(new UnaryExpression(NullExpression.INSTANCE, UnaryExpression.Operator.NOT));
|
|
||||||
Assertions.assertEquals("NOT NULL", v.getQuery());
|
|
||||||
|
|
||||||
v = new QueryBuilder();
|
|
||||||
v.visitUnary(new UnaryExpression(new BinaryExpression(ConstantExpression.V0, ConstantExpression.V1, BinaryExpression.Operator.EQ), UnaryExpression.Operator.NOT));
|
|
||||||
Assertions.assertEquals("NOT (0 = 1)", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void visitWhere() {
|
|
||||||
var v = new QueryBuilder();
|
|
||||||
v.visitWhere(new WhereExpression(ConstantExpression.V0, ConstantExpression.V1));
|
|
||||||
Assertions.assertEquals("0 WHERE 1", v.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class TestClass extends SerializableObject {
|
|
||||||
public int i = 1;
|
|
||||||
public Object o = new Object();
|
|
||||||
public double d;
|
|
||||||
public float f;
|
|
||||||
public long l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package jef.util;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
class CheckTest {
|
|
||||||
@Test
|
|
||||||
void notNull_paramNotNull() {
|
|
||||||
//setup
|
|
||||||
var obj = new Object();
|
|
||||||
|
|
||||||
//test
|
|
||||||
var ret = Check.notNull(obj, "obj") ;
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertEquals(obj, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void notNull_paramNull() {
|
|
||||||
//setup
|
|
||||||
var varName = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
//test & assert
|
|
||||||
var ex = assertThrows(IllegalArgumentException.class, () -> Check.notNull(null, varName));
|
|
||||||
assertEquals(varName + " must be not null", ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package jef.util;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
class UtilTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void tryGet_NoException() {
|
|
||||||
//setup
|
|
||||||
var object = new Object();
|
|
||||||
|
|
||||||
//test
|
|
||||||
var ret = Util.tryGet(() -> object);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertTrue(ret.isPresent());
|
|
||||||
assertEquals(object, ret.orElseThrow());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void tryGet_Exception() {
|
|
||||||
//test
|
|
||||||
var ret = Util.tryGet(() -> {
|
|
||||||
throw new Exception();
|
|
||||||
});
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertFalse(ret.isPresent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>jef</artifactId>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<version>0.1</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>migration-creator</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<artifactId>core</artifactId>
|
|
||||||
<version>${project.parent.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<artifactId>core</artifactId>
|
|
||||||
<version>${project.parent.version}</version>
|
|
||||||
<type>test-jar</type>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
-127
@@ -1,127 +0,0 @@
|
|||||||
package jef.model.migration.creator;
|
|
||||||
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbEntity;
|
|
||||||
import jef.model.DbEntityBuilder;
|
|
||||||
import jef.model.DbField;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.annotations.Generated;
|
|
||||||
import jef.model.constraints.ForeignKeyConstraint;
|
|
||||||
import jef.model.constraints.IndexConstraint;
|
|
||||||
import jef.model.constraints.KeyConstraint;
|
|
||||||
import jef.model.constraints.PrimaryKeyConstraint;
|
|
||||||
import jef.model.constraints.UniqueKeyConstraint;
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ModelBuilderGenerator {
|
|
||||||
private final ModelBuilder mb;
|
|
||||||
private final String className;
|
|
||||||
private final String packageName;
|
|
||||||
private final SqlTypeMapper sqlTypeMapper;
|
|
||||||
|
|
||||||
private final Set<Class<?>> imports = new HashSet<>();
|
|
||||||
@Getter
|
|
||||||
private String java = null;
|
|
||||||
|
|
||||||
public ModelBuilderGenerator generate() {
|
|
||||||
if (java == null) {
|
|
||||||
java = generateModelBuilderJava();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateModelBuilderJava() {
|
|
||||||
var indent = " ";
|
|
||||||
imports.add(DbContext.class);
|
|
||||||
imports.add(ModelBuilder.class);
|
|
||||||
imports.add(DbEntityBuilder.class);
|
|
||||||
var java = ""
|
|
||||||
+ "public class " + className + " extends DbContext {\n"
|
|
||||||
+ " @Override\n"
|
|
||||||
+ " public void onModelCreate(ModelBuilder mb) {\n";
|
|
||||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
|
||||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
|
||||||
+ indent + " .name(\"" + entity.getName() + "\");\n";
|
|
||||||
for (DbField<?> field : entity.getFields()) {
|
|
||||||
if (field.getGenerated() != Generated.Type.NONE) {
|
|
||||||
imports.add(Generated.class);
|
|
||||||
}
|
|
||||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
|
||||||
+ indent + " .field(\"" + field.getName() + "\", \"" + field.getTypeName() + "\")"
|
|
||||||
+ "\n" + indent + " .sqlType(" + getSqlType(field) + ")"
|
|
||||||
+ (field.isNotNull() ? "\n" + indent + " .isNotNull()" : "")
|
|
||||||
+ (field.getGenerated() != Generated.Type.NONE ? "\n" + indent + " .generated(Generated.Type." + field.getGenerated() + ")" : "")
|
|
||||||
+ "\n" + indent + " .isDatabaseField(" + field.isDatabaseField() + ")"
|
|
||||||
+ "\n" + indent + " .isModelField(" + field.isModelField() + ");\n";
|
|
||||||
}
|
|
||||||
if (entity.getPrimaryKey() != null) {
|
|
||||||
imports.add(List.class);
|
|
||||||
imports.add(PrimaryKeyConstraint.class);
|
|
||||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
|
||||||
+ indent + " .hasOne(" + entity.getPrimaryKey().getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
|
||||||
+ indent + " .isPrimaryKey();\n";
|
|
||||||
}
|
|
||||||
java += "\n";
|
|
||||||
}
|
|
||||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
|
||||||
if (entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ForeignKeyConstraint foreignKey : entity.getForeignKeys()) {
|
|
||||||
imports.add(ForeignKeyConstraint.class);
|
|
||||||
//TODO hasOne/hasMany/withOne/WithMany
|
|
||||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
|
||||||
+ indent + " .hasOne(" + foreignKey.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", " + indent)) + ")\n"
|
|
||||||
+ indent + " .withOne(\"" + foreignKey.getReferencedEntity().getTypeName() + "\", " + foreignKey.getReferencedFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
|
||||||
+ indent + " .onUpdate(ForeignKeyConstraint.Action." + foreignKey.getOnUpdate().name() + ")\n"
|
|
||||||
+ indent + " .onDelete(ForeignKeyConstraint.Action." + foreignKey.getOnDelete().name() + ");\n";
|
|
||||||
}
|
|
||||||
for (UniqueKeyConstraint uniqueKey : entity.getUniqueKeys()) {
|
|
||||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
|
||||||
+ indent + " .hasOne(" + uniqueKey.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
|
||||||
+ indent + " .isUnique();\n";
|
|
||||||
}
|
|
||||||
for (KeyConstraint key : entity.getKeys()) {
|
|
||||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
|
||||||
+ indent + " .hasOne(" + key.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
|
||||||
+ indent + " .isKey();\n";
|
|
||||||
}
|
|
||||||
for (IndexConstraint index : entity.getIndexes()) {
|
|
||||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
|
||||||
+ indent + " .hasOne(" + index.getFields().stream().map(f -> "\"" + f.getName() + "\"").collect(Collectors.joining(", ")) + ")\n"
|
|
||||||
+ indent + " .isIndex();\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
java += " }\n"
|
|
||||||
+ "}\n";
|
|
||||||
|
|
||||||
//imports
|
|
||||||
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 normalImportsString = normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
|
|
||||||
var javaImportsString = javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
|
|
||||||
|
|
||||||
//finalize
|
|
||||||
java = (packageName != null ? "package " + packageName + ";\n\n" : "")
|
|
||||||
+ normalImportsString + (normalImportsString.length() > 0 ? "\n\n" : "")
|
|
||||||
+ javaImportsString + (javaImportsString.length() > 0 ? "\n\n" : "")
|
|
||||||
+ java;
|
|
||||||
return java;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getSqlType(DbField<?> f) {
|
|
||||||
return Optional.ofNullable(f.getSqlType()).or(() -> sqlTypeMapper.map(f.getTypeName())).map(e -> "\"" + e + "\"").orElse(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>jef</artifactId>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<version>0.1</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>mysql</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<artifactId>core</artifactId>
|
|
||||||
<version>${project.parent.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jef</groupId>
|
|
||||||
<artifactId>migration-creator</artifactId>
|
|
||||||
<version>${project.parent.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
@@ -1,333 +0,0 @@
|
|||||||
package jef.platform.mysql;
|
|
||||||
|
|
||||||
import jef.MigrationException;
|
|
||||||
import jef.expressions.Expression;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbField;
|
|
||||||
import jef.model.DbFieldBuilder;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.platform.base.Database;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.platform.mysql.query.MysqlQueryBuilder;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.util.Util;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.sql.Types;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class MysqlDatabase extends Database {
|
|
||||||
private final MysqlPlatform platform;
|
|
||||||
|
|
||||||
public MysqlDatabase(Connection connection, DatabaseOptions options, MysqlPlatform platform) {
|
|
||||||
super(connection, options);
|
|
||||||
this.platform = platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void migrate() throws MigrationException {
|
|
||||||
platform.getMigrationApplier(connection, options).migrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> List<T> queryExpression(DbContext context, Class<T> clazz, Expression expression) throws SQLException {
|
|
||||||
MysqlQueryBuilder queryBuilder = new MysqlQueryBuilder();
|
|
||||||
queryBuilder.visit(expression);
|
|
||||||
return queryRaw(context, clazz, queryBuilder.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> void add(DbContext context, Class<T> clazz, T model) throws SQLException {
|
|
||||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
|
||||||
var entity = modelBuilder.entity(clazz);
|
|
||||||
|
|
||||||
var columns = new ArrayList<String>();
|
|
||||||
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
|
|
||||||
|
|
||||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
|
||||||
Field f = field.getField().getField();
|
|
||||||
if (f == null) {
|
|
||||||
if (field.getField().isDatabaseField()) {
|
|
||||||
columns.add(field.getField().getName());
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!field.getField().isDatabaseField()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.setAccessible(true);
|
|
||||||
|
|
||||||
columns.add(field.getField().getName());
|
|
||||||
try {
|
|
||||||
if (f.get(model) == null) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
|
||||||
} else if (field.getField().getType() == String.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
|
||||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
|
||||||
throw new UnsupportedOperationException("char");
|
|
||||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var columnsString = columns.stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
|
|
||||||
var placeholdersString = columns.stream().map(e -> "?").collect(Collectors.joining(","));
|
|
||||||
var query = "INSERT INTO `" + entity.name() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
|
|
||||||
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
|
|
||||||
var i = 1;
|
|
||||||
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
|
|
||||||
try {
|
|
||||||
valueBinder.accept(stmt, i++);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
if (e instanceof SQLException) {
|
|
||||||
throw (SQLException) e;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("This may not happen", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var added = stmt.executeUpdate();
|
|
||||||
if (added != 1) {
|
|
||||||
throw new SQLException("Failed to insert entity");
|
|
||||||
}
|
|
||||||
try (var res = stmt.getGeneratedKeys()) {
|
|
||||||
var primary = entity.getEntity().getPrimaryKey();
|
|
||||||
if (primary != null) {
|
|
||||||
if (!res.next()) {
|
|
||||||
throw new SQLException("Failed to add entity");
|
|
||||||
}
|
|
||||||
var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
|
|
||||||
idField.getField().setAccessible(true);
|
|
||||||
if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
|
|
||||||
idField.getField().set(model, res.getInt(1));
|
|
||||||
} else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
|
|
||||||
idField.getField().set(model, res.getLong(1));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> void update(DbContext context, Class<T> clazz, T model) throws SQLException {
|
|
||||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
|
||||||
var entity = modelBuilder.entity(clazz);
|
|
||||||
if (entity.getEntity().getPrimaryKey() == null) {
|
|
||||||
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
|
|
||||||
}
|
|
||||||
|
|
||||||
var columns = new ArrayList<String>();
|
|
||||||
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
|
|
||||||
|
|
||||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
|
||||||
Field f = field.getField().getField();
|
|
||||||
if (f == null) {
|
|
||||||
if (field.getField().isDatabaseField()) {
|
|
||||||
columns.add(field.getField().getName());
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));//TODO set proper type
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!field.getField().isDatabaseField()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.setAccessible(true);
|
|
||||||
|
|
||||||
columns.add(field.getField().getName());
|
|
||||||
try {
|
|
||||||
if (f.get(model) == null) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
|
||||||
} else if (field.getField().getType() == String.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
|
||||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
|
||||||
throw new UnsupportedOperationException("char");
|
|
||||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var updatesString = columns.stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(", "));
|
|
||||||
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
|
|
||||||
|
|
||||||
//add values for where clause
|
|
||||||
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
|
|
||||||
Field f = field.getField();
|
|
||||||
f.setAccessible(true);
|
|
||||||
try {
|
|
||||||
if (f.get(model) == null) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
|
||||||
} else if (field.getField().getType() == String.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
|
||||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
|
||||||
throw new UnsupportedOperationException("char");
|
|
||||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = "UPDATE `" + entity.name() + "` SET " + updatesString + " WHERE " + whereString;
|
|
||||||
System.out.println(query);
|
|
||||||
try (var stmt = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
|
|
||||||
var i = 1;
|
|
||||||
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
|
|
||||||
try {
|
|
||||||
valueBinder.accept(stmt, i++);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
if (e instanceof SQLException) {
|
|
||||||
throw (SQLException) e;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("This may not happen", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var updated = stmt.executeUpdate();
|
|
||||||
if (updated != 1) {
|
|
||||||
throw new SQLException("Failed to update entity");
|
|
||||||
}
|
|
||||||
try (var res = stmt.getGeneratedKeys()) {
|
|
||||||
// var primary = entity.getEntity().getPrimaryKey();
|
|
||||||
// if (primary != null) {
|
|
||||||
// if (!res.next()) {
|
|
||||||
// throw new SQLException("Failed to add entity");
|
|
||||||
// }
|
|
||||||
// var idField = primary.getFields().get(0);//TODO this only supports primary keys, and only with one column
|
|
||||||
// idField.getField().setAccessible(true);
|
|
||||||
// if (idField.getField().getType() == int.class || idField.getField().getType() == Integer.class) {//TODO support more types
|
|
||||||
// idField.getField().set(model, res.getInt(1));
|
|
||||||
// } else if (idField.getField().getType() == long.class || idField.getField().getType() == Long.class) {
|
|
||||||
// idField.getField().set(model, res.getLong(1));
|
|
||||||
// } else {
|
|
||||||
// throw new UnsupportedOperationException();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (IllegalAccessException e) {
|
|
||||||
// throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends SerializableObject> void delete(DbContext context, Class<T> clazz, T model) throws SQLException {
|
|
||||||
var modelBuilder = ModelBuilder.from(context.getClass());
|
|
||||||
var entity = modelBuilder.entity(clazz);
|
|
||||||
if (entity.getEntity().getPrimaryKey() == null) {
|
|
||||||
throw new UnsupportedOperationException("Primary key required for updates");//at least for now
|
|
||||||
}
|
|
||||||
|
|
||||||
var valueBinders = new ArrayList<Util.ThrowableBiConsumer<PreparedStatement, Integer>>();
|
|
||||||
var whereString = entity.getEntity().getPrimaryKey().getFields().stream().map(e -> "`" + e.getName() + "` = ?").collect(Collectors.joining(" AND "));
|
|
||||||
|
|
||||||
//add values for where clause
|
|
||||||
for (DbField<?> field : entity.getEntity().getPrimaryKey().getFields()) {
|
|
||||||
Field f = field.getField();
|
|
||||||
f.setAccessible(true);
|
|
||||||
try {
|
|
||||||
if (f.get(model) == null) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setNull(i, Types.INTEGER));
|
|
||||||
} else if (field.getField().getType() == String.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setString(i, (String) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == byte.class || field.getField().getType() == Byte.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setByte(i, (byte) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == char.class || field.getField().getType() == Character.class) {
|
|
||||||
// valueSetters.add((stmt, i) -> stmt.setString(i, (char) f.get(model)));//TODO handle char
|
|
||||||
throw new UnsupportedOperationException("char");
|
|
||||||
} else if (field.getField().getType() == short.class || field.getField().getType() == Short.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setShort(i, (short) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == int.class || field.getField().getType() == Integer.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setInt(i, (int) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == long.class || field.getField().getType() == Long.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setLong(i, (long) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == float.class || field.getField().getType() == Float.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setFloat(i, (float) f.get(model)));
|
|
||||||
} else if (field.getField().getType() == double.class || field.getField().getType() == Double.class) {
|
|
||||||
valueBinders.add((stmt, i) -> stmt.setDouble(i, (double) f.get(model)));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = "DELETE FROM `" + entity.name() + "` WHERE " + whereString;
|
|
||||||
System.out.println(query);
|
|
||||||
try (var stmt = connection.prepareStatement(query)) {
|
|
||||||
var i = 1;
|
|
||||||
for (Util.ThrowableBiConsumer<PreparedStatement, Integer> valueBinder : valueBinders) {
|
|
||||||
try {
|
|
||||||
valueBinder.accept(stmt, i++);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
if (e instanceof SQLException) {
|
|
||||||
throw (SQLException) e;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("This may not happen", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var deleted = stmt.executeUpdate();
|
|
||||||
if (deleted != 1) {
|
|
||||||
throw new SQLException("Failed to delete entity");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package jef.platform.mysql;
|
|
||||||
|
|
||||||
import jef.platform.SqlPlatform;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
import jef.platform.base.migration.MigrationApplier;
|
|
||||||
import jef.platform.mysql.migration.MysqlMigrationApplier;
|
|
||||||
import jef.platform.mysql.migration.MysqlMigrationOperationTranslator;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
|
|
||||||
public class MysqlPlatform implements SqlPlatform {
|
|
||||||
@Override
|
|
||||||
public MysqlMigrationOperationTranslator getTranslator() {
|
|
||||||
return new MysqlMigrationOperationTranslator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SqlTypeMapper getTypeMapper() {
|
|
||||||
return new MysqlTypeMapper();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MigrationApplier getMigrationApplier(Connection connection, DatabaseOptions options) {
|
|
||||||
return new MysqlMigrationApplier(connection, options, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package jef.platform.mysql;
|
|
||||||
|
|
||||||
import jef.platform.base.SqlTypeMapper;
|
|
||||||
|
|
||||||
public class MysqlTypeMapper extends SqlTypeMapper {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,445 +0,0 @@
|
|||||||
package jef.platform.mysql.dbinterface;
|
|
||||||
|
|
||||||
import jef.asm.access.model.ClassDescription;
|
|
||||||
import jef.asm.access.model.GetterDescription;
|
|
||||||
import jef.asm.access.model.SetterDescription;
|
|
||||||
import jef.model.DbField;
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
|
||||||
import org.objectweb.asm.Label;
|
|
||||||
import org.objectweb.asm.MethodVisitor;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Types;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public abstract class ByteCodeHelper {
|
|
||||||
public final static Type T_BOOL = Type.getType(boolean.class);
|
|
||||||
public final static Type T_BYTE = Type.getType(byte.class);
|
|
||||||
public final static Type T_CHAR = Type.getType(char.class);
|
|
||||||
public final static Type T_CLASS = Type.getType(Class.class);
|
|
||||||
public final static Type T_CONNECTION = Type.getType(Connection.class);
|
|
||||||
public final static Type T_DOUBLE = Type.getType(double.class);
|
|
||||||
public final static Type T_FIELD = Type.getType(Field.class);
|
|
||||||
public final static Type T_FLOAT = Type.getType(float.class);
|
|
||||||
public final static Type T_INT = Type.getType(int.class);
|
|
||||||
public final static Type T_LONG = Type.getType(long.class);
|
|
||||||
public final static Type T_MYSQL_ENTITY_DB_INTERFACE = Type.getType(MysqlEntityDbInterface.class);
|
|
||||||
public final static Type T_OBJECT = Type.getType(Object.class);
|
|
||||||
public final static Type T_OVERRIDE = Type.getType(Override.class);
|
|
||||||
public final static Type T_PREPARED_STATEMENT = Type.getType(PreparedStatement.class);
|
|
||||||
public final static Type T_RESULT_SET = Type.getType(ResultSet.class);
|
|
||||||
public final static Type T_SHORT = Type.getType(short.class);
|
|
||||||
public final static Type T_SQLEXCEPTION = Type.getType(SQLException.class);
|
|
||||||
public final static Type T_STRING = Type.getType(String.class);
|
|
||||||
public final static Type T_VOID = Type.getType(void.class);
|
|
||||||
|
|
||||||
//region getter
|
|
||||||
private static void visitGetFieldDirectAccess(MethodVisitor mv, Field field, int varIndexEntity) {
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
|
||||||
mv.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(), Type.getInternalName(field.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void visitGetter(MethodVisitor mv, GetterDescription getter, int varIndexEntity) {
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
|
||||||
getter.getMethodDeclaringClassName().replace(".", "/"),
|
|
||||||
getter.getMethodName(),
|
|
||||||
"(" + Arrays.stream(getter.getMethodParameterClassNames())
|
|
||||||
.map(ByteCodeHelper::getDescriptorForClassName)
|
|
||||||
.collect(Collectors.joining())
|
|
||||||
+ ")" + getDescriptorForClassName(getter.getMethodReturnClassName()),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void visitGetViaReflection(MethodVisitor mv, Field field, int varIndexEntity, String className, Runnable defineFieldCallback) {
|
|
||||||
defineFieldCallback.run();
|
|
||||||
|
|
||||||
// field.get*(entity, value);
|
|
||||||
mv.visitFieldInsn(Opcodes.GETSTATIC, className.replace(".", "/"), makeReflectedFieldName(field), Type.getDescriptor(field.getType()));
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
|
||||||
var clazz = field.getType();
|
|
||||||
if (clazz == boolean.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getBoolean", Type.getMethodDescriptor(T_BOOL, T_OBJECT), false);
|
|
||||||
} else if (clazz == byte.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getByte", Type.getMethodDescriptor(T_BYTE, T_OBJECT), false);
|
|
||||||
} else if (clazz == char.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getCharacter", Type.getMethodDescriptor(T_CHAR, T_OBJECT), false);
|
|
||||||
} else if (clazz == short.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getShort", Type.getMethodDescriptor(T_SHORT, T_OBJECT), false);
|
|
||||||
} else if (clazz == int.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getInt", Type.getMethodDescriptor(T_INT, T_OBJECT), false);
|
|
||||||
} else if (clazz == long.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getLong", Type.getMethodDescriptor(T_LONG, T_OBJECT), false);
|
|
||||||
} else if (clazz == float.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getFloat", Type.getMethodDescriptor(T_FLOAT, T_OBJECT), false);
|
|
||||||
} else if (clazz == double.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "getDouble", Type.getMethodDescriptor(T_DOUBLE, T_OBJECT), false);
|
|
||||||
} else {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "get", Type.getMethodDescriptor(T_OBJECT, T_OBJECT), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void visitGetField(MethodVisitor mv, Field field, int varIndexEntity, String className, Runnable defineFieldCallback, ClassDescription classDescription) {
|
|
||||||
//try direct member access
|
|
||||||
if (Modifier.isPublic(field.getModifiers())) {//TODO better check if publicly accessible
|
|
||||||
visitGetFieldDirectAccess(mv, field, varIndexEntity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//try finding getter
|
|
||||||
Optional<GetterDescription> getter = classDescription.getDeclaredGetters().stream()
|
|
||||||
.filter(e -> e.getFieldDeclaringClassName().equals(field.getDeclaringClass().getName())
|
|
||||||
&& e.getFieldName().equals(field.getName()))
|
|
||||||
.findFirst();
|
|
||||||
if (getter.isPresent()) {
|
|
||||||
visitGetter(mv, getter.get(), varIndexEntity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//use reflection
|
|
||||||
visitGetViaReflection(mv, field, varIndexEntity, className, defineFieldCallback);
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region setter
|
|
||||||
private static void visitSetFieldDirectAccess(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity) {
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
|
||||||
value.accept(mv);
|
|
||||||
mv.visitFieldInsn(Opcodes.PUTFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(), Type.getInternalName(field.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void visitSetter(MethodVisitor mv, SetterDescription setter, Consumer<MethodVisitor> value, int varIndexEntity) {
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
|
||||||
value.accept(mv);
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
|
||||||
setter.getMethodDeclaringClassName().replace(".", "/"),
|
|
||||||
setter.getMethodName(),
|
|
||||||
"(" + Arrays.stream(setter.getMethodParameterClassNames())
|
|
||||||
.map(ByteCodeHelper::getDescriptorForClassName)
|
|
||||||
.collect(Collectors.joining())
|
|
||||||
+ ")" + getDescriptorForClassName(setter.getMethodReturnClassName()),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void visitSetViaReflection(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, String className, Runnable defineFieldCallback) {
|
|
||||||
defineFieldCallback.run();
|
|
||||||
|
|
||||||
// field.set*(entity, value);
|
|
||||||
mv.visitFieldInsn(Opcodes.GETSTATIC, className.replace(".", "/"), makeReflectedFieldName(field), Type.getDescriptor(field.getType()));
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexEntity);
|
|
||||||
value.accept(mv);
|
|
||||||
var clazz = field.getType();
|
|
||||||
if (clazz == boolean.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setBoolean", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_BOOL), false);
|
|
||||||
} else if (clazz == byte.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setByte", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_BYTE), true);
|
|
||||||
} else if (clazz == char.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setCharacter", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_CHAR), true);
|
|
||||||
} else if (clazz == short.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setShort", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_SHORT), true);
|
|
||||||
} else if (clazz == int.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setInt", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_INT), true);
|
|
||||||
} else if (clazz == long.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setLong", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_LONG), true);
|
|
||||||
} else if (clazz == float.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setFloat", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_FLOAT), true);
|
|
||||||
} else if (clazz == double.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setDouble", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_DOUBLE), true);
|
|
||||||
} else {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "set", Type.getMethodDescriptor(T_VOID, T_OBJECT, T_OBJECT), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void visitSetField(MethodVisitor mv, Field field, Consumer<MethodVisitor> value, int varIndexEntity, String className, Runnable defineFieldCallback, ClassDescription classDescription) {
|
|
||||||
//try direct member access
|
|
||||||
if (Modifier.isPublic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {//TODO better check if publicly accessible
|
|
||||||
visitSetFieldDirectAccess(mv, field, value, varIndexEntity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//try finding getter
|
|
||||||
Optional<SetterDescription> setter = classDescription.getDeclaredSetters().stream()
|
|
||||||
.filter(e -> e.getFieldDeclaringClassName().equals(field.getDeclaringClass().getName())
|
|
||||||
&& e.getFieldName().equals(field.getName()))
|
|
||||||
.findFirst();
|
|
||||||
if (setter.isPresent()) {
|
|
||||||
visitSetter(mv, setter.get(), value, varIndexEntity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//use reflection
|
|
||||||
visitSetViaReflection(mv, field, value, varIndexEntity, className, defineFieldCallback);
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region PrepareStatement
|
|
||||||
static void visitPreparedStatementSetNull(MethodVisitor mv, DbField<?> field, int varIndexStmt, int index) {
|
|
||||||
//stmt.setNull(i, Types.*);
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexStmt);//push stmt
|
|
||||||
visitIntInsn(mv, index);//push index
|
|
||||||
var clazz = field.getType();
|
|
||||||
if (clazz == String.class) {
|
|
||||||
visitIntInsn(mv, Types.VARCHAR);//push Types.VARCHAR
|
|
||||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
|
||||||
visitIntInsn(mv, Types.BIT);//push Types.BIT
|
|
||||||
} else if (clazz == byte.class || clazz == Byte.class) {
|
|
||||||
visitIntInsn(mv, Types.TINYINT);//push Types.TINYINT
|
|
||||||
} else if (clazz == char.class || clazz == Character.class) {
|
|
||||||
visitIntInsn(mv, Types.CHAR);//push Types.CHAR
|
|
||||||
} else if (clazz == short.class || clazz == Short.class) {
|
|
||||||
visitIntInsn(mv, Types.SMALLINT);//push Types.SMALLINT
|
|
||||||
} else if (clazz == int.class || clazz == Integer.class) {
|
|
||||||
visitIntInsn(mv, Types.INTEGER);//push Types.INTEGER
|
|
||||||
} else if (clazz == long.class || clazz == Long.class) {
|
|
||||||
visitIntInsn(mv, Types.BIGINT);//push Types.BIGINT
|
|
||||||
} else if (clazz == float.class || clazz == Float.class) {
|
|
||||||
visitIntInsn(mv, Types.FLOAT);//push Types.FLOAT
|
|
||||||
} else if (clazz == double.class || clazz == Double.class) {
|
|
||||||
visitIntInsn(mv, Types.DOUBLE);//push Types.DOUBLE
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException(clazz.getName());
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_PREPARED_STATEMENT.getInternalName(), "setNull", "(II)V", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void visitPreparedStatementSetAny(MethodVisitor mv, DbField<?> field, int varIndexStmt, int index, Consumer<MethodVisitor> value) {
|
|
||||||
var clazz = field.getType();
|
|
||||||
//stmt.set*(i, value);
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexStmt);//push stmt
|
|
||||||
visitIntInsn(mv, index);//push index
|
|
||||||
value.accept(mv);
|
|
||||||
if (clazz == String.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setString", "(ILjava.lang.String;)V", true);
|
|
||||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
|
||||||
if (clazz == Boolean.class) {//unbox
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_BOOL.getInternalName(), "booleanValue", "()Z", true);
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setBoolean", "(IZ)V", true);
|
|
||||||
} else if (clazz == byte.class || clazz == Byte.class) {
|
|
||||||
if (clazz == Byte.class) {//unbox
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_BYTE.getInternalName(), "byteValue", "()B", true);
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setByte", "(IB)V", true);
|
|
||||||
} else if (clazz == char.class || clazz == Character.class) {
|
|
||||||
// if (clazz == Character.class) {//unbox
|
|
||||||
// mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CHAR.getInternalName(), "charValue", "()C", true);
|
|
||||||
// }
|
|
||||||
// mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setCharacter", "(IC)V", true);
|
|
||||||
throw new UnsupportedOperationException("char");
|
|
||||||
} else if (clazz == short.class || clazz == Short.class) {
|
|
||||||
if (clazz == Short.class) {//unbox
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_SHORT.getInternalName(), "shortValue", "()S", true);
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setShort", "(IS)V", true);
|
|
||||||
} else if (clazz == int.class || clazz == Integer.class) {
|
|
||||||
if (clazz == Integer.class) {//unbox
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_INT.getInternalName(), "intValue", "()I", true);
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setInt", "(II)V", true);
|
|
||||||
} else if (clazz == long.class || clazz == Long.class) {
|
|
||||||
if (clazz == Long.class) {//unbox
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_LONG.getInternalName(), "longValue", "()J", true);
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setLong", "(IJ)V", true);
|
|
||||||
} else if (clazz == float.class || clazz == Float.class) {
|
|
||||||
if (clazz == Float.class) {//unbox
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FLOAT.getInternalName(), "floatValue", "()F", true);
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setFloat", "(IF)V", true);
|
|
||||||
} else if (clazz == double.class || clazz == Double.class) {
|
|
||||||
if (clazz == Double.class) {//unbox
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_DOUBLE.getInternalName(), "doubleValue", "()D", true);
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_PREPARED_STATEMENT.getInternalName(), "setDouble", "(ID)V", true);
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException(clazz.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region ResultSet
|
|
||||||
static void visitResultSetGetAny(MethodVisitor mv, DbField<?> field, int varIndexRes) {
|
|
||||||
var clazz = field.getType();
|
|
||||||
// res.get*(fieldname);
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);//push res
|
|
||||||
mv.visitLdcInsn(field.getName());//push 1
|
|
||||||
if (clazz == String.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getString", "(Ljava.lang.String;)Ljava.lang.String;", true);
|
|
||||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getBoolean", "(Ljava.lang.String;)Z", true);
|
|
||||||
if (clazz == Boolean.class) {//box
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_BOOL.getInternalName(), "<init>", "(Z)V", true);
|
|
||||||
}
|
|
||||||
} else if (clazz == byte.class || clazz == Byte.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getByte", "(Ljava.lang.String;)B", true);
|
|
||||||
if (clazz == Byte.class) {//box
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_BYTE.getInternalName(), "<init>", "(B)V", true);
|
|
||||||
}
|
|
||||||
} else if (clazz == char.class || clazz == Character.class) {
|
|
||||||
// mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getCharacter", "(Ljava.lang.String;)C", true);
|
|
||||||
// if (clazz == Character.class) {//unbox
|
|
||||||
// mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_CHAR.getInternalName(), "<init>", "(C)Ljava.lang.Character;", true);
|
|
||||||
// }
|
|
||||||
throw new UnsupportedOperationException("char");
|
|
||||||
} else if (clazz == short.class || clazz == Short.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getShort", "(Ljava.lang.String;)S", true);
|
|
||||||
if (clazz == Short.class) {//box
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_SHORT.getInternalName(), "<init>", "(S)V", true);
|
|
||||||
}
|
|
||||||
} else if (clazz == int.class || clazz == Integer.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getInt", "(Ljava.lang.String;)I", true);
|
|
||||||
if (clazz == Integer.class) {//box
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_INT.getInternalName(), "<init>", "(I)V", true);
|
|
||||||
}
|
|
||||||
} else if (clazz == long.class || clazz == Long.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getLong", "(Ljava.lang.String;)J", true);
|
|
||||||
if (clazz == Long.class) {//box
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_LONG.getInternalName(), "<init>", "(J)V", true);
|
|
||||||
}
|
|
||||||
} else if (clazz == float.class || clazz == Float.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getFloat", "(Ljava.lang.String;)F", true);
|
|
||||||
if (clazz == Float.class) {//box
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_FLOAT.getInternalName(), "<init>", "(F)V", true);
|
|
||||||
}
|
|
||||||
} else if (clazz == double.class || clazz == Double.class) {
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "getDouble", "(Ljava.lang.String;)D", true);
|
|
||||||
if (clazz == Double.class) {//box
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_DOUBLE.getInternalName(), "<init>", "(D)V", true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException(clazz.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (res.wasNull()) {
|
|
||||||
// POP
|
|
||||||
// PUSH NULL
|
|
||||||
// }
|
|
||||||
if (!clazz.isPrimitive()) {
|
|
||||||
var labelWasNotNull = new Label();
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);//push res
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, T_RESULT_SET.getInternalName(), "wasNull", "()Z", true);
|
|
||||||
mv.visitJumpInsn(Opcodes.IFEQ, labelWasNotNull);
|
|
||||||
mv.visitInsn(Opcodes.POP);//POP
|
|
||||||
mv.visitInsn(Opcodes.ACONST_NULL);//PUSH NULL
|
|
||||||
mv.visitLabel(labelWasNotNull);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region util
|
|
||||||
static void visitIntInsn(MethodVisitor mv, int value) {
|
|
||||||
switch (value) {
|
|
||||||
case -1 -> mv.visitInsn(Opcodes.ICONST_M1);
|
|
||||||
case 0 -> mv.visitInsn(Opcodes.ICONST_0);
|
|
||||||
case 1 -> mv.visitInsn(Opcodes.ICONST_1);
|
|
||||||
case 2 -> mv.visitInsn(Opcodes.ICONST_2);
|
|
||||||
case 3 -> mv.visitInsn(Opcodes.ICONST_3);
|
|
||||||
case 4 -> mv.visitInsn(Opcodes.ICONST_4);
|
|
||||||
case 5 -> mv.visitInsn(Opcodes.ICONST_5);
|
|
||||||
default -> {
|
|
||||||
if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
|
|
||||||
mv.visitIntInsn(Opcodes.BIPUSH, value);
|
|
||||||
} else if (Short.MIN_VALUE <= value && value <= Short.MAX_VALUE) {
|
|
||||||
mv.visitIntInsn(Opcodes.SIPUSH, value);
|
|
||||||
} else {
|
|
||||||
mv.visitLdcInsn(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void visitThrowSQLException(MethodVisitor mv, String message) {
|
|
||||||
mv.visitTypeInsn(Opcodes.NEW, T_SQLEXCEPTION.getInternalName());
|
|
||||||
mv.visitInsn(Opcodes.DUP);
|
|
||||||
mv.visitLdcInsn(message);
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, T_SQLEXCEPTION.getInternalName(), "<init>", Type.getMethodDescriptor(T_VOID, T_STRING), false);
|
|
||||||
mv.visitInsn(Opcodes.ATHROW);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void visitPushDefault(MethodVisitor mv, Class<?> type) {
|
|
||||||
if (Set.of(void.class, boolean.class, byte.class, char.class, short.class, int.class, float.class).contains(type)) {
|
|
||||||
mv.visitInsn(Opcodes.ICONST_0);
|
|
||||||
} else if (Set.of(double.class, long.class).contains(type)) {
|
|
||||||
mv.visitInsn(Opcodes.ICONST_0);
|
|
||||||
mv.visitInsn(Opcodes.ICONST_0);
|
|
||||||
} else {
|
|
||||||
mv.visitInsn(Opcodes.ACONST_NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getDescriptorForClassName(String className) {
|
|
||||||
return switch (className) {
|
|
||||||
case "void" -> "V";
|
|
||||||
case "boolean" -> "Z";
|
|
||||||
case "byte" -> "B";
|
|
||||||
case "char" -> "C";
|
|
||||||
case "short" -> "S";
|
|
||||||
case "int" -> "I";
|
|
||||||
case "long" -> "J";
|
|
||||||
case "float" -> "F";
|
|
||||||
case "double" -> "D";
|
|
||||||
default -> "L" + className.replace(".", "/") + ";";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region field reflection
|
|
||||||
public static void visitReflectedFieldDefinition(ClassVisitor visitor, Field field, boolean synthetic) {
|
|
||||||
visitor.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | (synthetic ? Opcodes.ACC_SYNTHETIC : 0),
|
|
||||||
makeReflectedFieldName(field), T_FIELD.getDescriptor(), null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void visitReflectedFieldInit(MethodVisitor mv, Field field, String className) {
|
|
||||||
className = className.replace(".", "/");
|
|
||||||
var fieldname = makeReflectedFieldName(field);
|
|
||||||
|
|
||||||
// field = EntityClass.class.getDeclaredField("fieldname");
|
|
||||||
mv.visitLdcInsn(Type.getObjectType(field.getDeclaringClass().getName()));
|
|
||||||
mv.visitLdcInsn(field.getName());
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_CLASS.getInternalName(), "getDeclaredField", Type.getMethodDescriptor(T_FIELD, T_STRING), false);
|
|
||||||
mv.visitFieldInsn(Opcodes.PUTSTATIC, className, fieldname, T_FIELD.getDescriptor());
|
|
||||||
|
|
||||||
// field.setAccessible(true);
|
|
||||||
mv.visitFieldInsn(Opcodes.GETSTATIC, className, fieldname, Type.getDescriptor(field.getType()));
|
|
||||||
mv.visitInsn(Opcodes.ICONST_1);
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, T_FIELD.getInternalName(), "setAccessible", Type.getMethodDescriptor(T_VOID, T_BOOL), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String makeReflectedFieldName(Field field) {
|
|
||||||
return field.getDeclaringClass().getName().replace(".", "_") + "_" + field.getName();
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region var access
|
|
||||||
// private void visitLoadVariable(MethodVisitor mv, String className, int varIndex) {
|
|
||||||
// switch (className) {
|
|
||||||
// case "boolean", "byte", "char", "short", "int" -> mv.visitVarInsn(Opcodes.ILOAD, varIndex);
|
|
||||||
// case "long" -> mv.visitVarInsn(Opcodes.LLOAD, varIndex);
|
|
||||||
// case "float" -> mv.visitVarInsn(Opcodes.FLOAD, varIndex);
|
|
||||||
// case "double" -> mv.visitVarInsn(Opcodes.DLOAD, varIndex);
|
|
||||||
// default -> mv.visitVarInsn(Opcodes.ALOAD, varIndex);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void visitStoreVariable(MethodVisitor mv, String className, int varIndex) {
|
|
||||||
// switch (className) {
|
|
||||||
// case "boolean", "byte", "char", "short", "int" -> mv.visitVarInsn(Opcodes.ISTORE, varIndex);
|
|
||||||
// case "long" -> mv.visitVarInsn(Opcodes.LSTORE, varIndex);
|
|
||||||
// case "float" -> mv.visitVarInsn(Opcodes.FSTORE, varIndex);
|
|
||||||
// case "double" -> mv.visitVarInsn(Opcodes.DSTORE, varIndex);
|
|
||||||
// default -> mv.visitVarInsn(Opcodes.ASTORE, varIndex);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//endregion
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package jef.platform.mysql.dbinterface;
|
|
||||||
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
|
|
||||||
import javax.xml.transform.Result;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Types;
|
|
||||||
|
|
||||||
public abstract class MysqlEntityDbInterface<T extends SerializableObject> {
|
|
||||||
public void add(Connection connection, T entity) throws SQLException {
|
|
||||||
try (var stmt = queryAdd(connection)) {
|
|
||||||
bindParamsAdd(stmt, entity);
|
|
||||||
|
|
||||||
var added = stmt.executeUpdate();
|
|
||||||
if (added != 1) {
|
|
||||||
throw new SQLException("Failed to insert entity");
|
|
||||||
}
|
|
||||||
|
|
||||||
readBackGeneratedValuesAdd(stmt, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract PreparedStatement queryAdd(Connection connection) throws SQLException;
|
|
||||||
|
|
||||||
protected abstract void bindParamsAdd(PreparedStatement stmt, T entity) throws SQLException;
|
|
||||||
|
|
||||||
protected void readBackGeneratedValuesAdd(PreparedStatement stmt, T entity) throws SQLException {
|
|
||||||
try (var res = stmt.getGeneratedKeys()) {
|
|
||||||
readBackGeneratedValuesAdd(res, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void readBackGeneratedValuesAdd(ResultSet res, T entity) throws SQLException;
|
|
||||||
|
|
||||||
public void update(Connection connection, T entity) throws SQLException {
|
|
||||||
try (var stmt = queryUpdate(connection)) {
|
|
||||||
bindParamsUpdate(stmt, entity);
|
|
||||||
|
|
||||||
var updated = stmt.executeUpdate();
|
|
||||||
if (updated != 1) {
|
|
||||||
throw new SQLException("Failed to update entity");
|
|
||||||
}
|
|
||||||
|
|
||||||
readBackGeneratedValuesUpdate(stmt, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract PreparedStatement queryUpdate(Connection connection) throws SQLException;
|
|
||||||
|
|
||||||
protected abstract void bindParamsUpdate(PreparedStatement stmt, T entity) throws SQLException;
|
|
||||||
|
|
||||||
protected void readBackGeneratedValuesUpdate(PreparedStatement stmt, T entity) throws SQLException {
|
|
||||||
try (var res = stmt.getGeneratedKeys()) {
|
|
||||||
readBackGeneratedValuesUpdate(res, entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void readBackGeneratedValuesUpdate(ResultSet res, T entity) throws SQLException;
|
|
||||||
|
|
||||||
public void delete(Connection connection, T entity) throws SQLException {
|
|
||||||
try (var stmt = queryUpdate(connection)) {
|
|
||||||
bindParamsDelete(stmt, entity);
|
|
||||||
|
|
||||||
var deleted = stmt.executeUpdate();
|
|
||||||
if (deleted != 1) {
|
|
||||||
throw new SQLException("Failed to delete entity");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected abstract void bindParamsDelete(PreparedStatement stmt, T entity) throws SQLException;
|
|
||||||
|
|
||||||
public abstract T read(ResultSet res);
|
|
||||||
}
|
|
||||||
-791
@@ -1,791 +0,0 @@
|
|||||||
package jef.platform.mysql.dbinterface;
|
|
||||||
|
|
||||||
import jef.asm.access.ClassAnalyzer;
|
|
||||||
import jef.asm.access.model.ClassDescription;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbEntity;
|
|
||||||
import jef.model.DbField;
|
|
||||||
import jef.model.annotations.Generated;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
|
||||||
import org.objectweb.asm.ClassWriter;
|
|
||||||
import org.objectweb.asm.Label;
|
|
||||||
import org.objectweb.asm.MethodVisitor;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class MysqlEntityDbInterfaceGenerator<T extends SerializableObject> {
|
|
||||||
private static final boolean SET_SYNTHETIC_FLAG = false;
|
|
||||||
private static final int SYNTHETIC_FLAG = SET_SYNTHETIC_FLAG ? Opcodes.ACC_SYNTHETIC : 0;
|
|
||||||
|
|
||||||
private final Class<? extends DbContext> context;
|
|
||||||
private final DbEntity<T> entity;
|
|
||||||
|
|
||||||
private final ClassWriter writer;
|
|
||||||
private final ClassVisitor visitor;
|
|
||||||
|
|
||||||
//runtime
|
|
||||||
private ClassDescription classDescription;
|
|
||||||
private final Set<Field> fieldsToReflect = new HashSet<>();
|
|
||||||
|
|
||||||
//results
|
|
||||||
@Getter
|
|
||||||
private String className;
|
|
||||||
@Getter
|
|
||||||
private byte[] byteCode;
|
|
||||||
|
|
||||||
public MysqlEntityDbInterfaceGenerator(Class<? extends DbContext> context, DbEntity<T> entity) {
|
|
||||||
this.context = context;
|
|
||||||
this.entity = entity;
|
|
||||||
writer = new ClassWriter(Opcodes.ASM9);
|
|
||||||
visitor = new ClassVisitor(Opcodes.ASM9, writer) {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void generate() {
|
|
||||||
classDescription = new ClassAnalyzer().analyze(entity.getType());
|
|
||||||
|
|
||||||
//follows template "<context full class name>$<entity classname>MysqlEntityDbInterface"
|
|
||||||
// className = context.getName() + "$" + entity.getType().getSimpleName() + "" + MysqlEntityDbInterface.class.getSimpleName();
|
|
||||||
className = entity.getType().getSimpleName() + "" + MysqlEntityDbInterface.class.getSimpleName();
|
|
||||||
|
|
||||||
defineClass();
|
|
||||||
|
|
||||||
//add
|
|
||||||
defineQueryAddFunction();
|
|
||||||
defineBindParamsAddFunction();
|
|
||||||
defineReadBackGeneratedValuesAddPreparedStatement();
|
|
||||||
defineReadBackGeneratedValuesAddResultSet();
|
|
||||||
|
|
||||||
//update
|
|
||||||
defineQueryUpdateFunction();
|
|
||||||
defineBindParamsUpdateFunction();
|
|
||||||
defineReadBackGeneratedValuesUpdatePreparedStatement();
|
|
||||||
defineReadBackGeneratedValuesUpdateResultSet();
|
|
||||||
|
|
||||||
//delete
|
|
||||||
defineQueryDeleteFunction();
|
|
||||||
defineBindParamsDeleteFunction();
|
|
||||||
|
|
||||||
//read
|
|
||||||
defineReadFunction();
|
|
||||||
|
|
||||||
//other
|
|
||||||
defineReflectedFields();
|
|
||||||
defineClassConstructor();
|
|
||||||
defineConstructor();
|
|
||||||
|
|
||||||
visitor.visitEnd();
|
|
||||||
|
|
||||||
byteCode = writer.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void defineClass() {
|
|
||||||
var classSimpleName = className.substring(Math.max(className.lastIndexOf("."), className.lastIndexOf("$")) + 1);
|
|
||||||
visitor.visit(Opcodes.V17, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | SYNTHETIC_FLAG, classSimpleName, Type.getDescriptor(MysqlEntityDbInterface.class).replace(";", "<" + ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()) + ">;"), Type.getInternalName(MysqlEntityDbInterface.class), new String[]{});
|
|
||||||
// visitor.visitNestHost(Type.getInternalName(context));
|
|
||||||
// visitor.visitInnerClass(className.replace(".", "/"), Type.getInternalName(context), classSimpleName, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SUPER);
|
|
||||||
}
|
|
||||||
|
|
||||||
//public <init>() {}
|
|
||||||
private void defineConstructor() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PUBLIC | SYNTHETIC_FLAG, "<init>", "()V", null, new String[0]);
|
|
||||||
mv.visitCode();
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), "<init>", "()V", false);
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
mv.visitMaxs(1, 1);
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineReflectedFields() {
|
|
||||||
for (Field field : fieldsToReflect) {
|
|
||||||
ByteCodeHelper.visitReflectedFieldDefinition(visitor, field, SET_SYNTHETIC_FLAG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//<clinit>
|
|
||||||
private void defineClassConstructor() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_STATIC | SYNTHETIC_FLAG, "<clinit>", "()V", null, new String[0]);
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
//init reflected fields
|
|
||||||
for (Field field : fieldsToReflect) {
|
|
||||||
ByteCodeHelper.visitReflectedFieldInit(mv, field, className);
|
|
||||||
}
|
|
||||||
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
mv.visitMaxs(2, 0);
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
//region add
|
|
||||||
private void defineQueryAddFunction() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryAdd", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexConnection = 1;
|
|
||||||
|
|
||||||
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
|
|
||||||
|
|
||||||
var columnsString = columnsAndValueBindersSet.getColumns().stream().map(e -> "`" + e + "`").collect(Collectors.joining(","));
|
|
||||||
var placeholdersString = columnsAndValueBindersSet.getColumns().stream().map(e -> "?").collect(Collectors.joining(","));
|
|
||||||
var query = "INSERT INTO `" + entity.getName() + "` (" + columnsString + ") VALUES (" + placeholdersString + ")";
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartConnection = new Label();
|
|
||||||
var labelEndConnection = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartConnection);
|
|
||||||
|
|
||||||
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
|
||||||
mv.visitLdcInsn(query);
|
|
||||||
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
|
|
||||||
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
|
|
||||||
} else {
|
|
||||||
mv.visitInsn(Opcodes.ICONST_0); //no flags
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
|
|
||||||
mv.visitInsn(Opcodes.ARETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndConnection);
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
|
|
||||||
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
|
|
||||||
|
|
||||||
mv.visitMaxs(3, 2);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineBindParamsAddFunction() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexStmt = 1;
|
|
||||||
var varIndexEntity = 2;
|
|
||||||
|
|
||||||
//create lists for columns and value binders
|
|
||||||
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartStmt = new Label();
|
|
||||||
var labelEndStmt = new Label();
|
|
||||||
var labelStartEntity = new Label();
|
|
||||||
var labelEndEntity = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartStmt);
|
|
||||||
mv.visitLabel(labelStartEntity);
|
|
||||||
|
|
||||||
//stmt.set* bind calls
|
|
||||||
var i = 1;
|
|
||||||
for (var valueBinder : columnsAndValueBindersSet.getValueBinders()) {
|
|
||||||
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
//return
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
mv.visitLabel(labelEndStmt);
|
|
||||||
mv.visitLabel(labelEndEntity);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
|
|
||||||
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
|
|
||||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
|
|
||||||
|
|
||||||
mv.visitMaxs(3, 3);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineReadBackGeneratedValuesAddPreparedStatement() {
|
|
||||||
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.IDENTITY)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//stub method if no generated values
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var labelStart = new Label();
|
|
||||||
var labelEnd = new Label();
|
|
||||||
|
|
||||||
mv.visitLabel(labelStart);
|
|
||||||
|
|
||||||
mv.visitCode();
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
|
|
||||||
mv.visitLabel(labelEnd);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStart, labelEnd, 0);
|
|
||||||
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStart, labelEnd, 1);
|
|
||||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStart, labelEnd, 2);
|
|
||||||
|
|
||||||
mv.visitMaxs(0, 3);
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineReadBackGeneratedValuesAddResultSet() {
|
|
||||||
var fieldsToRead = entity.getFields().stream().filter(e -> e.getGenerated() != Generated.Type.NONE).toList();
|
|
||||||
if (fieldsToRead.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesAdd", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_RESULT_SET, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
visitReadFieldsIntoEntityFunctionBody(mv, fieldsToRead, true);
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region update
|
|
||||||
private void defineQueryUpdateFunction() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexConnection = 1;
|
|
||||||
|
|
||||||
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
|
|
||||||
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
|
|
||||||
|
|
||||||
var updatesString = columnsAndValueBindersSet.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(", "));
|
|
||||||
var whereString = columnsAndValueBindersWhere.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(" AND "));
|
|
||||||
|
|
||||||
var query = "UPDATE `" + entity.getName() + "` SET " + updatesString + " WHERE " + whereString;
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartConnection = new Label();
|
|
||||||
var labelEndConnection = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartConnection);
|
|
||||||
|
|
||||||
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
|
||||||
mv.visitLdcInsn(query);
|
|
||||||
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.COMPUTED)) {
|
|
||||||
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
|
|
||||||
} else {
|
|
||||||
mv.visitInsn(Opcodes.ICONST_0); //no flags
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
|
|
||||||
mv.visitInsn(Opcodes.ARETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndConnection);
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
|
|
||||||
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
|
|
||||||
|
|
||||||
mv.visitMaxs(3, 2);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineBindParamsUpdateFunction() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexStmt = 1;
|
|
||||||
var varIndexEntity = 2;
|
|
||||||
|
|
||||||
//create lists for columns and value binders
|
|
||||||
var columnsAndValueBindersSet = prepareColumnsAndValueBindersSet();
|
|
||||||
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartStmt = new Label();
|
|
||||||
var labelEndStmt = new Label();
|
|
||||||
var labelStartEntity = new Label();
|
|
||||||
var labelEndEntity = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartStmt);
|
|
||||||
mv.visitLabel(labelStartEntity);
|
|
||||||
|
|
||||||
//stmt.set* bind calls
|
|
||||||
var i = 1;
|
|
||||||
for (var valueBinder : columnsAndValueBindersSet.getValueBinders()) {
|
|
||||||
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
|
|
||||||
}
|
|
||||||
for (var valueBinder : columnsAndValueBindersWhere.getValueBinders()) {
|
|
||||||
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
//return
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
mv.visitLabel(labelEndStmt);
|
|
||||||
mv.visitLabel(labelEndEntity);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
|
|
||||||
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
|
|
||||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
|
|
||||||
|
|
||||||
mv.visitMaxs(3, 3);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineReadBackGeneratedValuesUpdatePreparedStatement() {
|
|
||||||
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() == Generated.Type.COMPUTED)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//stub method if no generated values
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var labelStart = new Label();
|
|
||||||
var labelEnd = new Label();
|
|
||||||
|
|
||||||
mv.visitLabel(labelStart);
|
|
||||||
|
|
||||||
mv.visitCode();
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
|
|
||||||
mv.visitLabel(labelEnd);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStart, labelEnd, 0);
|
|
||||||
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStart, labelEnd, 1);
|
|
||||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStart, labelEnd, 2);
|
|
||||||
|
|
||||||
mv.visitMaxs(0, 3);
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineReadBackGeneratedValuesUpdateResultSet() {
|
|
||||||
var fieldsToRead = entity.getFields().stream().filter(e -> e.getGenerated() == Generated.Type.COMPUTED).toList();
|
|
||||||
if (fieldsToRead.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "readBackGeneratedValuesUpdate", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_RESULT_SET, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
visitReadFieldsIntoEntityFunctionBody(mv, fieldsToRead, true);
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region delete
|
|
||||||
private void defineQueryDeleteFunction() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "queryDelete", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_CONNECTION), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexConnection = 1;
|
|
||||||
|
|
||||||
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
|
|
||||||
|
|
||||||
var whereString = columnsAndValueBindersWhere.getColumns().stream().map(e -> "`" + e + "` = ?").collect(Collectors.joining(" AND "));
|
|
||||||
|
|
||||||
var query = "DELETE FROM `" + entity.getName() + "` WHERE " + whereString;
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartConnection = new Label();
|
|
||||||
var labelEndConnection = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartConnection);
|
|
||||||
|
|
||||||
//return connection.prepareStatement("query", Statement.RETURN_GENERATED_KEYS);
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
|
||||||
mv.visitLdcInsn(query);
|
|
||||||
if (entity.getFields().stream().anyMatch(e -> e.getGenerated() != Generated.Type.NONE)) {
|
|
||||||
ByteCodeHelper.visitIntInsn(mv, Statement.RETURN_GENERATED_KEYS);
|
|
||||||
} else {
|
|
||||||
mv.visitInsn(Opcodes.ICONST_0); //no flags
|
|
||||||
}
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_CONNECTION.getInternalName(), "prepareStatement", Type.getMethodDescriptor(ByteCodeHelper.T_PREPARED_STATEMENT, ByteCodeHelper.T_STRING, ByteCodeHelper.T_INT), true);
|
|
||||||
mv.visitInsn(Opcodes.ARETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndConnection);
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartConnection, labelEndConnection, varIndexThis);
|
|
||||||
mv.visitLocalVariable("connection", ByteCodeHelper.T_CONNECTION.getDescriptor(), null, labelStartThis, labelEndThis, varIndexConnection);
|
|
||||||
|
|
||||||
mv.visitMaxs(3, 2);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineBindParamsDeleteFunction() {
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "bindParamsDelete", Type.getMethodDescriptor(ByteCodeHelper.T_VOID, ByteCodeHelper.T_PREPARED_STATEMENT, Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()))), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexStmt = 1;
|
|
||||||
var varIndexEntity = 2;
|
|
||||||
|
|
||||||
//create lists for columns and value binders
|
|
||||||
var columnsAndValueBindersWhere = prepareColumnsAndValueBindersWhere();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartStmt = new Label();
|
|
||||||
var labelEndStmt = new Label();
|
|
||||||
var labelStartEntity = new Label();
|
|
||||||
var labelEndEntity = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartStmt);
|
|
||||||
mv.visitLabel(labelStartEntity);
|
|
||||||
|
|
||||||
//stmt.set* bind calls
|
|
||||||
var i = 1;
|
|
||||||
for (var valueBinder : columnsAndValueBindersWhere.getValueBinders()) {
|
|
||||||
valueBinder.bind(mv, varIndexEntity, varIndexStmt, i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
//return
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
mv.visitLabel(labelEndStmt);
|
|
||||||
mv.visitLabel(labelEndEntity);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
|
|
||||||
mv.visitLocalVariable("stmt", ByteCodeHelper.T_PREPARED_STATEMENT.getDescriptor(), null, labelStartStmt, labelEndStmt, varIndexStmt);
|
|
||||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
|
|
||||||
|
|
||||||
mv.visitMaxs(3, 3);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region read
|
|
||||||
private void defineReadFunction() {
|
|
||||||
var fields = entity.getFields().stream().filter(e -> e.isDatabaseField()).toList();
|
|
||||||
var bestConstructor = classDescription.getDeclaredConstructors().stream().filter(e -> e.getSuperConstructorParameterDescriptions().size() == 0).map(ctor -> {
|
|
||||||
var matchingParamCount = ctor.getConstructorParameterDescriptions().stream()
|
|
||||||
.filter(param -> fields.stream().anyMatch(field -> field.getField().getDeclaringClass().getName().equals(param.getDeclaringClassName())
|
|
||||||
&& field.getField().getName().equals(param.getFieldName())))
|
|
||||||
.count();
|
|
||||||
return new AbstractMap.SimpleEntry<>(ctor, matchingParamCount);
|
|
||||||
}).sorted(Comparator.comparingLong(e -> -e.getValue())).findFirst();
|
|
||||||
if (!bestConstructor.isPresent()) {
|
|
||||||
throw new IllegalStateException("No suitable constructor found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var orderedFields = bestConstructor.orElseThrow().getKey().getConstructorParameterDescriptions().stream()
|
|
||||||
.sorted(Comparator.comparingInt(e -> e.getParameterIndex()))
|
|
||||||
.map(param -> new AbstractMap.SimpleEntry<>(param, fields.stream()
|
|
||||||
.filter(field -> field.getField().getDeclaringClass().getName().equals(param.getDeclaringClassName())
|
|
||||||
&& field.getField().getName().equals(param.getFieldName()))
|
|
||||||
.findFirst().orElse(null)))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
//begin
|
|
||||||
var mv = visitor.visitMethod(Opcodes.ACC_PROTECTED | SYNTHETIC_FLAG, "read", Type.getMethodDescriptor(Type.getType(ByteCodeHelper.getDescriptorForClassName(entity.getTypeName())), ByteCodeHelper.T_RESULT_SET), null, new String[]{ByteCodeHelper.T_SQLEXCEPTION.getInternalName()});
|
|
||||||
mv.visitAnnotation(ByteCodeHelper.T_OVERRIDE.getDescriptor(), true).visitEnd();
|
|
||||||
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexRes = 1;
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartRes = new Label();
|
|
||||||
var labelEndRes = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartRes);
|
|
||||||
|
|
||||||
// push params
|
|
||||||
mv.visitLdcInsn(Type.getObjectType(entity.getTypeName().replace(".", "/")));
|
|
||||||
for (var paramAndField : orderedFields) {
|
|
||||||
if (paramAndField.getValue() == null) {
|
|
||||||
var fieldType = entity.getFields().stream()
|
|
||||||
.filter(field -> field.getField().getDeclaringClass().getName().equals(paramAndField.getKey().getDeclaringClassName())
|
|
||||||
&& field.getField().getName().equals(paramAndField.getKey().getFieldName()))
|
|
||||||
.findFirst().orElseThrow()
|
|
||||||
.getField().getType();
|
|
||||||
// PUSH null, 0, or false
|
|
||||||
ByteCodeHelper.visitPushDefault(mv, fieldType);
|
|
||||||
} else {
|
|
||||||
// PUSH res.get*(1);
|
|
||||||
ByteCodeHelper.visitResultSetGetAny(mv, paramAndField.getValue(), varIndexRes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//new Entity(...)
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, entity.getTypeName().replace(".", "/"), "<init>",
|
|
||||||
Type.getMethodDescriptor(ByteCodeHelper.T_VOID, orderedFields.stream()
|
|
||||||
.map(paramAndField -> Type.getType(ByteCodeHelper.getDescriptorForClassName(paramAndField.getKey().getParameterClassName())))
|
|
||||||
.toArray(Type[]::new)), false);
|
|
||||||
|
|
||||||
//return
|
|
||||||
mv.visitInsn(Opcodes.ARETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
mv.visitLabel(labelEndRes);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
|
|
||||||
mv.visitLocalVariable("res", ByteCodeHelper.T_RESULT_SET.getDescriptor(), null, labelStartRes, labelEndRes, varIndexRes);
|
|
||||||
|
|
||||||
mv.visitMaxs(orderedFields.size() + 1, 2);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region util
|
|
||||||
private ColumnsAndValueBinders prepareColumnsAndValueBindersSet() {
|
|
||||||
var columns = new ArrayList<String>();
|
|
||||||
var valueBinders = new ArrayList<ValueBinder>();
|
|
||||||
|
|
||||||
for (DbField<?> field : entity.getFields()) {
|
|
||||||
if (!field.isDatabaseField()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Field f = field.getField();
|
|
||||||
if (f == null) {
|
|
||||||
if (field.isDatabaseField()) {//field defined but not present in entity class -> currently nothing to insert -> null
|
|
||||||
columns.add(field.getName());
|
|
||||||
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
|
|
||||||
ByteCodeHelper.visitIntInsn(mv, index);
|
|
||||||
|
|
||||||
//stmt.setNull(i, Types.INTEGER);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
columns.add(field.getName());
|
|
||||||
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
|
|
||||||
if (field.isNotNull()) {
|
|
||||||
//stmt.set*(i, value);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
|
|
||||||
} else {
|
|
||||||
// getFieldValue(entity);
|
|
||||||
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription);
|
|
||||||
|
|
||||||
// if (value == null)
|
|
||||||
// stmt.setNull(i, Types.*);
|
|
||||||
// } else {
|
|
||||||
// stmt.set*(i, value);
|
|
||||||
// }
|
|
||||||
var labelAfter = new Label();
|
|
||||||
var labelElse = new Label();
|
|
||||||
mv.visitJumpInsn(Opcodes.IFNULL, labelElse);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
|
|
||||||
mv.visitJumpInsn(Opcodes.GOTO, labelAfter);
|
|
||||||
mv.visitLabel(labelElse);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
|
|
||||||
mv.visitLabel(labelAfter);
|
|
||||||
// mv.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
|
|
||||||
mv.visitFrame(Opcodes.F_FULL, 3, new Object[]{ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), ByteCodeHelper.T_PREPARED_STATEMENT.getInternalName(), entity.getTypeName().replace(".", "/")}, 0, new Object[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new ColumnsAndValueBinders(columns, valueBinders);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ColumnsAndValueBinders prepareColumnsAndValueBindersWhere() {
|
|
||||||
var columns = new ArrayList<String>();
|
|
||||||
var valueBinders = new ArrayList<ValueBinder>();
|
|
||||||
|
|
||||||
for (DbField<?> field : entity.getPrimaryKey().getFields()) {
|
|
||||||
if (!field.isDatabaseField()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Field f = field.getField();
|
|
||||||
if (f == null) {
|
|
||||||
if (field.isDatabaseField()) {//field defined but not present in entity class -> currently nothing to insert -> null
|
|
||||||
columns.add(field.getName());
|
|
||||||
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
|
|
||||||
ByteCodeHelper.visitIntInsn(mv, index);
|
|
||||||
|
|
||||||
//stmt.setNull(i, Types.INTEGER);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
columns.add(field.getName());
|
|
||||||
valueBinders.add((mv, varIndexEntity, varIndexStmt, index) -> {
|
|
||||||
if (field.isNotNull()) {
|
|
||||||
//stmt.set*(i, value);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
|
|
||||||
} else {
|
|
||||||
// getFieldValue(entity);
|
|
||||||
ByteCodeHelper.visitGetField(mv, f, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription);
|
|
||||||
|
|
||||||
// if (value == null)
|
|
||||||
// stmt.setNull(i, Types.*);
|
|
||||||
// } else {
|
|
||||||
// stmt.set*(i, value);
|
|
||||||
// }
|
|
||||||
var labelAfter = new Label();
|
|
||||||
var labelElse = new Label();
|
|
||||||
mv.visitJumpInsn(Opcodes.IFNULL, labelElse);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetAny(mv, field, varIndexStmt, index, (mv2) -> ByteCodeHelper.visitGetField(mv2, field.getField(), varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription));
|
|
||||||
mv.visitJumpInsn(Opcodes.GOTO, labelAfter);
|
|
||||||
mv.visitLabel(labelElse);
|
|
||||||
ByteCodeHelper.visitPreparedStatementSetNull(mv, field, varIndexStmt, index);
|
|
||||||
mv.visitLabel(labelAfter);
|
|
||||||
// mv.visitFrame(Opcodes.F_SAME, 0, new Object[0], 0, new Object[0]);
|
|
||||||
mv.visitFrame(Opcodes.F_FULL, 3, new Object[]{ByteCodeHelper.T_MYSQL_ENTITY_DB_INTERFACE.getInternalName(), ByteCodeHelper.T_PREPARED_STATEMENT.getInternalName(), entity.getTypeName().replace(".", "/")}, 0, new Object[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new ColumnsAndValueBinders(columns, valueBinders);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void visitReadFieldsIntoEntityFunctionBody(MethodVisitor mv, List<DbField<?>> fields, boolean moveNext) {
|
|
||||||
var varIndexThis = 0;
|
|
||||||
var varIndexRes = 1;
|
|
||||||
var varIndexEntity = 2;
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
var labelStartThis = new Label();
|
|
||||||
var labelEndThis = new Label();
|
|
||||||
var labelStartRes = new Label();
|
|
||||||
var labelEndRes = new Label();
|
|
||||||
var labelStartEntity = new Label();
|
|
||||||
var labelEndEntity = new Label();
|
|
||||||
|
|
||||||
// begin
|
|
||||||
mv.visitCode();
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelStartThis);
|
|
||||||
mv.visitLabel(labelStartRes);
|
|
||||||
mv.visitLabel(labelStartEntity);
|
|
||||||
|
|
||||||
if (moveNext) {
|
|
||||||
// if (!res.next()) {
|
|
||||||
// throw new SQLException("Missing generated keys for entity but are required");
|
|
||||||
// }
|
|
||||||
var labelResHasNext = new Label();
|
|
||||||
mv.visitVarInsn(Opcodes.ALOAD, varIndexRes);
|
|
||||||
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ByteCodeHelper.T_RESULT_SET.getInternalName(), "next", Type.getMethodDescriptor(ByteCodeHelper.T_BOOL), true);
|
|
||||||
mv.visitJumpInsn(Opcodes.IFNE, labelResHasNext);
|
|
||||||
ByteCodeHelper.visitThrowSQLException(mv, "Missing generated keys for entity but are required");
|
|
||||||
mv.visitLabel(labelResHasNext);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var field : fields) {
|
|
||||||
// var value = res.get*(1);
|
|
||||||
Consumer<MethodVisitor> resGetValue = (mv2) -> {
|
|
||||||
ByteCodeHelper.visitResultSetGetAny(mv2, field, varIndexRes);
|
|
||||||
};
|
|
||||||
|
|
||||||
// *SET*(entity, field, value);
|
|
||||||
ByteCodeHelper.visitSetField(mv, field.getField(), resGetValue, varIndexEntity, className, () -> fieldsToReflect.add(field.getField()), classDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
//return
|
|
||||||
mv.visitInsn(Opcodes.RETURN);
|
|
||||||
|
|
||||||
// variable scopes
|
|
||||||
mv.visitLabel(labelEndThis);
|
|
||||||
mv.visitLabel(labelEndRes);
|
|
||||||
mv.visitLabel(labelEndEntity);
|
|
||||||
|
|
||||||
// debug symbols
|
|
||||||
mv.visitLocalVariable("this", ByteCodeHelper.getDescriptorForClassName(className), null, labelStartThis, labelEndThis, varIndexThis);
|
|
||||||
mv.visitLocalVariable("res", ByteCodeHelper.T_RESULT_SET.getDescriptor(), null, labelStartRes, labelEndRes, varIndexRes);
|
|
||||||
mv.visitLocalVariable("entity", ByteCodeHelper.getDescriptorForClassName(entity.getTypeName()), null, labelStartEntity, labelEndEntity, varIndexEntity);
|
|
||||||
|
|
||||||
mv.visitMaxs(3, 3);
|
|
||||||
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface ValueBinder {
|
|
||||||
void bind(MethodVisitor mv, int varIndexEntity, int varIndexStmt, int index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Getter
|
|
||||||
private static class ColumnsAndValueBinders {
|
|
||||||
private final List<String> columns;
|
|
||||||
private final List<ValueBinder> valueBinders;
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package jef.platform.mysql.dbinterface;
|
|
||||||
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class MysqlInterfaceClassLoader extends ClassLoader {
|
|
||||||
private final Map<String, byte[]> resources = new HashMap<>();
|
|
||||||
|
|
||||||
public MysqlInterfaceClassLoader(String name, ClassLoader parent) {
|
|
||||||
super(name, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MysqlInterfaceClassLoader(ClassLoader parent) {
|
|
||||||
super(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MysqlInterfaceClassLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<T>> defineClass(String className, byte[] byteCode) {
|
|
||||||
synchronized (resources) {
|
|
||||||
var resourceName = className.replace(".", "/") + ".class";
|
|
||||||
resources.put(resourceName, byteCode);
|
|
||||||
}
|
|
||||||
return (Class<? extends MysqlEntityDbInterface<T>>) super.defineClass(className, byteCode, 0, byteCode.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getResource(String name) {
|
|
||||||
return super.getResource(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<URL> getResources(String name) throws IOException {
|
|
||||||
return super.getResources(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getResourceAsStream(String name) {
|
|
||||||
synchronized (resources) {
|
|
||||||
return super.getResourceAsStream(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<URL> resources(String name) {
|
|
||||||
return super.resources(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected URL findResource(String moduleName, String name) throws IOException {
|
|
||||||
return super.findResource(moduleName, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected URL findResource(String name) {
|
|
||||||
return super.findResource(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package jef.platform.mysql.dbinterface;
|
|
||||||
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbEntity;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class MysqlInterfaceFactory {
|
|
||||||
private static MysqlInterfaceFactory getInstance(Class<DbContext> context) {
|
|
||||||
return new MysqlInterfaceFactory(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Class<DbContext> context;
|
|
||||||
private final MysqlInterfaceClassLoader classLoader = new MysqlInterfaceClassLoader();
|
|
||||||
private final Map<Class<? extends SerializableObject>, Class<? extends MysqlEntityDbInterface<? extends SerializableObject>>> cache = new HashMap<>();
|
|
||||||
|
|
||||||
public <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<T>> getInterface(DbEntity<T> entity) {
|
|
||||||
synchronized (cache) {
|
|
||||||
return (Class<? extends MysqlEntityDbInterface<T>>) cache.computeIfAbsent(entity.getType(), ignored -> generateInterface(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends SerializableObject> Class<? extends MysqlEntityDbInterface<? extends SerializableObject>> generateInterface(DbEntity<T> entity) {
|
|
||||||
var generator = new MysqlEntityDbInterfaceGenerator<T>(context, entity);
|
|
||||||
generator.generate();
|
|
||||||
var byteCode = generator.getByteCode();
|
|
||||||
return classLoader.defineClass(generator.getClassName(), byteCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package jef.platform.mysql.migration;
|
|
||||||
|
|
||||||
import jef.model.migration.Migration;
|
|
||||||
import jef.platform.SqlPlatform;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.platform.base.migration.MigrationApplier;
|
|
||||||
|
|
||||||
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, SqlPlatform sqlPlatform) {
|
|
||||||
super(connection, options, sqlPlatform);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void insertMigrationLog(Migration m) throws SQLException {
|
|
||||||
try (var stmt = connection.prepareStatement("INSERT INTO `" + options.getMigrationsTableName() + "` (`migration`, `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(options.getMigrationsTableName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<String> getAppliedMigrations() throws SQLException {
|
|
||||||
var ret = new ArrayList<String>();
|
|
||||||
if (!migrationsTableExists()) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (var stmt = connection.prepareStatement("SELECT `migration` FROM `" + options.getMigrationsTableName() + "`");
|
|
||||||
var res = stmt.executeQuery()) {
|
|
||||||
while (res.next()) {
|
|
||||||
if (!res.getString(1).equals(options.getMigrationsTableName())) {
|
|
||||||
ret.add(res.getString("migration"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-163
@@ -1,163 +0,0 @@
|
|||||||
package jef.platform.mysql.migration;
|
|
||||||
|
|
||||||
import jef.model.annotations.Generated;
|
|
||||||
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 jef.platform.base.migration.MigrationOperationTranslator;
|
|
||||||
|
|
||||||
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 implements MigrationOperationTranslator {
|
|
||||||
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 translateS(Connection connection, MigrationOperation operation) throws SQLException {
|
|
||||||
return Optional.ofNullable(translators.get(operation.getClass())).orElseThrow().apply(connection, operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PreparedStatement translate(Connection connection, MigrationOperation operation) throws SQLException {
|
|
||||||
return translateS(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
|
|
||||||
+ (op.getGenerated() == Generated.Type.IDENTITY ? "AUTO_INCREMENT " : "")
|
|
||||||
// + (op.getGenerated() == Generated.Type.COMPUTED ? "AUTO_INCREMENT " : "")//TODO
|
|
||||||
+ " 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" : "")
|
|
||||||
+ (f.getGenerated() == Generated.Type.IDENTITY ? " AUTO_INCREMENT" : "");
|
|
||||||
// + (op.getGenerated() == Generated.Type.COMPUTED ? "AUTO_INCREMENT " : "")//TODO
|
|
||||||
}).collect(Collectors.joining(", "))
|
|
||||||
+ (op.getPrimaryKey() != null
|
|
||||||
? ", PRIMARY KEY (" + op.getPrimaryKey().build().getFields().stream().map(e -> "`" + e + "`").collect(Collectors.joining(", ")) + ")"
|
|
||||||
: "")
|
|
||||||
+ ")"); //TODO default collocation from database config or operation, field collation, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package jef.platform.mysql.query;
|
|
||||||
|
|
||||||
import jef.platform.base.query.QueryBuilder;
|
|
||||||
|
|
||||||
public class MysqlQueryBuilder extends QueryBuilder {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package jef.platform.mysql;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbContextOptions;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.platform.base.Database;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
class MysqlSimpleIntegrativeTest {
|
|
||||||
@Test
|
|
||||||
void queryEntities() throws Exception {
|
|
||||||
//setup
|
|
||||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
|
||||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
|
||||||
var ctxoptions = new DbContextOptions(dboptions);
|
|
||||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
|
||||||
var sqlPlatform = new MysqlPlatform();
|
|
||||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
|
||||||
var ctx = new Ctx(db, ctxoptions);
|
|
||||||
|
|
||||||
//test
|
|
||||||
var result = ctx.getCompanies().toList();
|
|
||||||
System.out.println(result);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addEntity() throws Exception {
|
|
||||||
//setup
|
|
||||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
|
||||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
|
||||||
var ctxoptions = new DbContextOptions(dboptions);
|
|
||||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
|
||||||
var sqlPlatform = new MysqlPlatform();
|
|
||||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
|
||||||
var ctx = new Ctx(db, ctxoptions);
|
|
||||||
|
|
||||||
//test
|
|
||||||
var entity = new Company();
|
|
||||||
entity.setName("some company");
|
|
||||||
ctx.getCompanies().add(entity);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
assertNotEquals(0, entity.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void updateEntity() throws Exception {
|
|
||||||
//setup
|
|
||||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
|
||||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
|
||||||
var ctxoptions = new DbContextOptions(dboptions);
|
|
||||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
|
||||||
var sqlPlatform = new MysqlPlatform();
|
|
||||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
|
||||||
var ctx = new Ctx(db, ctxoptions);
|
|
||||||
|
|
||||||
var entity = new Company();
|
|
||||||
entity.setName("some company");
|
|
||||||
ctx.getCompanies().add(entity);
|
|
||||||
assertNotEquals(0, entity.getId());
|
|
||||||
|
|
||||||
//test
|
|
||||||
entity.setName("other company");
|
|
||||||
ctx.getCompanies().update(entity);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
var id = entity.getId();
|
|
||||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.getId()).toList().stream().findFirst().orElseThrow();
|
|
||||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.id).toList().stream().findFirst().orElseThrow();
|
|
||||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == id).toList().stream().findFirst().orElseThrow();
|
|
||||||
var fromdb = ctx.getCompanies().toList().stream().filter(e -> e.id == id).findFirst().orElseThrow();
|
|
||||||
// var fromdb = ctx.getCompanies().toList().stream().filter(e -> e.id == 5).findFirst().orElseThrow();
|
|
||||||
assertEquals(entity, fromdb);
|
|
||||||
assertNotSame(entity, fromdb);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deleteEntity() throws Exception {
|
|
||||||
//setup
|
|
||||||
Class.forName("com.mysql.cj.jdbc.Driver").getDeclaredConstructor().newInstance();
|
|
||||||
var dboptions = new DatabaseOptions("jdbc:mysql://localhost/test", "test", "password", getClass().getSimpleName(), null);
|
|
||||||
var ctxoptions = new DbContextOptions(dboptions);
|
|
||||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
|
||||||
var sqlPlatform = new MysqlPlatform();
|
|
||||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
|
||||||
var ctx = new Ctx(db, ctxoptions);
|
|
||||||
|
|
||||||
var entity = new Company();
|
|
||||||
entity.setName("some company");
|
|
||||||
ctx.getCompanies().add(entity);
|
|
||||||
assertNotEquals(0, entity.getId());
|
|
||||||
|
|
||||||
//test
|
|
||||||
ctx.getCompanies().delete(entity);
|
|
||||||
|
|
||||||
//assert
|
|
||||||
var id = entity.getId();
|
|
||||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.getId()).toList().stream().findFirst().orElseThrow();//TODO get this to work too
|
|
||||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == entity.id).toList().stream().findFirst().orElseThrow();
|
|
||||||
// var fromdb = ctx.getCompanies().filter(e -> e.id == id).toList().stream().findFirst().orElseThrow();
|
|
||||||
var fromdb = ctx.getCompanies().toList().stream().noneMatch(e -> e.id == id);
|
|
||||||
assertTrue(fromdb);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(Company.class)
|
|
||||||
private DbSet<Company> companies;
|
|
||||||
@Clazz(Employee.class)
|
|
||||||
private DbSet<Employee> employees;
|
|
||||||
|
|
||||||
public Ctx() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Ctx(Database database, DbContextOptions options) {
|
|
||||||
super(database, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ToString
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public static class Company extends SerializableObject {
|
|
||||||
private int id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private Employee ceo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ToString
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public static class Employee extends SerializableObject {
|
|
||||||
private int id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private Employee boss;
|
|
||||||
@ForeignKey(getterOrField = "boss")
|
|
||||||
private int bossId;
|
|
||||||
|
|
||||||
private Company company;
|
|
||||||
@ForeignKey(getterOrField = "company")
|
|
||||||
private int companyId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-86
@@ -1,86 +0,0 @@
|
|||||||
package jef.platform.mysql.dbinterface;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbContextOptions;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.platform.base.Database;
|
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
|
|
||||||
class MysqlEntityDbInterfaceGeneratorTest {
|
|
||||||
private static final String GENERATED_MIGRATIONS_DBINTERFACES = "target/test-generated-dbinterfaces/";
|
|
||||||
// private static final String GENERATED_MIGRATIONS_FOLDER_SRC = GENERATED_MIGRATIONS_DBINTERFACES + "src/";
|
|
||||||
private static final String GENERATED_MIGRATIONS_FOLDER_TARGET = GENERATED_MIGRATIONS_DBINTERFACES + "target/";
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void generate() throws IOException {
|
|
||||||
var mb = ModelBuilder.from(Ctx.class);
|
|
||||||
var generator = new MysqlEntityDbInterfaceGenerator<>(Ctx.class, mb.getEntity(Employee.class));
|
|
||||||
generator.generate();
|
|
||||||
// var path = Path.of(GENERATED_MIGRATIONS_FOLDER_TARGET, "MysqlEntityDbInterfaceGeneratorTest", "MysqlEntityDbInterfaceGeneratorTest$Ctx$EmployeeMysqlEntityDbInterface.class");
|
|
||||||
var path = Path.of(GENERATED_MIGRATIONS_FOLDER_TARGET, "MysqlEntityDbInterfaceGeneratorTest", "EmployeeMysqlEntityDbInterface.class");
|
|
||||||
path.toFile().getParentFile().mkdirs();
|
|
||||||
Files.write(path, generator.getByteCode(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class Ctx extends DbContext {
|
|
||||||
@Clazz(Company.class)
|
|
||||||
private DbSet<Company> companies;
|
|
||||||
@Clazz(Employee.class)
|
|
||||||
private DbSet<Employee> employees;
|
|
||||||
|
|
||||||
public Ctx() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Ctx(Database database, DbContextOptions options) {
|
|
||||||
super(database, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Getter
|
|
||||||
// @Setter
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ToString
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public static class Company extends SerializableObject {
|
|
||||||
private int id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private Employee ceo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Getter
|
|
||||||
// @Setter
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
@ToString
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public static class Employee extends SerializableObject {
|
|
||||||
private int id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private Employee boss;
|
|
||||||
@ForeignKey(getterOrField = "boss")
|
|
||||||
private int bossId;
|
|
||||||
|
|
||||||
private Company company;
|
|
||||||
@ForeignKey(getterOrField = "company")
|
|
||||||
private int companyId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
package jef.platform.mysql.migration;
|
|
||||||
|
|
||||||
import jef.DbSet;
|
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.model.DbContextOptions;
|
|
||||||
import jef.model.ModelBuilder;
|
|
||||||
import jef.model.annotations.Clazz;
|
|
||||||
import jef.model.annotations.ForeignKey;
|
|
||||||
import jef.model.migration.creator.MigrationCreator;
|
|
||||||
import jef.platform.base.Database;
|
|
||||||
import jef.platform.base.DatabaseOptions;
|
|
||||||
import jef.platform.mysql.MysqlDatabase;
|
|
||||||
import jef.platform.mysql.MysqlPlatform;
|
|
||||||
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.nio.file.Files;
|
|
||||||
import java.nio.file.OpenOption;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
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/";
|
|
||||||
private static final String PACKAGE_NAME = MysqlMigrationTest.class.getSimpleName();
|
|
||||||
|
|
||||||
@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", PACKAGE_NAME, null);
|
|
||||||
var ctxoptions = new DbContextOptions(dboptions);
|
|
||||||
var conn = DriverManager.getConnection(dboptions.getUrl(), dboptions.getUser(), dboptions.getPassword());
|
|
||||||
var sqlPlatform = new MysqlPlatform();
|
|
||||||
var db = new MysqlDatabase(conn, dboptions, sqlPlatform);
|
|
||||||
var ctx = new Ctx(db, ctxoptions);
|
|
||||||
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 + PACKAGE_NAME));
|
|
||||||
delRecursive(new File(GENERATE_MIGRATIONS_FOLDER_TARGET + PACKAGE_NAME));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 sqlPlatform = new MysqlPlatform();
|
|
||||||
var from = new ModelBuilder();
|
|
||||||
var to = ModelBuilder.from(Ctx.class);
|
|
||||||
var result = new MigrationCreator(sqlPlatform, from, to, "Initial", PACKAGE_NAME, null).createMigration();
|
|
||||||
|
|
||||||
var dir = Path.of(GENERATE_MIGRATIONS_FOLDER_SRC, PACKAGE_NAME).toFile();
|
|
||||||
dir.mkdirs();
|
|
||||||
|
|
||||||
var fileOptions = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE};
|
|
||||||
Files.writeString(Path.of(dir.toString(), result.getMigrationClassName() + ".java"), result.getMigration(), fileOptions);
|
|
||||||
Files.writeString(Path.of(dir.toString(), result.getMigrationSnapshotClassName() + ".java"), result.getMigrationSnapshot(), fileOptions);
|
|
||||||
Files.writeString(Path.of(dir.toString(), result.getCurrentSnapshotClassName() + ".java"), result.getCurrentSnapshot(), fileOptions);
|
|
||||||
} 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" + File.pathSeparator + "../core/target/classes",
|
|
||||||
"-encoding", "UTF8",
|
|
||||||
"-g", //debug symbols
|
|
||||||
"-d", GENERATE_MIGRATIONS_FOLDER_TARGET //target
|
|
||||||
));
|
|
||||||
Arrays.stream(new File(GENERATE_MIGRATIONS_FOLDER_SRC + PACKAGE_NAME).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() {
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,13 +7,6 @@
|
|||||||
<groupId>jef</groupId>
|
<groupId>jef</groupId>
|
||||||
<artifactId>jef</artifactId>
|
<artifactId>jef</artifactId>
|
||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
<packaging>pom</packaging>
|
|
||||||
<modules>
|
|
||||||
<module>core</module>
|
|
||||||
<module>cli</module>
|
|
||||||
<module>mysql</module>
|
|
||||||
<module>migration-creator</module>
|
|
||||||
</modules>
|
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
@@ -101,22 +94,12 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.8.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<!-- <plugin> <!– for inserting values in plugin.yml –>-->
|
<plugin> <!-- for inserting values in plugin.yml -->
|
||||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
|
||||||
<!-- <artifactId>maven-resources-plugin</artifactId>-->
|
|
||||||
<!-- <version>2.7</version>-->
|
|
||||||
<!-- <configuration>-->
|
|
||||||
<!-- <encoding>${project.build.sourceEncoding}</encoding>-->
|
|
||||||
<!-- </configuration>-->
|
|
||||||
<!-- </plugin>-->
|
|
||||||
<plugin> <!-- for tests which generate, compile, and execute migrations -->
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.0.0-M7</version>
|
<version>2.7</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<additionalClasspathElements>
|
<encoding>${project.build.sourceEncoding}</encoding>
|
||||||
<additionalClasspathElement>${project.basedir}/target/test-generated-migrations/target</additionalClasspathElement>
|
|
||||||
</additionalClasspathElements>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
@@ -163,18 +146,10 @@
|
|||||||
<version>${mockito.version}</version>
|
<version>${mockito.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- <dependency>-->
|
||||||
<dependency>
|
<!-- <groupId>javax.persistence</groupId>-->
|
||||||
<groupId>mysql</groupId>
|
<!-- <artifactId>javax.persistence-api</artifactId>-->
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<!-- <version>2.2</version>-->
|
||||||
<version>8.0.30</version>
|
<!-- </dependency>-->
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- <dependency>-->
|
|
||||||
<!-- <groupId>javax.persistence</groupId>-->
|
|
||||||
<!-- <artifactId>javax.persistence-api</artifactId>-->
|
|
||||||
<!-- <version>2.2</version>-->
|
|
||||||
<!-- </dependency>-->
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -4,24 +4,19 @@ import jef.expressions.Expression;
|
|||||||
import jef.expressions.SelectExpression;
|
import jef.expressions.SelectExpression;
|
||||||
import jef.expressions.TableExpression;
|
import jef.expressions.TableExpression;
|
||||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||||
import jef.model.DbContext;
|
|
||||||
import jef.serializable.SerializableObject;
|
import jef.serializable.SerializableObject;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
|
||||||
public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||||
@Getter
|
|
||||||
private final DbContext context;
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Class<T> clazz;
|
private final Class<T> clazz;
|
||||||
private final String table;
|
private final String table;
|
||||||
|
|
||||||
public DbSet(DbContext context, Class<T> clazz, String table) {
|
public DbSet(Class<T> clazz, String table) {
|
||||||
this.context = context;
|
|
||||||
this.clazz = clazz;
|
this.clazz = clazz;
|
||||||
this.table = table;
|
this.table = table;
|
||||||
}
|
}
|
||||||
@@ -41,23 +36,6 @@ public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
|||||||
return "SELECT * FROM `" + table + "`";
|
return "SELECT * FROM `" + table + "`";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DbSet<T> originalSet() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(T entity) throws SQLException {
|
|
||||||
context.getDatabase().add(context, clazz, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void update(T entity) throws SQLException {
|
|
||||||
context.getDatabase().update(context, clazz, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(T entity) throws SQLException {
|
|
||||||
context.getDatabase().delete(context, clazz, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<T> iterator() {
|
public Iterator<T> iterator() {
|
||||||
return null;
|
return null;
|
||||||
@@ -6,15 +6,13 @@ import jef.operations.FilterOp;
|
|||||||
import jef.operations.LimitOp;
|
import jef.operations.LimitOp;
|
||||||
import jef.operations.SortOp;
|
import jef.operations.SortOp;
|
||||||
import jef.serializable.SerializableFunction;
|
import jef.serializable.SerializableFunction;
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.serializable.SerializablePredicate;
|
import jef.serializable.SerializablePredicate;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.io.Serializable;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
|
||||||
public interface Queryable<T extends SerializableObject> {
|
public interface Queryable<T extends Serializable> {
|
||||||
|
|
||||||
//TODO documentation
|
//TODO documentation
|
||||||
//TODO table alias thing still not ready
|
//TODO table alias thing still not ready
|
||||||
@@ -24,8 +22,6 @@ public interface Queryable<T extends SerializableObject> {
|
|||||||
|
|
||||||
String toString();
|
String toString();
|
||||||
|
|
||||||
DbSet<T> originalSet();
|
|
||||||
|
|
||||||
//stream functions
|
//stream functions
|
||||||
default Iterator<T> iterator() {
|
default Iterator<T> iterator() {
|
||||||
return null;
|
return null;
|
||||||
@@ -135,9 +131,6 @@ public interface Queryable<T extends SerializableObject> {
|
|||||||
// default Object[] toArray() {
|
// default Object[] toArray() {
|
||||||
// return new Object[0];
|
// return new Object[0];
|
||||||
// }
|
// }
|
||||||
default List<T> toList() throws SQLException {
|
|
||||||
return originalSet().getContext().getDatabase().queryExpression(originalSet().getContext(), originalSet().getClazz(), getExpression());
|
|
||||||
}
|
|
||||||
//
|
//
|
||||||
// default <A> A[] toArray(IntFunction<A[]> intFunction) {
|
// default <A> A[] toArray(IntFunction<A[]> intFunction) {
|
||||||
// return null;
|
// return null;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package jef;
|
package jef;
|
||||||
|
|
||||||
import jef.expressions.Expression;
|
import jef.expressions.Expression;
|
||||||
import jef.serializable.SerializableObject;
|
|
||||||
import jef.serializable.SerializablePredicate;
|
import jef.serializable.SerializablePredicate;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
|
|
||||||
public class QueryableProxy<T extends SerializableObject> implements Queryable<T> {
|
public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
||||||
private final Queryable<T> delegate;
|
private final Queryable<T> delegate;
|
||||||
|
|
||||||
public QueryableProxy(Queryable<T> delegate) {
|
public QueryableProxy(Queryable<T> delegate) {
|
||||||
@@ -24,11 +24,6 @@ public class QueryableProxy<T extends SerializableObject> implements Queryable<T
|
|||||||
return delegate.getExpression();
|
return delegate.getExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DbSet<T> originalSet() {
|
|
||||||
return delegate.originalSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<T> iterator() {
|
public Iterator<T> iterator() {
|
||||||
return delegate.iterator();
|
return delegate.iterator();
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
package jef.asm;
|
package jef.asm;
|
||||||
|
|
||||||
public class AsmParseException extends RuntimeException {
|
public class AsmParseException extends Exception {
|
||||||
public AsmParseException() {
|
public AsmParseException() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package jef.asm;
|
||||||
|
|
||||||
|
import jef.expressions.Expression;
|
||||||
|
import jef.serializable.SerializableFunction;
|
||||||
|
import jef.serializable.SerializablePredicate;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.invoke.SerializedLambda;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class AsmParser {
|
||||||
|
private final Serializable lambda;
|
||||||
|
|
||||||
|
public AsmParser(SerializablePredicate<?> predicate) {
|
||||||
|
this.lambda = predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsmParser(SerializableFunction<?, ?> function) {
|
||||||
|
this.lambda = function;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Expression parse() throws AsmParseException {
|
||||||
|
try {
|
||||||
|
return parseExpression();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AsmParseException("PredicateParser: failed to parse expression: " + e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression parseExpression() throws Exception {
|
||||||
|
var cls = lambda.getClass();
|
||||||
|
var loader = cls.getClassLoader();
|
||||||
|
InputStream is;
|
||||||
|
// System.out.println(cls);
|
||||||
|
// System.out.println(cls.getName());
|
||||||
|
if (cls.getName().contains("$$Lambda$")) {
|
||||||
|
// System.out.println(cls.getName().split("\\$\\$")[0].replace(".", "/") + ".class");
|
||||||
|
is = loader.getResourceAsStream(cls.getName().split("\\$\\$")[0].replace(".", "/") + ".class");
|
||||||
|
} else {
|
||||||
|
// System.out.println(cls.getName().replace(".", "/") + ".class");
|
||||||
|
is = loader.getResourceAsStream(cls.getName().replace(".", "/") + ".class");
|
||||||
|
}
|
||||||
|
var x = cls.getDeclaredMethod("writeReplace");
|
||||||
|
// System.out.println(x);
|
||||||
|
x.setAccessible(true);
|
||||||
|
var serlambda = (SerializedLambda) x.invoke(lambda);
|
||||||
|
Object[] args = IntStream.range(0, serlambda.getCapturedArgCount()).mapToObj(serlambda::getCapturedArg).toArray();
|
||||||
|
// System.out.println(serlambda);
|
||||||
|
|
||||||
|
var lambdaname = serlambda.getImplMethodName();
|
||||||
|
// System.out.println(lambdaname);
|
||||||
|
|
||||||
|
var expr = new Expression[1];
|
||||||
|
|
||||||
|
var cr = new ClassReader(is);
|
||||||
|
var visiter = new FilterClassVisitor(Opcodes.ASM9, lambdaname, args, e -> expr[0] = e);
|
||||||
|
cr.accept(visiter, 0);
|
||||||
|
|
||||||
|
return expr[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package jef.asm;
|
||||||
|
|
||||||
|
import jef.expressions.Expression;
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
class FilterClassVisitor extends ClassVisitor {
|
||||||
|
private final int api;
|
||||||
|
private final String lambdaname;
|
||||||
|
private final Consumer<Expression> queryConsumer;
|
||||||
|
private final Object[] args;
|
||||||
|
|
||||||
|
protected FilterClassVisitor(int api, String lambdaname, Object[] args, Consumer<Expression> queryConsumer) {
|
||||||
|
super(api);
|
||||||
|
this.api = api;
|
||||||
|
this.lambdaname = lambdaname;
|
||||||
|
this.args = args;
|
||||||
|
this.queryConsumer = queryConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||||
|
if (!name.equals(lambdaname)) {
|
||||||
|
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("found method name: " + name);
|
||||||
|
return new FilterMethodVisitor(api, descriptor, args, queryConsumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-43
@@ -3,7 +3,6 @@ package jef.asm;
|
|||||||
import jef.expressions.BinaryExpression;
|
import jef.expressions.BinaryExpression;
|
||||||
import jef.expressions.ConstantExpression;
|
import jef.expressions.ConstantExpression;
|
||||||
import jef.expressions.Expression;
|
import jef.expressions.Expression;
|
||||||
import jef.expressions.FunctionExpression;
|
|
||||||
import jef.expressions.IntermediateFieldExpression;
|
import jef.expressions.IntermediateFieldExpression;
|
||||||
import jef.expressions.NullExpression;
|
import jef.expressions.NullExpression;
|
||||||
import jef.expressions.ParameterExpression;
|
import jef.expressions.ParameterExpression;
|
||||||
@@ -23,7 +22,6 @@ import org.objectweb.asm.Type;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -31,6 +29,7 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
@@ -38,30 +37,22 @@ class FilterMethodVisitor extends MethodVisitor {
|
|||||||
private Stack<Expression> varStack = new Stack<>();
|
private Stack<Expression> varStack = new Stack<>();
|
||||||
private final String[] parameterClasses;
|
private final String[] parameterClasses;
|
||||||
private final Object[] args;
|
private final Object[] args;
|
||||||
|
private final Consumer<Expression> exprConsumer;
|
||||||
private Expression lambdaExpr;
|
private Expression lambdaExpr;
|
||||||
private final Set<Field> accessedMembers = new HashSet<>();
|
|
||||||
|
|
||||||
protected FilterMethodVisitor(int api, String className, String descriptor, Object[] args) {
|
protected FilterMethodVisitor(int api, String descriptor, Object[] args, Consumer<Expression> exprConsumer) {
|
||||||
super(api);
|
super(api);
|
||||||
|
this.args = args;
|
||||||
|
this.exprConsumer = exprConsumer;
|
||||||
|
|
||||||
//parameters
|
//parameters
|
||||||
var types = Type.getMethodType(descriptor).getArgumentTypes();
|
var types = Type.getMethodType(descriptor).getArgumentTypes();
|
||||||
if (types.length == 0) {// class method //TODO this is dirty, as only works for parameterless class methods
|
parameterClasses = new String[types.length];
|
||||||
this.args = new Object[]{null};
|
for (int i = 0; i < types.length; i++) {
|
||||||
parameterClasses = new String[]{className};
|
parameterClasses[i] = types[i].getClassName();
|
||||||
} else {
|
|
||||||
this.args = args;
|
|
||||||
parameterClasses = new String[types.length];
|
|
||||||
for (int i = 0; i < types.length; i++) {
|
|
||||||
parameterClasses[i] = types[i].getClassName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AsmParseResult getResult() {
|
|
||||||
return new AsmParseResult(lambdaExpr, accessedMembers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||||
System.out.println("local var: " + name);
|
System.out.println("local var: " + name);
|
||||||
@@ -76,11 +67,6 @@ class FilterMethodVisitor extends MethodVisitor {
|
|||||||
var v = varStack.pop();
|
var v = varStack.pop();
|
||||||
if (v instanceof ParameterExpression p) {
|
if (v instanceof ParameterExpression p) {
|
||||||
if (p.isInput()) {
|
if (p.isInput()) {
|
||||||
try {
|
|
||||||
accessedMembers.add(Class.forName(owner.replace("/", ".")).getDeclaredField(name));
|
|
||||||
} catch (NoSuchFieldException | ClassNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
varStack.push(new IntermediateFieldExpression(name, descriptor));
|
varStack.push(new IntermediateFieldExpression(name, descriptor));
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("field insn: unsupported GETFIELD expression");
|
throw new RuntimeException("field insn: unsupported GETFIELD expression");
|
||||||
@@ -110,27 +96,13 @@ class FilterMethodVisitor extends MethodVisitor {
|
|||||||
} else if (opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEVIRTUAL) {
|
} else if (opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEVIRTUAL) {
|
||||||
try {
|
try {
|
||||||
if (name.equals("contains")
|
if (name.equals("contains")
|
||||||
&& owner.startsWith("java/util/")
|
&& owner.startsWith("java/util/")
|
||||||
&& Collection.class.isAssignableFrom(Class.forName(owner.replace("/", ".")))) {
|
&& Collection.class.isAssignableFrom(Class.forName(owner.replace("/", ".")))) {
|
||||||
var element = varStack.pop();
|
var element = varStack.pop();
|
||||||
var collection = varStack.pop();
|
var collection = varStack.pop();
|
||||||
varStack.push(new BinaryExpression(element, collection, BinaryExpression.Operator.IN));
|
varStack.push(new BinaryExpression(element, collection, BinaryExpression.Operator.IN));
|
||||||
} else if (name.equals("equals")
|
} else if (name.startsWith("get") && name.length() > 3 && descriptor.startsWith("()")) { //hacky getter support, TODO replace this with proper getter eval later
|
||||||
&& owner.equals("java/lang/String")) {
|
var field = findField(owner, name);
|
||||||
var value = varStack.pop();
|
|
||||||
var variable = varStack.pop();
|
|
||||||
varStack.push(new BinaryExpression(variable, value, BinaryExpression.Operator.EQ));
|
|
||||||
} else if (name.equals("equalsIgnoreCase")
|
|
||||||
&& owner.equals("java/lang/String")) {
|
|
||||||
var value = varStack.pop();
|
|
||||||
var variable = varStack.pop();
|
|
||||||
varStack.push(new BinaryExpression(new FunctionExpression(FunctionExpression.TOLOWER, List.of(variable)),
|
|
||||||
new FunctionExpression(FunctionExpression.TOLOWER, List.of(value)),
|
|
||||||
BinaryExpression.Operator.EQ));
|
|
||||||
} else if (descriptor.startsWith("()")) {
|
|
||||||
var method = Class.forName(owner.replace("/", ".")).getDeclaredMethod(name);
|
|
||||||
var res = new OptimizedAsmParser(method).parse();
|
|
||||||
var field = res.getAccessedFields().stream().findFirst();
|
|
||||||
if (field.isPresent()) {
|
if (field.isPresent()) {
|
||||||
var v = varStack.pop();
|
var v = varStack.pop();
|
||||||
if (v instanceof ParameterExpression p) {
|
if (v instanceof ParameterExpression p) {
|
||||||
@@ -149,9 +121,6 @@ class FilterMethodVisitor extends MethodVisitor {
|
|||||||
throw new RuntimeException("method insn: unsupported function " + name + " in " + owner);
|
throw new RuntimeException("method insn: unsupported function " + name + " in " + owner);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e instanceof RuntimeException re) {
|
|
||||||
throw re;
|
|
||||||
}
|
|
||||||
throw new RuntimeException("method insn: ", e);
|
throw new RuntimeException("method insn: ", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -417,6 +386,7 @@ class FilterMethodVisitor extends MethodVisitor {
|
|||||||
System.out.println("end");
|
System.out.println("end");
|
||||||
|
|
||||||
super.visitEnd();
|
super.visitEnd();
|
||||||
|
exprConsumer.accept(lambdaExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
+12
-10
@@ -1,16 +1,23 @@
|
|||||||
package jef.asm;
|
package jef.asm;
|
||||||
|
|
||||||
|
import jef.expressions.Expression;
|
||||||
import jef.expressions.modifier.ExpressionOptimizerBottomUp;
|
import jef.expressions.modifier.ExpressionOptimizerBottomUp;
|
||||||
import jef.expressions.modifier.IConst0Fixer;
|
import jef.expressions.modifier.IConst0Fixer;
|
||||||
|
import jef.expressions.modifier.TableAliasInjector;
|
||||||
import jef.expressions.modifier.TernaryRewriter;
|
import jef.expressions.modifier.TernaryRewriter;
|
||||||
import jef.serializable.SerializableFunction;
|
import jef.serializable.SerializableFunction;
|
||||||
import jef.serializable.SerializablePredicate;
|
import jef.serializable.SerializablePredicate;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.invoke.SerializedLambda;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class OptimizedAsmParser extends AsmParser {
|
public class OptimizedAsmParser extends AsmParser{
|
||||||
|
|
||||||
public OptimizedAsmParser(SerializablePredicate<?> predicate) {
|
public OptimizedAsmParser(SerializablePredicate<?> predicate) {
|
||||||
super(predicate);
|
super(predicate);
|
||||||
@@ -20,13 +27,8 @@ public class OptimizedAsmParser extends AsmParser {
|
|||||||
super(function);
|
super(function);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OptimizedAsmParser(Method method) {
|
public Expression parse() throws AsmParseException {
|
||||||
super(method);
|
var expr = super.parse();
|
||||||
}
|
|
||||||
|
|
||||||
public AsmParseResult parse() throws AsmParseException {
|
|
||||||
var result = super.parse();
|
|
||||||
var expr = result.getExpression();
|
|
||||||
|
|
||||||
System.out.println(expr);
|
System.out.println(expr);
|
||||||
expr = new TernaryRewriter().modify(expr);
|
expr = new TernaryRewriter().modify(expr);
|
||||||
@@ -35,6 +37,6 @@ public class OptimizedAsmParser extends AsmParser {
|
|||||||
System.out.println(expr);
|
System.out.println(expr);
|
||||||
expr = new ExpressionOptimizerBottomUp().modify(expr);
|
expr = new ExpressionOptimizerBottomUp().modify(expr);
|
||||||
|
|
||||||
return new AsmParseResult(expr, result.getAccessedFields());
|
return expr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
@@ -5,6 +5,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
-1
@@ -25,7 +25,6 @@ public class BinaryExpression implements Expression {
|
|||||||
case EQ, NE, IS -> Priority.EQUALITY;
|
case EQ, NE, IS -> Priority.EQUALITY;
|
||||||
case LT, LE, GT, GE -> Priority.RELATIONAL;
|
case LT, LE, GT, GE -> Priority.RELATIONAL;
|
||||||
case IN -> Priority.RELATIONAL; //or equality?
|
case IN -> Priority.RELATIONAL; //or equality?
|
||||||
//TODO +-, */, %, ...
|
|
||||||
default -> throw new IllegalStateException();
|
default -> throw new IllegalStateException();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package jef.expressions;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class ConstantExpression implements Expression {
|
||||||
|
public static final jef.expressions.ConstantExpression V0 = new jef.expressions.ConstantExpression(0);
|
||||||
|
public static final jef.expressions.ConstantExpression V1 = new jef.expressions.ConstantExpression(1);
|
||||||
|
public static final jef.expressions.ConstantExpression V2 = new jef.expressions.ConstantExpression(2);
|
||||||
|
public static final jef.expressions.ConstantExpression V3 = new jef.expressions.ConstantExpression(3);
|
||||||
|
public static final jef.expressions.ConstantExpression V4 = new jef.expressions.ConstantExpression(4);
|
||||||
|
public static final jef.expressions.ConstantExpression V5 = new jef.expressions.ConstantExpression(5);
|
||||||
|
|
||||||
|
protected final Object value;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getType() {
|
||||||
|
return Type.CONSTANT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Priority getPriority() {
|
||||||
|
return Priority.CONSTANT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-2
@@ -13,7 +13,6 @@ public interface Expression {
|
|||||||
BINARY,
|
BINARY,
|
||||||
CONSTANT,
|
CONSTANT,
|
||||||
FIELD,
|
FIELD,
|
||||||
FUNCTION,
|
|
||||||
INTERMEDIATE_FIELD,
|
INTERMEDIATE_FIELD,
|
||||||
LIMIT,
|
LIMIT,
|
||||||
NULL,
|
NULL,
|
||||||
@@ -43,7 +42,7 @@ public interface Expression {
|
|||||||
BIT_SHIFT(10), // <<, >>
|
BIT_SHIFT(10), // <<, >>
|
||||||
ADD_SUB(13), // +, -
|
ADD_SUB(13), // +, -
|
||||||
MUL_DIV_MOD(14), // *, /, %
|
MUL_DIV_MOD(14), // *, /, %
|
||||||
UNARY_PRE(15), // +, -, !, ~, --i, ++i, function calls
|
UNARY_PRE(15), // +, -, !, ~, --i, ++i
|
||||||
UNARY_POST(16), // i++, i--
|
UNARY_POST(16), // i++, i--
|
||||||
CONSTANT(17), // constant values
|
CONSTANT(17), // constant values
|
||||||
;
|
;
|
||||||
File renamed without changes.
@@ -0,0 +1,38 @@
|
|||||||
|
package jef.expressions;
|
||||||
|
|
||||||
|
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class IncludeExpression implements Expression {
|
||||||
|
private final Expression expr;
|
||||||
|
private final List<Include> includes;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getType() {
|
||||||
|
return Type.INCLUDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Priority getPriority() {
|
||||||
|
return Priority.UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
var ret = new SelectExpression(List.of(new DatabaseSelectAllExpression()), expr, "").toString();
|
||||||
|
ret += " LEFT JOIN "
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class Include {
|
||||||
|
private final FieldExpression expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 250 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user