modularize project
This commit is contained in:
20
core/pom.xml
Normal file
20
core/pom.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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>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>
|
||||
|
||||
</project>
|
||||
24
core/src/main/java/jef/Database.java
Normal file
24
core/src/main/java/jef/Database.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package jef;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public abstract class Database {
|
||||
// private final DatabaseConfiguration config;
|
||||
protected final Connection connection;
|
||||
protected final DatabaseOptions options;
|
||||
|
||||
public abstract void migrate() throws MigrationException;
|
||||
|
||||
// public ResultSet executeRaw(String s) throws SQLException {
|
||||
// try (var stmt = connection.prepareStatement(s)) {
|
||||
// try (var res = stmt.executeQuery()) {
|
||||
// return res;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
46
core/src/main/java/jef/DatabaseOptions.java
Normal file
46
core/src/main/java/jef/DatabaseOptions.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package jef;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public class DatabaseOptions {
|
||||
protected final String url;
|
||||
protected final String user;
|
||||
protected final String password;
|
||||
|
||||
protected final String migrationsPackage;
|
||||
|
||||
protected final String host;
|
||||
protected final String database;
|
||||
|
||||
public DatabaseOptions(String url, String user, String password, String migrationsPackage) {
|
||||
this.url = url;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
|
||||
host = extractHost(url);
|
||||
database = extractDatabase(url);
|
||||
this.migrationsPackage = migrationsPackage;
|
||||
}
|
||||
|
||||
private static String extractHost(String url) {
|
||||
var pattern = Pattern.compile("^.*?//(.*?)/.*$");
|
||||
var matcher = pattern.matcher(url);
|
||||
if (!matcher.matches()) {
|
||||
throw new RuntimeException(new MalformedURLException("Could not extract host for url: " + url));
|
||||
}
|
||||
return matcher.group(1);
|
||||
}
|
||||
|
||||
private static String extractDatabase(String url) {
|
||||
var pattern = Pattern.compile("^.*?//.*?/(.*?)([?/].*)?$");
|
||||
var matcher = pattern.matcher(url);
|
||||
if (!matcher.matches()) {
|
||||
throw new RuntimeException(new MalformedURLException("Could not extract database for url: " + url));
|
||||
}
|
||||
return matcher.group(1);
|
||||
}
|
||||
}
|
||||
234
core/src/main/java/jef/DbSet.java
Normal file
234
core/src/main/java/jef/DbSet.java
Normal file
@@ -0,0 +1,234 @@
|
||||
package jef;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.SelectExpression;
|
||||
import jef.expressions.TableExpression;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public class DbSet<T extends SerializableObject> implements Queryable<T> {
|
||||
@Getter
|
||||
private final Class<T> clazz;
|
||||
private final String table;
|
||||
|
||||
public DbSet(Class<T> clazz, String table) {
|
||||
this.clazz = clazz;
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTableAlias() {
|
||||
return String.valueOf((char) ('a' - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getExpression() {
|
||||
return new SelectExpression(List.of(DatabaseSelectAllExpression.INSTANCE), new TableExpression(table), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SELECT * FROM `" + table + "`";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<T> spliterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isParallel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> sequential() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> parallel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> unordered() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbSet<T> onClose(Runnable runnable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
//stream
|
||||
// @Override
|
||||
// public <R extends Serializable> Queryable<R> map(Function<? super T, ? extends R> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public IntStream mapToInt(ToIntFunction<? super T> toIntFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public LongStream mapToLong(ToLongFunction<? super T> toLongFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DoubleStream mapToDouble(ToDoubleFunction<? super T> toDoubleFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <R extends Serializable> Queryable<R> flatMap(Function<? super T, ? extends Stream<? extends R>> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public IntStream flatMapToInt(Function<? super T, ? extends IntStream> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public LongStream flatMapToLong(Function<? super T, ? extends LongStream> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DBSet<T> distinct() {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DBSet<T> sorted() {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DBSet<T> sorted(Comparator<? super T> comparator) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DBSet<T> peek(Consumer<? super T> consumer) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DBSet<T> limit(long l) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DBSet<T> skip(long l) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
//<editor-fold desc="terminating operations">
|
||||
// @Override
|
||||
// public void forEach(Consumer<? super T> consumer) {
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void forEachOrdered(Consumer<? super T> consumer) {
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Object[] toArray() {
|
||||
// return new Object[0];
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <A> A[] toArray(IntFunction<A[]> intFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public T reduce(T t, BinaryOperator<T> binaryOperator) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> reduce(BinaryOperator<T> binaryOperator) {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <U> U reduce(U u, BiFunction<U, ? super T, U> biFunction, BinaryOperator<U> binaryOperator) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <R extends Serializable> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> biConsumer, BiConsumer<R, R> biConsumer1) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <R extends Serializable, A> R collect(Collector<? super T, A, R> collector) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> min(Comparator<? super T> comparator) {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> max(Comparator<? super T> comparator) {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public long count() {
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean anyMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean allMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean noneMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> findFirst() {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> findAny() {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//</editor-fold>
|
||||
}
|
||||
18
core/src/main/java/jef/MigrationException.java
Normal file
18
core/src/main/java/jef/MigrationException.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package jef;
|
||||
|
||||
public class MigrationException extends Exception {
|
||||
public MigrationException() {
|
||||
}
|
||||
|
||||
public MigrationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MigrationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MigrationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
191
core/src/main/java/jef/Queryable.java
Normal file
191
core/src/main/java/jef/Queryable.java
Normal file
@@ -0,0 +1,191 @@
|
||||
package jef;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.OrderExpression;
|
||||
import jef.operations.FilterOp;
|
||||
import jef.operations.LimitOp;
|
||||
import jef.operations.SortOp;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public interface Queryable<T extends Serializable> {
|
||||
|
||||
//TODO documentation
|
||||
//TODO table alias thing still not ready
|
||||
String getTableAlias();
|
||||
|
||||
Expression getExpression();
|
||||
|
||||
String toString();
|
||||
|
||||
//stream functions
|
||||
default Iterator<T> iterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default Spliterator<T> spliterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean isParallel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default Queryable<T> sequential() {
|
||||
return this;
|
||||
}
|
||||
|
||||
default Queryable<T> parallel() {
|
||||
return this;
|
||||
}
|
||||
|
||||
default Queryable<T> unordered() {
|
||||
return this;
|
||||
}
|
||||
|
||||
default Queryable<T> onClose(Runnable runnable) {
|
||||
return this;
|
||||
}
|
||||
|
||||
default void close() {
|
||||
|
||||
}
|
||||
|
||||
//stream
|
||||
default Queryable<T> filter(SerializablePredicate<? super T> predicate) {
|
||||
return new FilterOp<>(this, predicate);
|
||||
}
|
||||
|
||||
// default <R extends Serializable> Queryable<R> map(Function<? super T, ? extends R> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default IntStream mapToInt(ToIntFunction<? super T> toIntFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default LongStream mapToLong(ToLongFunction<? super T> toLongFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default DoubleStream mapToDouble(ToDoubleFunction<? super T> toDoubleFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default <R extends Serializable> Queryable<R> flatMap(Function<? super T, ? extends Stream<? extends R>> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default IntStream flatMapToInt(Function<? super T, ? extends IntStream> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default LongStream flatMapToLong(Function<? super T, ? extends LongStream> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> function) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default Queryable<T> distinct() {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default SortOp<T> sorted() {//TODO sort by id
|
||||
// return new SortOp(this);
|
||||
// }
|
||||
|
||||
default <R> SortOp<T> sorted(SerializableFunction<? super T, R> fieldSelector) {
|
||||
return new SortOp<T>(this, fieldSelector, OrderExpression.SortDirection.ASCENDING);
|
||||
}
|
||||
|
||||
default <R> SortOp<T> sortedDescending(SerializableFunction<? super T, R> fieldSelector) {
|
||||
return new SortOp<T>(this, fieldSelector, OrderExpression.SortDirection.DESCENDING);
|
||||
}
|
||||
|
||||
//
|
||||
// default Queryable<T> peek(Consumer<? super T> consumer) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
default Queryable<T> limit(long l) {
|
||||
return new LimitOp<>(this, null, l);
|
||||
}
|
||||
|
||||
default Queryable<T> skip(long l) {
|
||||
return new LimitOp<>(this, l, null);
|
||||
}
|
||||
|
||||
//<editor-fold desc="terminating operations">
|
||||
// default void forEach(Consumer<? super T> consumer) {
|
||||
// }
|
||||
//
|
||||
// default void forEachOrdered(Consumer<? super T> consumer) {
|
||||
// }
|
||||
//
|
||||
// default Object[] toArray() {
|
||||
// return new Object[0];
|
||||
// }
|
||||
//
|
||||
// default <A> A[] toArray(IntFunction<A[]> intFunction) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default T reduce(T t, BinaryOperator<T> binaryOperator) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default Optional<T> reduce(BinaryOperator<T> binaryOperator) {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// default <U> U reduce(U u, BiFunction<U, ? super T, U> biFunction, BinaryOperator<U> binaryOperator) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default <R extends Serializable> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> biConsumer, BiConsumer<R, R> biConsumer1) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default <R extends Serializable, A> R collect(Collector<? super T, A, R> collector) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// default Optional<T> min(Comparator<? super T> comparator) {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// default Optional<T> max(Comparator<? super T> comparator) {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// default long count() {
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// default boolean anyMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// default boolean allMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// default boolean noneMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// default Optional<T> findFirst() {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//
|
||||
// default Optional<T> findAny() {
|
||||
// return Optional.empty();
|
||||
// }
|
||||
//</editor-fold>
|
||||
}
|
||||
229
core/src/main/java/jef/QueryableProxy.java
Normal file
229
core/src/main/java/jef/QueryableProxy.java
Normal file
@@ -0,0 +1,229 @@
|
||||
package jef;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
|
||||
public class QueryableProxy<T extends Serializable> implements Queryable<T> {
|
||||
private final Queryable<T> delegate;
|
||||
|
||||
public QueryableProxy(Queryable<T> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTableAlias() {
|
||||
return delegate.getTableAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getExpression() {
|
||||
return delegate.getExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<T> spliterator() {
|
||||
return delegate.spliterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isParallel() {
|
||||
return delegate.isParallel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queryable<T> sequential() {
|
||||
return delegate.sequential();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queryable<T> parallel() {
|
||||
return delegate.parallel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queryable<T> unordered() {
|
||||
return delegate.unordered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queryable<T> onClose(Runnable runnable) {
|
||||
return delegate.onClose(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
//stream
|
||||
@Override
|
||||
public Queryable<T> filter(SerializablePredicate<? super T> predicate) {
|
||||
return delegate.filter(predicate);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public <R extends Serializable> Queryable<R> map(Function<? super T, ? extends R> function) {
|
||||
// return delegate.map(function);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public IntStream mapToInt(ToIntFunction<? super T> toIntFunction) {
|
||||
// return delegate.mapToInt(toIntFunction);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public LongStream mapToLong(ToLongFunction<? super T> toLongFunction) {
|
||||
// return delegate.mapToLong(toLongFunction);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DoubleStream mapToDouble(ToDoubleFunction<? super T> toDoubleFunction) {
|
||||
// return delegate.mapToDouble(toDoubleFunction);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <R extends Serializable> Queryable<R> flatMap(Function<? super T, ? extends Stream<? extends R>> function) {
|
||||
// return delegate.flatMap(function);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public IntStream flatMapToInt(Function<? super T, ? extends IntStream> function) {
|
||||
// return delegate.flatMapToInt(function);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public LongStream flatMapToLong(Function<? super T, ? extends LongStream> function) {
|
||||
// return delegate.flatMapToLong(function);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> function) {
|
||||
// return delegate.flatMapToDouble(function);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Queryable<T> distinct() {
|
||||
// return delegate.distinct();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Queryable<T> sorted() {
|
||||
// return delegate.sorted();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Queryable<T> sorted(Comparator<? super T> comparator) {
|
||||
// return delegate.sorted(comparator);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Queryable<T> peek(Consumer<? super T> consumer) {
|
||||
// return delegate.peek(consumer);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Queryable<T> limit(long l) {
|
||||
// return delegate.limit(l);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Queryable<T> skip(long l) {
|
||||
// return delegate.skip(l);
|
||||
// }
|
||||
|
||||
//<editor-fold desc="terminating operations">
|
||||
// @Override
|
||||
// public void forEach(Consumer<? super T> consumer) {
|
||||
// delegate.forEach(consumer);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void forEachOrdered(Consumer<? super T> consumer) {
|
||||
// delegate.forEachOrdered(consumer);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Object[] toArray() {
|
||||
// return delegate.toArray();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <A> A[] toArray(IntFunction<A[]> intFunction) {
|
||||
// return delegate.toArray(intFunction);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public T reduce(T t, BinaryOperator<T> binaryOperator) {
|
||||
// return delegate.reduce(t, binaryOperator);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> reduce(BinaryOperator<T> binaryOperator) {
|
||||
// return delegate.reduce(binaryOperator);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <U> U reduce(U u, BiFunction<U, ? super T, U> biFunction, BinaryOperator<U> binaryOperator) {
|
||||
// return delegate.reduce(u, biFunction, binaryOperator);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <R extends Serializable> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> biConsumer, BiConsumer<R, R> biConsumer1) {
|
||||
// return delegate.collect(supplier, biConsumer, biConsumer1);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public <R extends Serializable, A> R collect(Collector<? super T, A, R> collector) {
|
||||
// return delegate.collect(collector);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> min(Comparator<? super T> comparator) {
|
||||
// return delegate.min(comparator);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> max(Comparator<? super T> comparator) {
|
||||
// return delegate.max(comparator);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public long count() {
|
||||
// return delegate.count();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean anyMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return delegate.anyMatch(SerializablePredicate);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean allMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return delegate.allMatch(SerializablePredicate);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean noneMatch(SerializablePredicate<? super T> SerializablePredicate) {
|
||||
// return delegate.noneMatch(SerializablePredicate);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> findFirst() {
|
||||
// return delegate.findFirst();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Optional<T> findAny() {
|
||||
// return delegate.findAny();
|
||||
// }
|
||||
//</editor-fold>
|
||||
}
|
||||
22
core/src/main/java/jef/asm/AsmParseException.java
Normal file
22
core/src/main/java/jef/asm/AsmParseException.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package jef.asm;
|
||||
|
||||
public class AsmParseException extends RuntimeException {
|
||||
public AsmParseException() {
|
||||
}
|
||||
|
||||
public AsmParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AsmParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AsmParseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AsmParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
15
core/src/main/java/jef/asm/AsmParseResult.java
Normal file
15
core/src/main/java/jef/asm/AsmParseResult.java
Normal file
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
}
|
||||
84
core/src/main/java/jef/asm/AsmParser.java
Normal file
84
core/src/main/java/jef/asm/AsmParser.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package jef.asm;
|
||||
|
||||
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.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 = predicate;
|
||||
this.method = null;
|
||||
}
|
||||
|
||||
public AsmParser(SerializableFunction<?, ?> function) {
|
||||
this.lambda = function;
|
||||
this.method = null;
|
||||
}
|
||||
|
||||
public AsmParser(Method method) {
|
||||
this.lambda = null;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public AsmParseResult parse() throws AsmParseException {
|
||||
try {
|
||||
if (this.lambda != null) {
|
||||
return parseLambdaExpression();
|
||||
} else if (this.method != null) {
|
||||
return parseMethodExpression();
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
} 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");
|
||||
|
||||
var cr = new ClassReader(is);
|
||||
var visiter = new FilterClassVisitor(Opcodes.ASM9, lambdaname, args);
|
||||
cr.accept(visiter, 0);
|
||||
|
||||
return visiter.getResult();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
54
core/src/main/java/jef/asm/FilterClassVisitor.java
Normal file
54
core/src/main/java/jef/asm/FilterClassVisitor.java
Normal file
@@ -0,0 +1,54 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
519
core/src/main/java/jef/asm/FilterMethodVisitor.java
Normal file
519
core/src/main/java/jef/asm/FilterMethodVisitor.java
Normal file
@@ -0,0 +1,519 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.NullExpression;
|
||||
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.expressions.selectable.DatabaseSelectAllExpression;
|
||||
import lombok.ToString;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
class FilterMethodVisitor extends MethodVisitor {
|
||||
private Stack<Expression> varStack = new Stack<>();
|
||||
private final String[] parameterClasses;
|
||||
private final Object[] args;
|
||||
private Expression lambdaExpr;
|
||||
private final Set<Field> accessedMembers = new HashSet<>();
|
||||
|
||||
protected FilterMethodVisitor(int api, String className, String descriptor, Object[] args) {
|
||||
super(api);
|
||||
|
||||
//parameters
|
||||
var types = Type.getMethodType(descriptor).getArgumentTypes();
|
||||
if (types.length == 0) {// class method //TODO this is dirty, as only works for parameterless class methods
|
||||
this.args = new Object[]{null};
|
||||
parameterClasses = new String[]{className};
|
||||
} 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
|
||||
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
||||
System.out.println("local var: " + name);
|
||||
super.visitLocalVariable(name, descriptor, signature, start, end, index);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
|
||||
System.out.println("field insn: " + ops.getOrDefault(opcode, "" + opcode) + ", " + owner + ", " + name + ", " + descriptor);
|
||||
if (opcode == Opcodes.GETFIELD) {
|
||||
var v = varStack.pop();
|
||||
if (v instanceof ParameterExpression p) {
|
||||
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));
|
||||
} else {
|
||||
throw new RuntimeException("field insn: unsupported GETFIELD expression");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("field insn: unsupported GETFIELD op");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("field insn: unsupported opcode " + ops.getOrDefault(opcode, "" + opcode));
|
||||
}
|
||||
super.visitFieldInsn(opcode, owner, name, descriptor);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
|
||||
System.out.println("method insn: " + ops.getOrDefault(opcode, "" + opcode) + ", " + owner + ", " + name + ", " + descriptor);
|
||||
if (opcode == Opcodes.INVOKESTATIC && name.equals("valueOf")) {
|
||||
//ignore boxed primitive types
|
||||
var boxedPrimitiveClasses = Set.of("java/lang/Boolean", "java/lang/Integer", "java/lang/Long", "java/lang/Float", "java/lang/Double");
|
||||
if (boxedPrimitiveClasses.contains(owner)) {
|
||||
//do nothing
|
||||
} else {
|
||||
//do something
|
||||
throw new RuntimeException("method insn: unsupported opcode " + ops.getOrDefault(opcode, "" + opcode));
|
||||
}
|
||||
} else if (opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEVIRTUAL) {
|
||||
try {
|
||||
if (name.equals("contains")
|
||||
&& owner.startsWith("java/util/")
|
||||
&& Collection.class.isAssignableFrom(Class.forName(owner.replace("/", ".")))) {
|
||||
var element = varStack.pop();
|
||||
var collection = varStack.pop();
|
||||
varStack.push(new BinaryExpression(element, collection, BinaryExpression.Operator.IN));
|
||||
} else if (name.equals("equals")
|
||||
&& owner.equals("java/lang/String")) {
|
||||
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()) {
|
||||
var v = varStack.pop();
|
||||
if (v instanceof ParameterExpression p) {
|
||||
if (p.isInput()) {
|
||||
varStack.push(new IntermediateFieldExpression(field.get().getName(), descriptor.substring(2)));
|
||||
} else {
|
||||
throw new RuntimeException("method insn: getter support only to predicate parameter");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("method insn: getter support expected ParameterExpression but found" + v.getClass().getSimpleName());
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("method insn: could not find field for getter " + name + " in " + owner);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("method insn: unsupported function " + name + " in " + owner);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException re) {
|
||||
throw re;
|
||||
}
|
||||
throw new RuntimeException("method insn: ", e);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("method insn: unsupported opcode " + ops.getOrDefault(opcode, "" + opcode));
|
||||
}
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitVarInsn(int opcode, int varIndex) {
|
||||
System.out.println("var insn: " + ops.getOrDefault(opcode, "" + opcode) + ", " + varIndex);
|
||||
if (opcode == Opcodes.ALOAD) {
|
||||
var value = varIndex == parameterClasses.length - 1 ? null : args[varIndex];
|
||||
varStack.push(new ParameterExpression(varIndex, value, varIndex == parameterClasses.length - 1, parameterClasses[varIndex]));
|
||||
} else {
|
||||
throw new RuntimeException("var insn: unsupported opcode " + ops.getOrDefault(opcode, "" + opcode));
|
||||
}
|
||||
super.visitVarInsn(opcode, varIndex);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
System.out.println("insn: " + ops.getOrDefault(opcode, "" + opcode));
|
||||
switch (opcode) {
|
||||
case Opcodes.ICONST_0, Opcodes.LCONST_0, Opcodes.FCONST_0, Opcodes.DCONST_0 -> varStack.push(ConstantExpression.V0);
|
||||
case Opcodes.ICONST_1, Opcodes.LCONST_1, Opcodes.FCONST_1, Opcodes.DCONST_1 -> varStack.push(ConstantExpression.V1);
|
||||
case Opcodes.ICONST_2, Opcodes.FCONST_2 -> varStack.push(ConstantExpression.V2);
|
||||
case Opcodes.ICONST_3 -> varStack.push(ConstantExpression.V3);
|
||||
case Opcodes.ICONST_4 -> varStack.push(ConstantExpression.V4);
|
||||
case Opcodes.ICONST_5 -> varStack.push(ConstantExpression.V5);
|
||||
case Opcodes.IRETURN, Opcodes.LRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.ARETURN -> {
|
||||
//collapse conditions
|
||||
for (int i = condStack.size() - 1; i >= 0; i--) {
|
||||
condStack.get(i).e1 = varStack.pop();
|
||||
varStack = condStack.get(i).varStack;
|
||||
evalCond(condStack.get(i));
|
||||
}
|
||||
// condStack.clear();
|
||||
lambdaExpr = varStack.pop();
|
||||
}
|
||||
case Opcodes.FCMPL, Opcodes.FCMPG, Opcodes.DCMPL, Opcodes.DCMPG, Opcodes.LCMP -> {
|
||||
var var2 = varStack.pop();
|
||||
var var1 = varStack.pop();
|
||||
varStack.push(new BinaryExpression(var1, var2, BinaryExpression.Operator.NE));
|
||||
}
|
||||
default -> throw new RuntimeException("insn: unsupported opcode " + ops.getOrDefault(opcode, "" + opcode));
|
||||
}
|
||||
|
||||
super.visitInsn(opcode);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@ToString
|
||||
class Cond {
|
||||
final int opcode;
|
||||
final Label condTarget;
|
||||
Label gotoTarget;
|
||||
final Stack<Expression> varStack;
|
||||
Expression e1;
|
||||
Expression e2;
|
||||
|
||||
public Cond(int opcode, Label condTarget, Stack<Expression> varStack) {
|
||||
this.opcode = opcode;
|
||||
this.condTarget = condTarget;
|
||||
this.varStack = varStack;
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedList<Cond> condStack = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
System.out.println("jump insn: " + ops.getOrDefault(opcode, "" + opcode) + ", " + label);
|
||||
|
||||
switch (opcode) {
|
||||
case Opcodes.IFEQ:
|
||||
case Opcodes.IFNE:
|
||||
case Opcodes.IF_ICMPNE:
|
||||
case Opcodes.IF_ICMPEQ:
|
||||
case Opcodes.IF_ICMPGE:
|
||||
case Opcodes.IF_ICMPLE:
|
||||
case Opcodes.IF_ICMPGT:
|
||||
case Opcodes.IF_ICMPLT:
|
||||
case Opcodes.IFNULL:
|
||||
case Opcodes.IFNONNULL:
|
||||
handleLextLabel();
|
||||
condStack.add(new Cond(opcode, label, varStack));
|
||||
varStack = new Stack<>();
|
||||
System.out.println("jump new stack");
|
||||
break;
|
||||
case Opcodes.GOTO:
|
||||
handleLextLabel();
|
||||
System.out.println("goto new stack");
|
||||
condStack.getLast().gotoTarget = label;
|
||||
condStack.getLast().e1 = varStack.pop();
|
||||
varStack = new Stack<>();
|
||||
break;
|
||||
default: {
|
||||
throw new RuntimeException("jump insn: unsupported opcode " + ops.getOrDefault(opcode, "" + opcode));
|
||||
}
|
||||
}
|
||||
|
||||
// int IFEQ = 153; // visitJumpInsn
|
||||
// int IFNE = 154; // -
|
||||
// int IFLT = 155; // -
|
||||
// int IFGE = 156; // -
|
||||
// int IFGT = 157; // -
|
||||
// int IFLE = 158; // -
|
||||
// int IF_ICMPEQ = 159; // -
|
||||
// int IF_ICMPNE = 160; // -
|
||||
// int IF_ICMPLT = 161; // -
|
||||
// int IF_ICMPGE = 162; // -
|
||||
// int IF_ICMPGT = 163; // -
|
||||
// int IF_ICMPLE = 164; // -
|
||||
// int IF_ACMPEQ = 165; // -
|
||||
// int IF_ACMPNE = 166; // -
|
||||
// int GOTO = 167; // -
|
||||
// int JSR = 168; // -
|
||||
// int RET = 169; // visitVarInsn
|
||||
|
||||
super.visitJumpInsn(opcode, label);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
List<Cond> nextLabelConds = new ArrayList<>();
|
||||
|
||||
private void handleLextLabel() {
|
||||
if (!nextLabelConds.isEmpty()) {
|
||||
var e1 = varStack.peek();
|
||||
for (int i = 0; i < nextLabelConds.size(); i++) {
|
||||
// for (int i = nextLabelConds.size() - 1; i >= 0; i--) {
|
||||
var cond = nextLabelConds.get(i);
|
||||
cond.e2 = e1;
|
||||
// for (int j = 0; j < condStack.size() && condStack.get(j) != cond; j++) {
|
||||
// condStack.get(j).e1 = e1;
|
||||
// }
|
||||
// condStack.remove(cond);
|
||||
// varStack = cond.varStack;
|
||||
// evalCond(cond);
|
||||
}
|
||||
nextLabelConds = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLabel(Label label) {
|
||||
System.out.println("label: " + label);
|
||||
handleLextLabel();
|
||||
|
||||
// System.out.println("------>");
|
||||
// System.out.println("condstack: " + condStack);
|
||||
List<Cond> conds = condStack.stream().filter(e -> e.gotoTarget != null && e.gotoTarget.equals(label)).toList();
|
||||
nextLabelConds = condStack.stream().filter(e -> e.gotoTarget == null && e.condTarget.equals(label)).toList();
|
||||
|
||||
// List<Cond> conds = condStack.stream().filter(e -> e.gotoTarget != null
|
||||
// ? e.gotoTarget.equals(label)
|
||||
// : e.condTarget.equals(label)//bad
|
||||
// ).toList();
|
||||
if (!conds.isEmpty()) {
|
||||
Expression e2 = varStack.pop();
|
||||
for (int i = conds.size() - 1; i >= 0; i--) {
|
||||
var cond = conds.get(i);
|
||||
// System.out.println("condstack pop " + cond);
|
||||
cond.e2 = e2;
|
||||
//also set e2 for all outer conds
|
||||
// for (int j = 0; j < condStack.size() && condStack.get(j) != cond; j++) {
|
||||
// condStack.get(j).e2 = e2;
|
||||
// }
|
||||
condStack.remove(cond);
|
||||
varStack = cond.varStack;
|
||||
evalCond(cond);
|
||||
}
|
||||
}
|
||||
// System.out.println("<------");
|
||||
super.visitLabel(label);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
Label collapseAfterNextInstruction = null;
|
||||
|
||||
private void evalCond(Cond cond) {
|
||||
var right = varStack.pop();
|
||||
|
||||
// System.out.println("left: " + left);
|
||||
// System.out.println("cond.e1: " + cond.e1);
|
||||
// System.out.println("cond.e2: " + cond.e2);
|
||||
boolean wrapInTernary = cond.e1 != ConstantExpression.V1 || cond.e2 != ConstantExpression.V0;
|
||||
// boolean wrapInTernary = true;
|
||||
Expression expr;
|
||||
// 153, "IFEQ",
|
||||
// 154, "IFNE",
|
||||
// 155, "IFLT",
|
||||
// 156, "IFGE",
|
||||
// 157, "IFGT",
|
||||
// 158, "IFLE",
|
||||
switch (cond.opcode) {
|
||||
case Opcodes.IFEQ:
|
||||
expr = right;
|
||||
break;
|
||||
case Opcodes.IFNE:
|
||||
expr = new UnaryExpression(right, UnaryExpression.Operator.NOT);
|
||||
break;
|
||||
case Opcodes.IFNULL:
|
||||
expr = new BinaryExpression(right, new UnaryExpression(NullExpression.INSTANCE, UnaryExpression.Operator.NOT), BinaryExpression.Operator.IS);
|
||||
break;
|
||||
case Opcodes.IFNONNULL:
|
||||
expr = new BinaryExpression(right, NullExpression.INSTANCE, BinaryExpression.Operator.IS);
|
||||
break;
|
||||
|
||||
default: {
|
||||
var left = varStack.pop();
|
||||
switch (cond.opcode) {
|
||||
case Opcodes.IF_ICMPEQ:
|
||||
expr = new BinaryExpression(left, right, BinaryExpression.Operator.NE);
|
||||
break;
|
||||
case Opcodes.IF_ICMPNE:
|
||||
expr = new BinaryExpression(left, right, BinaryExpression.Operator.EQ);
|
||||
break;
|
||||
case Opcodes.IF_ICMPLT:
|
||||
expr = new BinaryExpression(left, right, BinaryExpression.Operator.GE);
|
||||
break;
|
||||
case Opcodes.IF_ICMPGE:
|
||||
expr = new BinaryExpression(left, right, BinaryExpression.Operator.LT);
|
||||
break;
|
||||
case Opcodes.IF_ICMPGT:
|
||||
expr = new BinaryExpression(left, right, BinaryExpression.Operator.LE);
|
||||
break;
|
||||
case Opcodes.IF_ICMPLE:
|
||||
expr = new BinaryExpression(left, right, BinaryExpression.Operator.GT);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("jump insn: unsupported opcode " + ops.getOrDefault(cond.opcode, "" + cond.opcode));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wrapInTernary) {
|
||||
expr = new TernaryExpression(expr, cond.e1, cond.e2);
|
||||
}
|
||||
|
||||
varStack.push(expr);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAttribute(Attribute attribute) {
|
||||
System.out.println("attr: " + attribute);
|
||||
super.visitAttribute(attribute);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLdcInsn(Object value) {
|
||||
System.out.println("ldc: " + value);
|
||||
varStack.push(new ConstantExpression(value));
|
||||
super.visitLdcInsn(value);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
System.out.println("end");
|
||||
|
||||
super.visitEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIntInsn(int opcode, int operand) {
|
||||
System.out.println("intinsn: " + ops.getOrDefault(opcode, "" + opcode) + ", " + operand);
|
||||
switch (opcode) {
|
||||
case Opcodes.SIPUSH:
|
||||
varStack.push(new ConstantExpression(operand));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("intinsn: unsupported opcode " + ops.getOrDefault(opcode, "" + opcode));
|
||||
}
|
||||
super.visitIntInsn(opcode, operand);
|
||||
debugExpr();
|
||||
}
|
||||
|
||||
private Map<Integer, String> ops = createOpsMap(
|
||||
3, "ICONST_0",
|
||||
4, "ICONST_1",
|
||||
5, "ICONST_2",
|
||||
6, "ICONST_3",
|
||||
7, "ICONST_4",
|
||||
8, "ICONST_5",
|
||||
9, "LCONST_0",
|
||||
10, "LCONST_1",
|
||||
11, "FCONST_0",
|
||||
12, "FCONST_1",
|
||||
13, "FCONST_2",
|
||||
14, "DCONST_0",
|
||||
15, "DCONST_1",
|
||||
17, "SIPUSH",
|
||||
25, "ALOAD",
|
||||
|
||||
//jmp
|
||||
148, "LCMP",
|
||||
149, "FCMPL",
|
||||
150, "FCMPG",
|
||||
151, "DCMPL",
|
||||
152, "DCMPG",
|
||||
153, "IFEQ",
|
||||
154, "IFNE",
|
||||
155, "IFLT",
|
||||
156, "IFGE",
|
||||
157, "IFGT",
|
||||
158, "IFLE",
|
||||
159, "IF_ICMPEQ",
|
||||
160, "IF_ICMPNE",
|
||||
161, "IF_ICMPLT",
|
||||
162, "IF_ICMPGE",
|
||||
163, "IF_ICMPGT",
|
||||
164, "IF_ICMPLE",
|
||||
165, "IF_ACMPEQ",
|
||||
166, "IF_ACMPNE",
|
||||
167, "GOTO",
|
||||
168, "JSR",
|
||||
169, "RET",
|
||||
|
||||
172, "IRETURN",
|
||||
173, "LRETURN",
|
||||
174, "FRETURN",
|
||||
175, "DRETURN",
|
||||
176, "ARETURN",
|
||||
|
||||
//field
|
||||
180, "GETFIELD",
|
||||
181, "PUTFIELD",
|
||||
182, "INVOKEVIRTUAL",
|
||||
183, "INVOKESPECIAL",
|
||||
184, "INVOKESTATIC",
|
||||
185, "INVOKEINTERFACE",
|
||||
186, "INVOKEDYNAMIC",
|
||||
198, "IFNULL",
|
||||
199, "IFNONNULL"
|
||||
);
|
||||
|
||||
private static Map<Integer, String> createOpsMap(Object... o) {
|
||||
return IntStream.range(0, o.length / 2).boxed().collect(Collectors.toMap(i -> (int) o[i * 2], i -> (String) o[i * 2 + 1]));
|
||||
}
|
||||
|
||||
private void debugExpr() {
|
||||
if (!varStack.isEmpty()) {
|
||||
System.out.println("-------------------> " + new WhereExpression(new SelectExpression(List.of(DatabaseSelectAllExpression.INSTANCE), new TableExpression("dummy"), ""), varStack.peek()));
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<Field> findField(String owner, String name) throws Exception {
|
||||
var cls = Class.forName(owner.replace("/", "."));
|
||||
String fieldname = name.substring(3, 4).toLowerCase(Locale.ROOT) + name.substring(4);
|
||||
var field = cls.getDeclaredField(fieldname);
|
||||
if (field != null) {
|
||||
return Optional.of(field);
|
||||
}
|
||||
for (Field declaredField : cls.getDeclaredFields()) {
|
||||
if (declaredField.getName().equalsIgnoreCase(fieldname)) {
|
||||
return Optional.of(declaredField);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
40
core/src/main/java/jef/asm/OptimizedAsmParser.java
Normal file
40
core/src/main/java/jef/asm/OptimizedAsmParser.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package jef.asm;
|
||||
|
||||
import jef.expressions.modifier.ExpressionOptimizerBottomUp;
|
||||
import jef.expressions.modifier.IConst0Fixer;
|
||||
import jef.expressions.modifier.TernaryRewriter;
|
||||
import jef.serializable.SerializableFunction;
|
||||
import jef.serializable.SerializablePredicate;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@Getter
|
||||
public class OptimizedAsmParser extends AsmParser {
|
||||
|
||||
public OptimizedAsmParser(SerializablePredicate<?> predicate) {
|
||||
super(predicate);
|
||||
}
|
||||
|
||||
public OptimizedAsmParser(SerializableFunction<?, ?> function) {
|
||||
super(function);
|
||||
}
|
||||
|
||||
public OptimizedAsmParser(Method method) {
|
||||
super(method);
|
||||
}
|
||||
|
||||
public AsmParseResult parse() throws AsmParseException {
|
||||
var result = super.parse();
|
||||
var expr = result.getExpression();
|
||||
|
||||
System.out.println(expr);
|
||||
expr = new TernaryRewriter().modify(expr);
|
||||
System.out.println(expr);
|
||||
expr = new IConst0Fixer().modify(expr);
|
||||
System.out.println(expr);
|
||||
expr = new ExpressionOptimizerBottomUp().modify(expr);
|
||||
|
||||
return new AsmParseResult(expr, result.getAccessedFields());
|
||||
}
|
||||
}
|
||||
53
core/src/main/java/jef/expressions/AndExpression.java
Normal file
53
core/src/main/java/jef/expressions/AndExpression.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class AndExpression implements Expression {
|
||||
private final List<Expression> exprs;
|
||||
|
||||
public AndExpression(Expression... exprs) {
|
||||
this.exprs = List.of(exprs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.AND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.LOGIC_AND;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean equals(Object o) {
|
||||
// if (this == o) return true;
|
||||
// if (o == null || getClass() != o.getClass()) return false;
|
||||
// AndExpression that = (AndExpression) o;
|
||||
// return exprs.equals(that.exprs);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public int hashCode() {
|
||||
// return Objects.hash(exprs);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return exprs.stream().map(e -> {
|
||||
if (e.getPriority().getValue() < getPriority().getValue()) {
|
||||
return "(" + e + ")";
|
||||
} else {
|
||||
return e.toString();
|
||||
}
|
||||
}).collect(Collectors.joining(" AND "));
|
||||
}
|
||||
}
|
||||
92
core/src/main/java/jef/expressions/BinaryExpression.java
Normal file
92
core/src/main/java/jef/expressions/BinaryExpression.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class BinaryExpression implements Expression {
|
||||
private final Expression left;
|
||||
private final Expression right;
|
||||
private final Operator operator;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.BINARY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return switch (operator) {
|
||||
case EQ, NE, IS -> Priority.EQUALITY;
|
||||
case LT, LE, GT, GE -> Priority.RELATIONAL;
|
||||
case IN -> Priority.RELATIONAL; //or equality?
|
||||
default -> throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String ret = "";
|
||||
if (left.getPriority().getValue() < getPriority().getValue()) {
|
||||
ret += "(" + left + ")";
|
||||
} else {
|
||||
ret += left.toString();
|
||||
}
|
||||
ret += " " + operator + " ";
|
||||
if (right.getPriority().getValue() < getPriority().getValue()) {
|
||||
ret += "(" + right + ")";
|
||||
} else {
|
||||
ret += right.toString();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum Operator {
|
||||
EQ("="),
|
||||
NE("<>"),
|
||||
LT("<"),
|
||||
LE("<="),
|
||||
GT(">"),
|
||||
GE(">="),
|
||||
//
|
||||
// ADD("+"),
|
||||
// SUB("-"),
|
||||
|
||||
IN("IN"),
|
||||
IS("IS"),
|
||||
;
|
||||
private final String string;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
private static final Map<Operator, Operator> INVERSION = Map.of(
|
||||
EQ, NE,
|
||||
NE, EQ,
|
||||
LT, GE,
|
||||
GE, LT,
|
||||
LE, GT,
|
||||
GT, LE
|
||||
|
||||
// ADD, SUB,
|
||||
// SUB, ADD
|
||||
);
|
||||
|
||||
public Operator invert() {
|
||||
return INVERSION.get(this);
|
||||
}
|
||||
|
||||
public boolean isInvertible() {
|
||||
return INVERSION.containsKey(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
core/src/main/java/jef/expressions/ConstantExpression.java
Normal file
37
core/src/main/java/jef/expressions/ConstantExpression.java
Normal file
@@ -0,0 +1,37 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
53
core/src/main/java/jef/expressions/Expression.java
Normal file
53
core/src/main/java/jef/expressions/Expression.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
public interface Expression {
|
||||
Type getType();
|
||||
|
||||
Priority getPriority();
|
||||
|
||||
public enum Type {
|
||||
AND,
|
||||
BINARY,
|
||||
CONSTANT,
|
||||
FIELD,
|
||||
FUNCTION,
|
||||
INTERMEDIATE_FIELD,
|
||||
LIMIT,
|
||||
NULL,
|
||||
OR,
|
||||
ORDER,
|
||||
PARAMETER,
|
||||
SELECT,
|
||||
TABLE,
|
||||
TERNARY,
|
||||
UNARY,
|
||||
WHERE,
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum Priority {
|
||||
UNDEFINED(0),
|
||||
ASSIGNMENT(1), // =, +=, -=, *=, /=
|
||||
TERNARY(2), // ? :
|
||||
LOGIC_OR(3), // ||
|
||||
LOGIC_AND(4), // &&
|
||||
BITWISE_OR(5), // |
|
||||
BITWISE_XOR(6), // ^
|
||||
BITWISE_AND(7), // &
|
||||
EQUALITY(8), // ==, !=
|
||||
RELATIONAL(9), // <, <=, =>, >
|
||||
BIT_SHIFT(10), // <<, >>
|
||||
ADD_SUB(13), // +, -
|
||||
MUL_DIV_MOD(14), // *, /, %
|
||||
UNARY_PRE(15), // +, -, !, ~, --i, ++i, function calls
|
||||
UNARY_POST(16), // i++, i--
|
||||
CONSTANT(17), // constant values
|
||||
;
|
||||
|
||||
private final int value;
|
||||
}
|
||||
}
|
||||
34
core/src/main/java/jef/expressions/FieldExpression.java
Normal file
34
core/src/main/java/jef/expressions/FieldExpression.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package jef.expressions;
|
||||
|
||||
import jef.expressions.selectable.SelectableExpression;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class FieldExpression extends ConstantExpression implements SelectableExpression, Expression {
|
||||
private final String schema;
|
||||
private final String table;
|
||||
private final String name;
|
||||
private final String classDescriptor;
|
||||
|
||||
public FieldExpression(String schema, String table, String name, String classDescriptor) {
|
||||
super(name);
|
||||
this.schema = schema;
|
||||
this.table = table;
|
||||
this.name = name;
|
||||
this.classDescriptor = classDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.FIELD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (schema != null && !schema.isEmpty() ? "`" + schema + "`." : "")
|
||||
+ (table != null && !table.isEmpty() ? "`" + table + "`." : "")
|
||||
+ "`" + name + "`";
|
||||
}
|
||||
}
|
||||
32
core/src/main/java/jef/expressions/FunctionExpression.java
Normal file
32
core/src/main/java/jef/expressions/FunctionExpression.java
Normal file
@@ -0,0 +1,32 @@
|
||||
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(", ")) + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package jef.expressions;
|
||||
|
||||
import jef.expressions.selectable.SelectableExpression;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class IntermediateFieldExpression extends ConstantExpression implements SelectableExpression, Expression {
|
||||
private final String name;
|
||||
private final String classDescriptor;
|
||||
|
||||
public IntermediateFieldExpression(String name, String classDescriptor) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
this.classDescriptor = classDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.INTERMEDIATE_FIELD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "`" + name + "`";
|
||||
}
|
||||
}
|
||||
36
core/src/main/java/jef/expressions/LimitExpression.java
Normal file
36
core/src/main/java/jef/expressions/LimitExpression.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class LimitExpression implements Expression {
|
||||
private final Expression expr;
|
||||
private final Long start;
|
||||
private final Long count;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.LIMIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var ret = expr.toString();
|
||||
if (start != null) {
|
||||
ret += " OFFSET " + start;
|
||||
}
|
||||
if (count != null) {
|
||||
ret += " LIMIT " + count;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
22
core/src/main/java/jef/expressions/NullExpression.java
Normal file
22
core/src/main/java/jef/expressions/NullExpression.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class NullExpression extends ConstantExpression {
|
||||
public static final NullExpression INSTANCE = new NullExpression();
|
||||
|
||||
private NullExpression() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.NULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NULL";
|
||||
}
|
||||
}
|
||||
40
core/src/main/java/jef/expressions/OrExpression.java
Normal file
40
core/src/main/java/jef/expressions/OrExpression.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class OrExpression implements Expression {
|
||||
private final List<Expression> exprs;
|
||||
|
||||
public OrExpression(Expression... exprs) {
|
||||
this.exprs = List.of(exprs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.OR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.LOGIC_OR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return exprs.stream().map(e -> {
|
||||
if (e.getPriority().getValue() < getPriority().getValue()) {
|
||||
return "(" + e + ")";
|
||||
} else {
|
||||
return e.toString();
|
||||
}
|
||||
}).collect(Collectors.joining(" OR "));
|
||||
}
|
||||
}
|
||||
56
core/src/main/java/jef/expressions/OrderExpression.java
Normal file
56
core/src/main/java/jef/expressions/OrderExpression.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class OrderExpression implements Expression {
|
||||
private final Expression expr;
|
||||
private final List<Sort> sorts;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var ret = expr.toString();
|
||||
if (!sorts.isEmpty()) {
|
||||
var sortStrings = new ArrayList<String>(sorts.size());
|
||||
for (Sort sort : sorts) {
|
||||
sortStrings.add(sort.getExpr() + " " + sort.getDirection().getString());
|
||||
}
|
||||
ret += " ORDER BY " + String.join(", ", sortStrings);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public static class Sort {
|
||||
private final FieldExpression expr;
|
||||
private final SortDirection direction;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SortDirection {
|
||||
ASCENDING("ASC"),
|
||||
DESCENDING("DESC"),
|
||||
;
|
||||
private final String string;
|
||||
}
|
||||
}
|
||||
39
core/src/main/java/jef/expressions/ParameterExpression.java
Normal file
39
core/src/main/java/jef/expressions/ParameterExpression.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class ParameterExpression extends ConstantExpression implements Expression {
|
||||
private final int index;
|
||||
private final boolean isInput;
|
||||
private final String classDescriptor;
|
||||
|
||||
public ParameterExpression(int index, Object value, boolean isInput, String classDescriptor) {
|
||||
super(value);
|
||||
this.index = index;
|
||||
this.isInput = isInput;
|
||||
this.classDescriptor = classDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.PARAMETER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isInput) {
|
||||
return "param #" + index;
|
||||
} else if (this.value == null) {
|
||||
return "null";
|
||||
} else if (this.value instanceof Collection) {
|
||||
return "(" + ((Collection<?>) this.value).stream().map(e -> e == null ? "NULL" : (e instanceof String ? "\"" + e + "\"" : e.toString())).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
36
core/src/main/java/jef/expressions/SelectExpression.java
Normal file
36
core/src/main/java/jef/expressions/SelectExpression.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package jef.expressions;
|
||||
|
||||
import jef.expressions.selectable.SelectableExpression;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class SelectExpression implements Expression {
|
||||
private final List<SelectableExpression> fields;
|
||||
private final Expression from;
|
||||
private final String fromAlias;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.SELECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SELECT " + fields.stream().map(SelectableExpression::toString).collect(Collectors.joining(", ")) + " FROM "
|
||||
+ (!(from instanceof TableExpression) ? "(" + from + ")" : from)
|
||||
+ ((fromAlias == null || fromAlias.isBlank()) ? "" : " " + fromAlias);
|
||||
}
|
||||
|
||||
}
|
||||
30
core/src/main/java/jef/expressions/TableExpression.java
Normal file
30
core/src/main/java/jef/expressions/TableExpression.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class TableExpression extends ConstantExpression {
|
||||
private final String name;
|
||||
|
||||
public TableExpression(String name) {
|
||||
super(name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.TABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return (String) super.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "`" + name + "`";
|
||||
}
|
||||
}
|
||||
29
core/src/main/java/jef/expressions/TernaryExpression.java
Normal file
29
core/src/main/java/jef/expressions/TernaryExpression.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class TernaryExpression implements Expression {
|
||||
private final Expression cond;
|
||||
private final Expression whenTrue;
|
||||
private final Expression whenFalse;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.TERNARY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.TERNARY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IF(" + cond + ", " + whenTrue + ", " + whenFalse + ")";
|
||||
}
|
||||
}
|
||||
47
core/src/main/java/jef/expressions/UnaryExpression.java
Normal file
47
core/src/main/java/jef/expressions/UnaryExpression.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class UnaryExpression implements Expression {
|
||||
private final Expression expr;
|
||||
private final Operator operator;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.UNARY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNARY_PRE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (expr.getPriority().getValue() < getPriority().getValue()) {
|
||||
return operator + " (" + expr + ")";
|
||||
} else {
|
||||
return operator + " " + expr;
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum Operator {
|
||||
NOT("NOT"),
|
||||
// NEG("-"),
|
||||
// POS("+"),
|
||||
;
|
||||
private final String string;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
core/src/main/java/jef/expressions/WhereExpression.java
Normal file
28
core/src/main/java/jef/expressions/WhereExpression.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package jef.expressions;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class WhereExpression implements Expression {
|
||||
private Expression queryable;
|
||||
private Expression where;
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.WHERE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return queryable + " WHERE " + where;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package jef.expressions.modifier;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
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 java.util.ArrayList;
|
||||
|
||||
|
||||
public abstract class ExpressionModifier {
|
||||
|
||||
public Expression modify(Expression expr) {
|
||||
return switch (expr.getType()) {
|
||||
case AND -> modifyAnd((AndExpression) expr);
|
||||
case BINARY -> modifyBinary((BinaryExpression) expr);
|
||||
case CONSTANT -> modifyConstant((ConstantExpression) expr);
|
||||
case FIELD -> modifyField((FieldExpression) expr);
|
||||
case FUNCTION -> modifyFunction((FunctionExpression) expr);
|
||||
case INTERMEDIATE_FIELD -> modifyIntermediateField((IntermediateFieldExpression) expr);
|
||||
case LIMIT -> modifyLimit((LimitExpression) expr);
|
||||
case NULL -> modifyNull((NullExpression) expr);
|
||||
case OR -> modifyOr((OrExpression) expr);
|
||||
case ORDER -> modifyOrder((OrderExpression) expr);
|
||||
case PARAMETER -> modifyParameter((ParameterExpression) expr);
|
||||
case SELECT -> modifySelect((SelectExpression) expr);
|
||||
case TABLE -> modifyTable((TableExpression) expr);
|
||||
case TERNARY -> modifyTernary((TernaryExpression) expr);
|
||||
case UNARY -> modifyUnary((UnaryExpression) expr);
|
||||
case WHERE -> modifyWhere((WhereExpression) expr);
|
||||
default -> throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
|
||||
public Expression modifyAnd(AndExpression expr) {
|
||||
var exprs = new ArrayList<Expression>(expr.getExprs().size());
|
||||
for (Expression e : expr.getExprs()) {
|
||||
exprs.add(modify(e));
|
||||
}
|
||||
return new AndExpression(exprs);
|
||||
}
|
||||
|
||||
public Expression modifyBinary(BinaryExpression expr) {
|
||||
return new BinaryExpression(modify(expr.getLeft()), modify(expr.getRight()), expr.getOperator());
|
||||
}
|
||||
|
||||
public Expression modifyConstant(ConstantExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifyField(FieldExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifyFunction(FunctionExpression expr) {
|
||||
return new FunctionExpression(expr.getFunction(), expr.getParameters().stream().map(this::modify).toList());
|
||||
}
|
||||
|
||||
public Expression modifyIntermediateField(IntermediateFieldExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifyLimit(LimitExpression expr) {
|
||||
return new LimitExpression(modify(expr.getExpr()), expr.getStart(), expr.getCount());
|
||||
}
|
||||
|
||||
public Expression modifyNull(NullExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifyOr(OrExpression expr) {
|
||||
var exprs = new ArrayList<Expression>(expr.getExprs().size());
|
||||
for (Expression e : expr.getExprs()) {
|
||||
exprs.add(modify(e));
|
||||
}
|
||||
return new OrExpression(exprs);
|
||||
}
|
||||
|
||||
public Expression modifyParameter(ParameterExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifyOrder(OrderExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifySelect(SelectExpression expr) {
|
||||
return new SelectExpression(expr.getFields(), modify(expr.getFrom()), expr.getFromAlias());
|
||||
}
|
||||
|
||||
public Expression modifyTable(TableExpression expr) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
public Expression modifyTernary(TernaryExpression expr) {
|
||||
return new TernaryExpression(modify(expr.getCond()), modify(expr.getWhenTrue()), modify(expr.getWhenFalse()));
|
||||
}
|
||||
|
||||
public Expression modifyUnary(UnaryExpression expr) {
|
||||
return new UnaryExpression(modify(expr.getExpr()), expr.getOperator());
|
||||
}
|
||||
|
||||
public Expression modifyWhere(WhereExpression expr) {
|
||||
return new WhereExpression(modify(expr.getQueryable()), modify(expr.getWhere()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package jef.expressions.modifier;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.LimitExpression;
|
||||
import jef.expressions.OrExpression;
|
||||
import jef.expressions.OrderExpression;
|
||||
import jef.expressions.SelectExpression;
|
||||
import jef.expressions.TernaryExpression;
|
||||
import jef.expressions.UnaryExpression;
|
||||
import jef.expressions.WhereExpression;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpressionOptimizerBottomUp extends ExpressionModifier {
|
||||
@Override
|
||||
public Expression modifyAnd(AndExpression expr) {
|
||||
var andsOpt = expr.getExprs().stream().map(this::modify).toList();
|
||||
var ands = new ArrayList<Expression>(expr.getExprs().size() * 2);
|
||||
|
||||
//squash ands
|
||||
for (Expression e : andsOpt) {
|
||||
if (e.getType() == Expression.Type.AND) {
|
||||
ands.addAll(((AndExpression) e).getExprs());
|
||||
} else {
|
||||
ands.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
// x && false -> false
|
||||
for (Expression e : ands) {
|
||||
if (e == ConstantExpression.V0) {
|
||||
return ConstantExpression.V0;
|
||||
}
|
||||
}
|
||||
|
||||
//remove && true
|
||||
while (ands.remove(ConstantExpression.V1)) ;
|
||||
|
||||
return new AndExpression(ands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyLimit(LimitExpression expr) {
|
||||
var inner = modify(expr.getExpr());
|
||||
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) WHERE 1) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) WHERE 1 LIMIT 1
|
||||
if (inner instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return new LimitExpression(w, expr.getStart(), expr.getCount());
|
||||
}
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) ORDER BY x ASC 1) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) ORDER BY x ASC LIMIT 1
|
||||
else if (inner instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof OrderExpression o) {
|
||||
return new LimitExpression(o, expr.getStart(), expr.getCount());
|
||||
}
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) LIMIT 3) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) LIMIT 1
|
||||
else if (inner instanceof LimitExpression l) {
|
||||
return new LimitExpression(l.getExpr(), expr.getStart(), expr.getCount());
|
||||
}
|
||||
return super.modifyLimit(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyOr(OrExpression expr) {
|
||||
var orsOpt = expr.getExprs().stream().map(this::modify).toList();
|
||||
var ors = new ArrayList<Expression>(expr.getExprs().size() * 2);
|
||||
|
||||
//squash ors
|
||||
for (Expression e : orsOpt) {
|
||||
if (e.getType() == Expression.Type.OR) {
|
||||
ors.addAll(((OrExpression) e).getExprs());
|
||||
} else {
|
||||
ors.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
// x || true -> true
|
||||
for (Expression e : ors) {
|
||||
if (e == ConstantExpression.V1) {
|
||||
return ConstantExpression.V1;
|
||||
}
|
||||
}
|
||||
|
||||
//remove OR false
|
||||
while (ors.remove(ConstantExpression.V0)) ;
|
||||
|
||||
return new OrExpression(ors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyOrder(OrderExpression expr) {
|
||||
var inner = modify(expr.getExpr());
|
||||
//<- order(select(where(...)))
|
||||
//-> order(where(...))
|
||||
if (inner instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return new OrderExpression(w, expr.getSorts());
|
||||
}
|
||||
//<- order1(order2(...))
|
||||
//-> order1(...=
|
||||
else if (inner instanceof OrderExpression o) {
|
||||
return new OrderExpression(o.getExpr(), expr.getSorts());
|
||||
}
|
||||
return super.modifyOrder(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifySelect(SelectExpression expr) {
|
||||
var from = modify(expr.getFrom());
|
||||
if (from instanceof SelectExpression s
|
||||
&& s.getFields().equals(expr.getFields())) {
|
||||
return s;
|
||||
}
|
||||
return super.modifySelect(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyTernary(TernaryExpression expr) {
|
||||
var cond = modify(expr.getCond());
|
||||
var whenTrue = modify(expr.getWhenTrue());
|
||||
var whenFalse = modify(expr.getWhenFalse());
|
||||
var optimized = TernaryOptimizerUtil.optimizeTernary(new TernaryExpression(cond, whenTrue, whenFalse));
|
||||
if (optimized.equals(expr)) {
|
||||
return super.modifyTernary(expr);
|
||||
}
|
||||
return modify(optimized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyUnary(UnaryExpression expr) {
|
||||
var inner = modify(expr.getExpr());
|
||||
if (inner instanceof UnaryExpression u
|
||||
&& u.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT) {
|
||||
//!!x -> x
|
||||
return modify(u.getExpr());
|
||||
} else if (inner instanceof BinaryExpression b
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator().isInvertible()) {
|
||||
//!(a < b) -> a >= b
|
||||
return new BinaryExpression(b.getLeft(), b.getRight(), b.getOperator().invert());
|
||||
} else if (inner instanceof BinaryExpression b
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator() == BinaryExpression.Operator.IS) {
|
||||
//!(a < b) -> a >= b
|
||||
return new BinaryExpression(b.getLeft(), new UnaryExpression(b.getRight(), UnaryExpression.Operator.NOT), b.getOperator());
|
||||
} else {
|
||||
return super.modifyUnary(expr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyWhere(WhereExpression expr) {
|
||||
var queryable = modify(expr.getQueryable());
|
||||
var where = modify(expr.getWhere());
|
||||
|
||||
//<- where(cond1, where(cond2, ...))
|
||||
//-> where(and(cond1, cond2) ...))
|
||||
if (queryable instanceof WhereExpression w) {
|
||||
return new WhereExpression(w.getQueryable(), new AndExpression(w.getWhere(), where));
|
||||
}
|
||||
//<- where(cond1, select(where(cond2, ...)))
|
||||
//-> where(and(cond1, cond2) ...))
|
||||
else if (queryable instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return new WhereExpression(w.getQueryable(), new AndExpression(w.getWhere(), where));
|
||||
}
|
||||
return super.modifyWhere(expr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package jef.expressions.modifier;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.LimitExpression;
|
||||
import jef.expressions.OrExpression;
|
||||
import jef.expressions.OrderExpression;
|
||||
import jef.expressions.SelectExpression;
|
||||
import jef.expressions.TernaryExpression;
|
||||
import jef.expressions.UnaryExpression;
|
||||
import jef.expressions.WhereExpression;
|
||||
import jef.expressions.selectable.DatabaseSelectAllExpression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ExpressionOptimizerTopDown extends ExpressionModifier {
|
||||
@Override
|
||||
public Expression modifyAnd(AndExpression expr) {
|
||||
var ands = new ArrayList<Expression>(expr.getExprs().size() * 2);
|
||||
|
||||
//squash ands
|
||||
for (Expression e : expr.getExprs()) {
|
||||
if (e.getType() == Expression.Type.AND) {
|
||||
ands.addAll(((AndExpression) e).getExprs());
|
||||
} else {
|
||||
ands.add(e);
|
||||
}
|
||||
}
|
||||
ands.replaceAll(this::modify);
|
||||
|
||||
// x && false -> false
|
||||
for (Expression e : ands) {
|
||||
if (e == ConstantExpression.V0) {
|
||||
return ConstantExpression.V0;
|
||||
}
|
||||
}
|
||||
|
||||
//remove && true
|
||||
while (ands.remove(ConstantExpression.V1)) ;
|
||||
|
||||
return new AndExpression(ands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyLimit(LimitExpression expr) {
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) WHERE 1) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) WHERE 1 LIMIT 1
|
||||
if (expr.getExpr() instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return modify(new LimitExpression(w, expr.getStart(), expr.getCount()));
|
||||
}
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) ORDER BY x ASC 1) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) ORDER BY x ASC LIMIT 1
|
||||
else if (expr.getExpr() instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof OrderExpression o) {
|
||||
return modify(new LimitExpression(o, expr.getStart(), expr.getCount()));
|
||||
}
|
||||
//<- SELECT * FROM (SELECT * FROM (SELECT * FROM table) LIMIT 3) LIMIT 1
|
||||
//-> SELECT * FROM (SELECT * FROM table) LIMIT 1
|
||||
else if (expr.getExpr() instanceof LimitExpression l) {
|
||||
return modify(new LimitExpression(l.getExpr(), expr.getStart(), expr.getCount()));
|
||||
}
|
||||
return super.modifyLimit(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyOr(OrExpression expr) {
|
||||
var ors = new ArrayList<Expression>(expr.getExprs().size() * 2);
|
||||
|
||||
//squash ors
|
||||
for (Expression e : expr.getExprs()) {
|
||||
if (e.getType() == Expression.Type.OR) {
|
||||
ors.addAll(((OrExpression) e).getExprs());
|
||||
} else {
|
||||
ors.add(e);
|
||||
}
|
||||
}
|
||||
ors.replaceAll(this::modify);
|
||||
|
||||
// x || true -> true
|
||||
for (Expression e : ors) {
|
||||
if (e == ConstantExpression.V1) {
|
||||
return ConstantExpression.V1;
|
||||
}
|
||||
}
|
||||
|
||||
//remove OR false
|
||||
while (ors.remove(ConstantExpression.V0)) ;
|
||||
|
||||
return new OrExpression(ors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyOrder(OrderExpression expr) {
|
||||
//<- order(select(where(...)))
|
||||
//-> order(where(...))
|
||||
if (expr.getExpr() instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return modify(new OrderExpression(w, expr.getSorts()));
|
||||
}
|
||||
//<- order1(order2(...))
|
||||
//-> order1(...=
|
||||
else if (expr.getExpr() instanceof OrderExpression o) {
|
||||
return modify(new OrderExpression(o.getExpr(), expr.getSorts()));
|
||||
}
|
||||
return super.modifyOrder(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifySelect(SelectExpression expr) {
|
||||
if (expr.getFrom() instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))) {
|
||||
return modify(s);
|
||||
}
|
||||
return super.modifySelect(expr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyTernary(TernaryExpression expr) {
|
||||
var optimized = TernaryOptimizerUtil.optimizeTernary(expr);
|
||||
if (optimized == expr) {
|
||||
return super.modifyTernary(expr);
|
||||
}
|
||||
return modify(optimized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyUnary(UnaryExpression expr) {
|
||||
if (expr.getExpr() instanceof UnaryExpression u
|
||||
&& expr.getOperator() == u.getOperator()
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT) {
|
||||
//!!x -> x
|
||||
return modify(u.getExpr());
|
||||
} else if (expr.getExpr() instanceof BinaryExpression b
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator().isInvertible()) {
|
||||
//!(a < b) -> a >= b
|
||||
return modify(new BinaryExpression(b.getLeft(), b.getRight(), b.getOperator().invert()));
|
||||
} else if (expr.getExpr() instanceof BinaryExpression b
|
||||
&& expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& b.getOperator() == BinaryExpression.Operator.IS) {
|
||||
//!(a < b) -> a >= b
|
||||
return modify(new BinaryExpression(b.getLeft(), new UnaryExpression(b.getRight(), UnaryExpression.Operator.NOT), b.getOperator()));
|
||||
} else {
|
||||
return super.modifyUnary(expr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyWhere(WhereExpression expr) {
|
||||
//<- where(cond1, where(cond2, ...))
|
||||
//-> where(and(cond1, cond2) ...))
|
||||
if (expr.getQueryable() instanceof WhereExpression w) {
|
||||
return modify(new WhereExpression(w.getQueryable(), new AndExpression(w.getWhere(), expr.getWhere())));
|
||||
}
|
||||
//<- where(cond1, select(where(cond2, ...)))
|
||||
//-> where(and(cond1, cond2) ...))
|
||||
else if (expr.getQueryable() instanceof SelectExpression s
|
||||
&& s.getFields().equals(List.of(DatabaseSelectAllExpression.INSTANCE))
|
||||
&& s.getFrom() instanceof WhereExpression w) {
|
||||
return modify(new WhereExpression(w.getQueryable(), new AndExpression(w.getWhere(), expr.getWhere())));
|
||||
}
|
||||
return super.modifyWhere(expr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package jef.expressions.modifier;
|
||||
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
import jef.expressions.UnaryExpression;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class IConst0Fixer extends ExpressionModifier {
|
||||
private static final Set<String> DESCRIPTORS = Set.of("I", "F", "D", "L",
|
||||
"Ljava/lang/Integer;", "Ljava/lang/Float;", "Ljava/lang/Double;", "Ljava/lang/Long;");
|
||||
|
||||
@Override
|
||||
public Expression modifyUnary(UnaryExpression expr) {
|
||||
if (expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& expr.getExpr().getType() == Expression.Type.FIELD
|
||||
&& DESCRIPTORS.contains(((FieldExpression) expr.getExpr()).getClassDescriptor())) {
|
||||
return modify(new BinaryExpression(expr.getExpr(), ConstantExpression.V0, BinaryExpression.Operator.EQ));
|
||||
} else if (expr.getOperator() == UnaryExpression.Operator.NOT
|
||||
&& expr.getExpr().getType() == Expression.Type.INTERMEDIATE_FIELD
|
||||
&& DESCRIPTORS.contains(((IntermediateFieldExpression) expr.getExpr()).getClassDescriptor())) {
|
||||
return modify(new BinaryExpression(expr.getExpr(), ConstantExpression.V0, BinaryExpression.Operator.EQ));
|
||||
}
|
||||
return super.modifyUnary(expr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package jef.expressions.modifier;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
|
||||
public class TableAliasInjector extends ExpressionModifier {
|
||||
private final String tableAlias;
|
||||
|
||||
public TableAliasInjector(String tableAlias) {
|
||||
this.tableAlias = tableAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression modifyIntermediateField(IntermediateFieldExpression expr) {
|
||||
return new FieldExpression(null, tableAlias, expr.getName(), expr.getClassDescriptor());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package jef.expressions.modifier;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.OrExpression;
|
||||
import jef.expressions.TernaryExpression;
|
||||
import jef.expressions.UnaryExpression;
|
||||
|
||||
public abstract class TernaryOptimizerUtil {
|
||||
public static Expression optimizeTernary(TernaryExpression expr) {
|
||||
if (expr.getWhenFalse() == ConstantExpression.V1 && expr.getWhenFalse() == ConstantExpression.V0) {
|
||||
//x ? 1 : 0 -> x
|
||||
return expr.getCond();
|
||||
} else if (expr.getWhenFalse() == ConstantExpression.V0 && expr.getWhenFalse() == ConstantExpression.V1) {
|
||||
//x ? 0 : 1 -> !x
|
||||
return new UnaryExpression(expr.getCond(), UnaryExpression.Operator.NOT);
|
||||
} else if (expr.getWhenFalse() == ConstantExpression.V0) {
|
||||
//x ? y : 0 -> x && y
|
||||
return new AndExpression(expr.getCond(), expr.getWhenTrue());
|
||||
} else if (expr.getWhenFalse() == ConstantExpression.V1) {
|
||||
//x ? y : 1 -> !x or y
|
||||
return new OrExpression(new UnaryExpression(expr.getCond(), UnaryExpression.Operator.NOT), expr.getWhenTrue());
|
||||
} else if (expr.getWhenTrue() instanceof TernaryExpression t && expr.getWhenFalse() == t.getWhenFalse()) {
|
||||
// x ? (y ? z : u) : u -> (x && y) ? z : u
|
||||
return new TernaryExpression(new AndExpression(expr.getCond(), t.getCond()), t.getWhenTrue(), t.getWhenFalse());
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package jef.expressions.modifier;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.OrExpression;
|
||||
import jef.expressions.TernaryExpression;
|
||||
import jef.expressions.UnaryExpression;
|
||||
|
||||
public class TernaryRewriter extends ExpressionModifier {
|
||||
@Override
|
||||
public Expression modifyTernary(TernaryExpression expr) {
|
||||
//first optimize
|
||||
var newExpr = TernaryOptimizerUtil.optimizeTernary(expr);
|
||||
if (!(newExpr instanceof TernaryExpression)) {
|
||||
return modify(newExpr);
|
||||
}
|
||||
expr = (TernaryExpression) newExpr;
|
||||
|
||||
//rewrite
|
||||
return modify(new OrExpression(new AndExpression(expr.getCond(), expr.getWhenTrue()),
|
||||
new AndExpression(new UnaryExpression(expr.getCond(), UnaryExpression.Operator.NOT), expr.getWhenFalse())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package jef.expressions.selectable;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
|
||||
public class DatabaseFunctionExpression implements Expression, SelectableExpression {
|
||||
private final String function;
|
||||
|
||||
public DatabaseFunctionExpression(String function) {
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.CONSTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package jef.expressions.selectable;
|
||||
|
||||
import jef.expressions.Expression;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class DatabaseSelectAllExpression implements Expression, SelectableExpression {
|
||||
public static final DatabaseSelectAllExpression INSTANCE = new DatabaseSelectAllExpression();
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.CONSTANT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Priority getPriority() {
|
||||
return Priority.UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this == obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "*";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package jef.expressions.selectable;
|
||||
|
||||
public interface SelectableExpression {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package jef.expressions.visitors;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
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.expressions.selectable.SelectableExpression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DebugExpressionVisitor extends ExpressionVisitor {
|
||||
private int indent = 0;
|
||||
|
||||
@Override
|
||||
public void visitAnd(AndExpression expr) {
|
||||
for (int i = 0; i < expr.getExprs().size(); i++) {
|
||||
indent++;
|
||||
visit(expr.getExprs().get(i));
|
||||
indent--;
|
||||
if (i + 1 < expr.getExprs().size())
|
||||
System.out.println(i() + "AND");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitBinary(BinaryExpression expr) {
|
||||
indent++;
|
||||
visit(expr.getLeft());
|
||||
indent--;
|
||||
System.out.println(i() + expr.getOperator());
|
||||
indent++;
|
||||
visit(expr.getRight());
|
||||
indent--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitConstant(ConstantExpression expr) {
|
||||
System.out.println(i() + expr.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(FieldExpression expr) {
|
||||
System.out.println(i() + expr.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFunction(FunctionExpression expr) {
|
||||
System.out.println(i() + expr.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIntermediateField(IntermediateFieldExpression expr) {
|
||||
System.out.println(i() + expr.toString());
|
||||
}
|
||||
|
||||
public void visitLimit(LimitExpression expr) {
|
||||
visit(expr.getExpr());
|
||||
var s = "";
|
||||
if (expr.getCount() != null) {
|
||||
s += " LIMIT " + expr.getCount();
|
||||
}
|
||||
if (expr.getStart() != null) {
|
||||
s += " OFFSET " + expr.getStart();
|
||||
}
|
||||
System.out.println(i() + s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNull(NullExpression expr) {
|
||||
System.out.println(i() + expr.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOr(OrExpression expr) {
|
||||
for (int i = 0; i < expr.getExprs().size(); i++) {
|
||||
indent++;
|
||||
visit(expr.getExprs().get(i));
|
||||
indent--;
|
||||
if (i + 1 < expr.getExprs().size())
|
||||
System.out.println(i() + "OR");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOrder(OrderExpression expr) {
|
||||
visit(expr.getExpr());
|
||||
if (!expr.getSorts().isEmpty()) {
|
||||
var sortStrings = new ArrayList<String>(expr.getSorts().size());
|
||||
for (OrderExpression.Sort sort : expr.getSorts()) {
|
||||
sortStrings.add(sort.getExpr() + " " + sort.getDirection().getString());
|
||||
}
|
||||
System.out.println(i() + " ORDER BY " + String.join(", ", sortStrings));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitParameter(ParameterExpression expr) {
|
||||
if (expr.getValue() instanceof Collection c) {
|
||||
System.out.println(i() + c.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||
} else {
|
||||
System.out.println(i() + expr.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSelect(SelectExpression expr) {
|
||||
var table = expr.getFrom() instanceof TableExpression;
|
||||
var tableName = table ? ((TableExpression) expr.getFrom()).getName() : null;
|
||||
var tableAlias = (expr.getFromAlias() == null || expr.getFromAlias().isBlank()) ? "" : " " + expr.getFromAlias();
|
||||
System.out.println(i() + "SELECT " + expr.getFields().stream().map(SelectableExpression::toString).collect(Collectors.joining(", "))
|
||||
+ " FROM" + (!table ? " (" : tableName + " " + tableAlias));
|
||||
if (!table) {
|
||||
indent++;
|
||||
visit(expr.getFrom());
|
||||
indent--;
|
||||
System.out.println(i() + ")" + tableAlias);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTable(TableExpression expr) {
|
||||
System.out.println(i() + expr.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTernary(TernaryExpression expr) {
|
||||
indent++;
|
||||
visit(expr.getCond());
|
||||
indent--;
|
||||
System.out.println(i() + "?");
|
||||
indent++;
|
||||
visit(expr.getWhenTrue());
|
||||
indent--;
|
||||
System.out.println(i() + ":");
|
||||
indent++;
|
||||
visit(expr.getWhenFalse());
|
||||
indent--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUnary(UnaryExpression expr) {
|
||||
System.out.println(i() + expr.getOperator());
|
||||
indent++;
|
||||
visit(expr.getExpr());
|
||||
indent--;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitWhere(WhereExpression expr) {
|
||||
visit(expr.getQueryable());
|
||||
System.out.println(i() + "WHERE");
|
||||
indent++;
|
||||
visit(expr.getWhere());
|
||||
indent--;
|
||||
}
|
||||
|
||||
private String i() {
|
||||
return " ".repeat(indent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package jef.expressions.visitors;
|
||||
|
||||
import jef.expressions.AndExpression;
|
||||
import jef.expressions.BinaryExpression;
|
||||
import jef.expressions.ConstantExpression;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.FieldExpression;
|
||||
import jef.expressions.FunctionExpression;
|
||||
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;
|
||||
|
||||
public abstract class ExpressionVisitor {
|
||||
public void visit(Expression expr) {
|
||||
switch (expr.getType()) {
|
||||
case AND -> visitAnd((AndExpression) expr);
|
||||
case BINARY -> visitBinary((BinaryExpression) expr);
|
||||
case CONSTANT -> visitConstant((ConstantExpression) expr);
|
||||
case FIELD -> visitField((FieldExpression) expr);
|
||||
case FUNCTION -> visitFunction((FunctionExpression) expr);
|
||||
case INTERMEDIATE_FIELD -> visitIntermediateField((IntermediateFieldExpression) expr);
|
||||
case LIMIT -> visitLimit((LimitExpression) expr);
|
||||
case NULL -> visitNull((NullExpression) expr);
|
||||
case OR -> visitOr((OrExpression) expr);
|
||||
case ORDER -> visitOrder((OrderExpression) expr);
|
||||
case PARAMETER -> visitParameter((ParameterExpression) expr);
|
||||
case SELECT -> visitSelect((SelectExpression) expr);
|
||||
case TABLE -> visitTable((TableExpression) expr);
|
||||
case TERNARY -> visitTernary((TernaryExpression) expr);
|
||||
case UNARY -> visitUnary((UnaryExpression) expr);
|
||||
case WHERE -> visitWhere((WhereExpression) expr);
|
||||
default -> throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public void visitAnd(AndExpression expr) {
|
||||
for (Expression e : expr.getExprs()) {
|
||||
visit(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitBinary(BinaryExpression expr) {
|
||||
visit(expr.getLeft());
|
||||
visit(expr.getRight());
|
||||
}
|
||||
|
||||
public void visitConstant(ConstantExpression expr) {
|
||||
}
|
||||
|
||||
public void visitField(FieldExpression expr) {
|
||||
}
|
||||
|
||||
public void visitFunction(FunctionExpression expr) {
|
||||
}
|
||||
|
||||
public void visitIntermediateField(IntermediateFieldExpression expr) {
|
||||
}
|
||||
|
||||
public void visitLimit(LimitExpression expr) {
|
||||
visit(expr.getExpr());
|
||||
}
|
||||
|
||||
public void visitNull(NullExpression expr) {
|
||||
}
|
||||
|
||||
public void visitOr(OrExpression expr) {
|
||||
for (Expression e : expr.getExprs()) {
|
||||
visit(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitOrder(OrderExpression expr) {
|
||||
visit(expr.getExpr());
|
||||
}
|
||||
|
||||
public void visitParameter(ParameterExpression expr) {
|
||||
}
|
||||
|
||||
public void visitSelect(SelectExpression expr) {
|
||||
visit(expr.getFrom());
|
||||
}
|
||||
|
||||
public void visitTable(TableExpression expr) {
|
||||
}
|
||||
|
||||
public void visitTernary(TernaryExpression expr) {
|
||||
visit(expr.getCond());
|
||||
visit(expr.getWhenTrue());
|
||||
visit(expr.getWhenFalse());
|
||||
}
|
||||
|
||||
public void visitUnary(UnaryExpression expr) {
|
||||
visit(expr.getExpr());
|
||||
}
|
||||
|
||||
public void visitWhere(WhereExpression expr) {
|
||||
visit(expr.getQueryable());
|
||||
visit(expr.getWhere());
|
||||
}
|
||||
}
|
||||
117
core/src/main/java/jef/model/DbContext.java
Normal file
117
core/src/main/java/jef/model/DbContext.java
Normal file
@@ -0,0 +1,117 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.Database;
|
||||
import jef.DbSet;
|
||||
import jef.model.annotations.Clazz;
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
public abstract class DbContext {
|
||||
private static final String ILLEGAL_CHARACTERS = "\"'`,.";
|
||||
|
||||
private final Database database;
|
||||
private final DbContextOptions options;
|
||||
|
||||
public DbContext() {
|
||||
database = null;
|
||||
options = null;
|
||||
}
|
||||
|
||||
public DbContext(Database database, DbContextOptions options) {
|
||||
this.database = database;
|
||||
this.options = options;
|
||||
|
||||
initInitializeDbSets();
|
||||
}
|
||||
|
||||
private void initInitializeDbSets() {
|
||||
for (Field f : getClass().getDeclaredFields()) {
|
||||
if (f.getType() != DbSet.class) {
|
||||
continue;
|
||||
}
|
||||
f.setAccessible(true);
|
||||
Clazz anno = f.getAnnotation(Clazz.class);
|
||||
try {
|
||||
f.set(this, new DbSet(anno.value(), f.getName()));//TODO use table name from modelbuilder
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onModelCreate(ModelBuilder mb) {
|
||||
}
|
||||
|
||||
public void onModelValidate(ModelBuilder mb) {
|
||||
var errors = new ArrayList<String>();
|
||||
|
||||
//check for illegal characters
|
||||
for (var entity : mb.getEntities()) {
|
||||
if (entity.getName().matches("[" + ILLEGAL_CHARACTERS + "]")) {
|
||||
errors.add("DbEntity " + entity.getTypeName() + " has illegal characters in its name: " + entity.getName());
|
||||
}
|
||||
for (var field : entity.getFields()) {
|
||||
if (field.getName().matches("[" + ILLEGAL_CHARACTERS + "]")) {
|
||||
var fieldname = Optional.ofNullable(field.getField()).map(Field::getName).orElse(field.getName());
|
||||
errors.add("DbField " + entity.getTypeName() + "::" + fieldname + " has illegal characters in its name: " + entity.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check for duplicate entity names
|
||||
var entityNameMap = new HashMap<String, DbEntity<?>>();
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
var name = entity.getName().toLowerCase();
|
||||
if (entityNameMap.containsKey(name)) {
|
||||
errors.add("Duplicate DbEntity with name " + entity.getName() + ": " + entity.getTypeName() + " and " + entityNameMap.get(name).getTypeName());
|
||||
} else {
|
||||
entityNameMap.put(name, entity);
|
||||
}
|
||||
}
|
||||
|
||||
//check for duplicate field names
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
var fieldNameMap = new HashMap<String, DbField<?>>();
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
var name = field.getName().toLowerCase();
|
||||
if (fieldNameMap.containsKey(name)) {
|
||||
errors.add("Duplicate DbField in entity " + entity.getTypeName() + " with name " + field.getName() + ": " + field.getName() + " and " + fieldNameMap.get(name).getName());
|
||||
} else {
|
||||
fieldNameMap.put(name, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check foreign keys for existing primary keys
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
for (ForeignKeyConstraint foreignKey : entity.getForeignKeys()) {
|
||||
//verify primary key exists
|
||||
if (foreignKey.getReferencedEntity().getPrimaryKey() == null) {
|
||||
errors.add("DbEntity " + entity.getTypeName() + " has a foreign key constraint to " + foreignKey.getReferencedEntity().getTypeName()
|
||||
+ " which does not have primary key");
|
||||
}
|
||||
//verify field count, names, types, and order
|
||||
else if (!foreignKey.getReferencedEntity().getPrimaryKey().getFields().equals(foreignKey.getReferencedFields())) {
|
||||
errors.add("DbEntity " + entity.getTypeName() + " has a foreign key constraint to " + foreignKey.getReferencedEntity().getTypeName()
|
||||
+ " where the specified fields do not match: referring fields ["
|
||||
+ foreignKey.getReferencedFields().stream().map(DbField::getName).collect(Collectors.joining(", "))
|
||||
+ "] but found referenced fields ["
|
||||
+ foreignKey.getReferencedEntity().getPrimaryKey().getFields().stream().map(DbField::getName).collect(Collectors.joining(", "))
|
||||
+ "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new ModelException(String.join("\n", errors));
|
||||
}
|
||||
}
|
||||
}
|
||||
15
core/src/main/java/jef/model/DbContextOptions.java
Normal file
15
core/src/main/java/jef/model/DbContextOptions.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.DatabaseOptions;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class DbContextOptions {
|
||||
private final DatabaseOptions databaseOptions;
|
||||
}
|
||||
261
core/src/main/java/jef/model/DbEntity.java
Normal file
261
core/src/main/java/jef/model/DbEntity.java
Normal file
@@ -0,0 +1,261 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.asm.AsmParseException;
|
||||
import jef.asm.AsmParser;
|
||||
import jef.expressions.Expression;
|
||||
import jef.expressions.IntermediateFieldExpression;
|
||||
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.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
@Setter(value = AccessLevel.PACKAGE)
|
||||
public class DbEntity<T extends SerializableObject> {
|
||||
private String typeName;
|
||||
// @Setter
|
||||
private Class<T> type;
|
||||
private final List<DbField<?>> fields;
|
||||
private String name;
|
||||
private PrimaryKeyConstraint primaryKey;
|
||||
private final List<ForeignKeyConstraint> foreignKeys = new ArrayList<>();
|
||||
private final List<UniqueKeyConstraint> uniqueKeys = new ArrayList<>();
|
||||
private final List<KeyConstraint> keys = new ArrayList<>();
|
||||
private final List<IndexConstraint> indexes = new ArrayList<>();
|
||||
|
||||
//only used for migrations
|
||||
DbEntity(String typeName) {
|
||||
this.typeName = typeName;
|
||||
this.fields = new ArrayList<>();
|
||||
var split = typeName.split("\\.");
|
||||
this.name = split[split.length - 1];
|
||||
}
|
||||
|
||||
DbEntity(Class<T> type) {
|
||||
this(type, type.getSimpleName());
|
||||
}
|
||||
|
||||
DbEntity(Class<T> type, String name) {
|
||||
this(type, name, new ArrayList<>());
|
||||
}
|
||||
|
||||
DbEntity(Class<T> type, String name, List<DbField<?>> fields) {
|
||||
this.type = type;
|
||||
this.typeName = type.getName();
|
||||
this.fields = Check.notNull(fields, "fields");
|
||||
this.name = Check.notNull(name, "name");
|
||||
}
|
||||
|
||||
public List<DbField<?>> getFields() {
|
||||
return Collections.unmodifiableList(fields);
|
||||
}
|
||||
|
||||
public List<ForeignKeyConstraint> getForeignKeys() {
|
||||
return Collections.unmodifiableList(foreignKeys);
|
||||
}
|
||||
|
||||
public List<UniqueKeyConstraint> getUniqueKeys() {
|
||||
return Collections.unmodifiableList(uniqueKeys);
|
||||
}
|
||||
|
||||
public List<KeyConstraint> getKeys() {
|
||||
return Collections.unmodifiableList(keys);
|
||||
}
|
||||
|
||||
public List<IndexConstraint> getIndexes() {
|
||||
return Collections.unmodifiableList(indexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database model for the requested property or null if not present.
|
||||
*
|
||||
* @param getter the expression selecting the property
|
||||
* @param <R> the type of property class
|
||||
* @return the database model for the requested property or null if not present.
|
||||
*/
|
||||
public <R> DbField<R> getField(SerializableFunction<T, R> getter) {
|
||||
var name = extractFieldName(getter);
|
||||
var prop = (DbField<R>) this.fields.stream().filter(p -> p.getField().getName().equals(name)).findFirst().orElse(null);
|
||||
return prop;
|
||||
}
|
||||
|
||||
public <R> DbField<R> getField(Field field) {
|
||||
return (DbField<R>) fields.stream().filter(e -> e.getField().equals(field)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public <R> DbField<R> getField(String name) {
|
||||
return (DbField<R>) fields.stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public <R> DbField<R> getOrCreateField(SerializableFunction<T, R> getter) {
|
||||
try {
|
||||
var prop = getField(getter);
|
||||
if (prop == null) {
|
||||
var name = extractFieldName(getter);
|
||||
var field = ReflectionUtil.getFieldsRecursive((Class<SerializableObject>) Class.forName(typeName)).stream().filter(f -> f.getName().equals(name)).findFirst().orElse(null);
|
||||
if (field == null) {
|
||||
throw new RuntimeException("Field not found: " + name);
|
||||
}
|
||||
prop = new DbField<>(this, (Class<R>) field.getType(), field);
|
||||
fields.add(prop);
|
||||
}
|
||||
return prop;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <R> DbField<R> getOrCreateField(Field field) {
|
||||
try {
|
||||
var prop = (DbField<R>) fields.stream().filter(e -> e.getField() == field).findFirst().orElse(null);
|
||||
if (prop == null) {
|
||||
prop = new DbField<>(this, (Class<R>) field.getType(), field);
|
||||
fields.add(prop);
|
||||
}
|
||||
return prop;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <R> DbField<R> getOrCreateField(String name, String typeName) {
|
||||
try {
|
||||
var prop = (DbField<R>) fields.stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
|
||||
if (prop == null) {
|
||||
prop = new DbField<>(this, name, typeName);
|
||||
fields.add(prop);
|
||||
}
|
||||
return prop;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <R> DbField<R> addIfAbsent(DbField<R> field) {
|
||||
try {
|
||||
var prop = (DbField<R>) fields.stream().filter(e -> e.getName().equals(field.getName())).findFirst().orElse(null);
|
||||
if (prop == null) {
|
||||
prop = field;
|
||||
fields.add(prop);
|
||||
}
|
||||
return prop;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean dropField(DbField<?> field) {
|
||||
return this.fields.remove(field);
|
||||
}
|
||||
|
||||
public boolean dropForeignKey(ForeignKeyConstraint foreignKey) {
|
||||
return this.foreignKeys.remove(foreignKey);
|
||||
}
|
||||
|
||||
public boolean dropUniqueKey(UniqueKeyConstraint uniqueKey) {
|
||||
return this.uniqueKeys.remove(uniqueKey);
|
||||
}
|
||||
|
||||
public boolean dropKey(KeyConstraint key) {
|
||||
return this.keys.remove(key);
|
||||
}
|
||||
|
||||
public boolean dropIndex(IndexConstraint index) {
|
||||
return this.indexes.remove(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not validate foreign key integrity
|
||||
*
|
||||
* @param primaryKey
|
||||
*/
|
||||
public void setPrimaryKey(PrimaryKeyConstraint primaryKey) {
|
||||
this.primaryKey = primaryKey;
|
||||
}
|
||||
|
||||
public void addForeignKey(ForeignKeyConstraint foreignKey) {
|
||||
if (!foreignKeys.contains(foreignKey)) {
|
||||
foreignKeys.add(foreignKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void addUniqueKey(UniqueKeyConstraint uniqueKeyConstraint) {
|
||||
if (!uniqueKeys.contains(uniqueKeyConstraint)) {
|
||||
uniqueKeys.add(uniqueKeyConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
public void addKey(KeyConstraint key) {
|
||||
if (!keys.contains(key)) {
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void addIndex(IndexConstraint index) {
|
||||
if (!indexes.contains(index)) {
|
||||
indexes.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
void addField(DbField<?> field) {
|
||||
this.fields.add(field);
|
||||
}
|
||||
|
||||
DbEntityBuilder<T> builder(ModelBuilder mb) {
|
||||
return new DbEntityBuilder<>(mb, this);
|
||||
}
|
||||
|
||||
<R> String extractFieldName(SerializableFunction<T, R> getter) {
|
||||
try {
|
||||
var expr = new AsmParser(getter).parse().getExpression();
|
||||
if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) {
|
||||
throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression");
|
||||
}
|
||||
var name = ((IntermediateFieldExpression) expr).getName();
|
||||
return name;
|
||||
} catch (AsmParseException e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DbEntity<?> dbEntity = (DbEntity<?>) o;
|
||||
return typeName.equals(dbEntity.typeName)
|
||||
&& fields.equals(dbEntity.fields)
|
||||
&& name.equals(dbEntity.name)
|
||||
&& Objects.equals(primaryKey, dbEntity.primaryKey)
|
||||
&& foreignKeys.equals(dbEntity.foreignKeys)
|
||||
&& uniqueKeys.equals(dbEntity.uniqueKeys)
|
||||
&& keys.equals(dbEntity.keys)
|
||||
&& indexes.equals(dbEntity.indexes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(typeName, fields, name, primaryKey, foreignKeys, uniqueKeys, keys, indexes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DbEntity{" +
|
||||
"typeName=" + typeName +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
121
core/src/main/java/jef/model/DbEntityBuilder.java
Normal file
121
core/src/main/java/jef/model/DbEntityBuilder.java
Normal file
@@ -0,0 +1,121 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
131
core/src/main/java/jef/model/DbField.java
Normal file
131
core/src/main/java/jef/model/DbField.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Check;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DbField<T> {
|
||||
private final DbEntity<? extends SerializableObject> entity;
|
||||
private final String typeName;
|
||||
@Setter(value = AccessLevel.PACKAGE)
|
||||
private Class<T> type;
|
||||
@Setter(value = AccessLevel.PACKAGE)
|
||||
private Field field;
|
||||
private boolean isModelField;
|
||||
private boolean isDatabaseField;
|
||||
private DbField<?> exposingForeignKeyOf;
|
||||
private String name;
|
||||
private boolean notNull = false;
|
||||
private String sqlType;
|
||||
|
||||
DbField(DbEntity<? extends SerializableObject> entity, String name, String typeName) {
|
||||
this.entity = Check.notNull(entity, "entity");
|
||||
this.type = null;
|
||||
this.typeName = Check.notNull(typeName, "typeName");
|
||||
this.field = null;
|
||||
this.name = Check.notNull(name, "name");
|
||||
this.isModelField = false;
|
||||
this.isDatabaseField = true;
|
||||
}
|
||||
|
||||
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) {
|
||||
this(entity, type, field, Check.notNull(field, "field").getName());
|
||||
}
|
||||
|
||||
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) {
|
||||
this.entity = Check.notNull(entity, "entity");
|
||||
this.type = Check.notNull(type, "type");
|
||||
this.typeName = type.getName();
|
||||
this.field = field;
|
||||
this.name = Check.notNull(name, "name");
|
||||
this.isModelField = field != null;
|
||||
this.isDatabaseField = !Collection.class.isAssignableFrom(type) && !SerializableObject.class.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
public boolean isForeignKey() {
|
||||
return entity.getForeignKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
|
||||
}
|
||||
|
||||
public boolean isUnique() {
|
||||
return entity.getUniqueKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
|
||||
}
|
||||
|
||||
public boolean isIndex() {
|
||||
return entity.getIndexes().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
|
||||
}
|
||||
|
||||
public boolean isKey() {
|
||||
return entity.getKeys().stream().anyMatch(u -> u.getFields().size() == 1 && u.getFields().get(0) == this);
|
||||
}
|
||||
|
||||
public DbField<T> setModelField(boolean modelField) {
|
||||
isModelField = modelField;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DbField<T> setDatabaseField(boolean databaseField) {
|
||||
isDatabaseField = databaseField;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DbField<T> setNotNull(boolean notNull) {
|
||||
this.notNull = notNull;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setSqlType(String sqlType) {
|
||||
this.sqlType = sqlType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!equalsCommon(o)) {
|
||||
return false;
|
||||
}
|
||||
DbField<?> dbField = (DbField<?>) o;
|
||||
return name.equals(dbField.name);
|
||||
// && Objects.equals(field == null ? null : field.getName(), dbField.field == null ? null : dbField.field.getName());
|
||||
}
|
||||
|
||||
public boolean equalsExceptName(Object o) {
|
||||
return equalsCommon(o);
|
||||
}
|
||||
|
||||
private boolean equalsCommon(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DbField<?> dbField = (DbField<?>) o;
|
||||
return isModelField == dbField.isModelField
|
||||
&& isDatabaseField == dbField.isDatabaseField
|
||||
&& notNull == dbField.notNull
|
||||
&& entity.getName().equals(dbField.entity.getName())
|
||||
&& typeName.equals(dbField.typeName)
|
||||
// && Objects.equals(type, dbField.type)
|
||||
&& Objects.equals(exposingForeignKeyOf == null ? null : exposingForeignKeyOf.getName(),
|
||||
dbField.exposingForeignKeyOf == null ? null : dbField.exposingForeignKeyOf.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(entity.getName(), typeName, type, field == null ? null : field.getName(), isModelField, isDatabaseField,
|
||||
exposingForeignKeyOf == null ? null : exposingForeignKeyOf.getName(),
|
||||
name, notNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DbField{" +
|
||||
"name=" + name +
|
||||
", typeName=" + typeName + (notNull ? "" : "?") +
|
||||
", entity=" + entity +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
89
core/src/main/java/jef/model/DbFieldBuilder.java
Normal file
89
core/src/main/java/jef/model/DbFieldBuilder.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package jef.model;
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EntityDefaultConstructorChecker {
|
||||
static void checkEntities(ModelBuilder mb) {
|
||||
for (DbEntityBuilder<?> entity : mb.entities()) {
|
||||
checkEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkEntity(DbEntityBuilder<?> entity) {
|
||||
Log.debug("Checking default constructor exists for entity '" + entity.name() + "' of type " + entity.className());
|
||||
|
||||
//check no arg constructor
|
||||
Class<?> clazz = entity.type().orElseThrow();
|
||||
if (Arrays.stream(clazz.getDeclaredConstructors()).noneMatch(e -> e.getParameterCount() == 0)) {
|
||||
throw new ModelException("Class '" + clazz.getSimpleName() + "' does not have a default constructor!");
|
||||
}
|
||||
}
|
||||
}
|
||||
83
core/src/main/java/jef/model/EntityInitializer.java
Normal file
83
core/src/main/java/jef/model/EntityInitializer.java
Normal file
@@ -0,0 +1,83 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
core/src/main/java/jef/model/ForeignKeyBuilder.java
Normal file
20
core/src/main/java/jef/model/ForeignKeyBuilder.java
Normal file
@@ -0,0 +1,20 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
core/src/main/java/jef/model/ForeignKeyInitializer.java
Normal file
196
core/src/main/java/jef/model/ForeignKeyInitializer.java
Normal file
@@ -0,0 +1,196 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
186
core/src/main/java/jef/model/KeyBuilder.java
Normal file
186
core/src/main/java/jef/model/KeyBuilder.java
Normal file
@@ -0,0 +1,186 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
283
core/src/main/java/jef/model/ModelBuilder.java
Normal file
283
core/src/main/java/jef/model/ModelBuilder.java
Normal file
@@ -0,0 +1,283 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.Database;
|
||||
import jef.model.annotations.processors.AnnotationProcessor;
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.model.constraints.PrimaryKeyConstraint;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
import jef.mysql.MysqlDatabase;
|
||||
import jef.serializable.SerializableObject;
|
||||
import jef.util.Check;
|
||||
import jef.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModelBuilder {
|
||||
/**
|
||||
* Initializes a ModelBuilder and configures the entities found in the context Class according to annotations.
|
||||
*
|
||||
* @param context the context to use for initialization
|
||||
* @return an initialized ModelBuilder
|
||||
*/
|
||||
public static ModelBuilder from(Class<? extends DbContext> context) {
|
||||
return from(context, new ModelBuilderOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a ModelBuilder and configures the entities found in the context Class according to annotations.
|
||||
*
|
||||
* @param context the context to use for initialization
|
||||
* @param options the options to user during initialization
|
||||
* @return an initialized ModelBuilder
|
||||
*/
|
||||
public static ModelBuilder from(Class<? extends DbContext> context, ModelBuilderOptions options) {
|
||||
try {
|
||||
return from0(context, options);
|
||||
} catch (Throwable e) {
|
||||
throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModelBuilder from0(Class<? extends DbContext> context, ModelBuilderOptions options) {
|
||||
var mb = new ModelBuilder(new ArrayList<>());
|
||||
EntityInitializer.initEntities(mb, context);
|
||||
EntityDefaultConstructorChecker.checkEntities(mb);
|
||||
PrimaryKeyInitializer.initPrimaryKeys(mb);
|
||||
ForeignKeyExposeInitializer.initForeignKeyExposures(mb);
|
||||
ForeignKeyInitializer.initForeignKeys(mb);
|
||||
|
||||
for (AnnotationProcessor processor : options.getAnnotationProcessors()) {
|
||||
processor.apply(mb);
|
||||
}
|
||||
|
||||
Util.ThrowableBiFunction<Database, DbContextOptions, DbContext> init;//TODO create a function for initializing db contexts
|
||||
try {
|
||||
var ctor = context.getDeclaredConstructor(Database.class, DbContextOptions.class);
|
||||
init = ctor::newInstance;
|
||||
} catch (NoSuchMethodException e) {
|
||||
try {
|
||||
var ctor = context.getDeclaredConstructor(DbContextOptions.class);
|
||||
init = (d, o) -> ctor.newInstance(o);
|
||||
} catch (NoSuchMethodException e2) {
|
||||
try {
|
||||
var ctor = context.getDeclaredConstructor();
|
||||
init = (d, o) -> ctor.newInstance();
|
||||
} catch (NoSuchMethodException e3) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
DbContext instance = init.apply(new MysqlDatabase(null, null), options.getContextOptions());//TODO
|
||||
instance.onModelCreate(mb);
|
||||
instance.onModelValidate(mb);
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return mb;
|
||||
}
|
||||
|
||||
private final List<DbEntity<? extends SerializableObject>> entities;
|
||||
|
||||
/**
|
||||
* Initializes an empty ModelBuilder
|
||||
*/
|
||||
public ModelBuilder() {
|
||||
this(List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new ModelBuilder with the provided entities.
|
||||
*/
|
||||
public ModelBuilder(List<DbEntity<? extends SerializableObject>> entities) {
|
||||
this.entities = new ArrayList<>(entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable List of all entities.
|
||||
*
|
||||
* @return an unmodifiable List of all entities
|
||||
*/
|
||||
public List<DbEntity<? extends SerializableObject>> getEntities() {
|
||||
return Collections.unmodifiableList(entities);
|
||||
}
|
||||
|
||||
public List<DbEntityBuilder<? extends SerializableObject>> entities() {
|
||||
return entities.stream().map(e -> new DbEntityBuilder<>(this, e)).collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database model for the requested class or null if not present.
|
||||
*
|
||||
* @param clazz the class of the model class
|
||||
* @param <T> the type of model class
|
||||
* @return the database model for the requested class or null if not present.
|
||||
*/
|
||||
public <T extends SerializableObject> DbEntity<T> getEntity(Class<T> clazz) {
|
||||
Check.notNull(clazz, "clazz");
|
||||
return getEntity(clazz.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database model for the requested type name or null if not present.
|
||||
*
|
||||
* @param typeName the class name (including the package name) of the model class
|
||||
* @param <T> the type of model class
|
||||
* @return the database model for the requested class or null if not present.
|
||||
*/
|
||||
public <T extends SerializableObject> DbEntity<T> getEntity(String typeName) {
|
||||
Check.notNull(typeName, "typeName");
|
||||
return (DbEntity<T>) entities.stream().filter(e -> e.getTypeName().equals(typeName)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DbEntity for the requested class or creates a new empty DbEntity if none exists.
|
||||
*
|
||||
* @param clazz the class of the model class
|
||||
* @param <T> the type of model class
|
||||
* @return the DbEntity for the requested class or the newly created empty DbEntity if none existed.
|
||||
*/
|
||||
public <T extends SerializableObject> DbEntityBuilder<T> entity(Class<T> clazz) {
|
||||
Check.notNull(clazz, "clazz");
|
||||
var entity = (DbEntity<T>) getEntity(clazz);
|
||||
if (entity == null) {
|
||||
entity = new DbEntity<>(clazz);
|
||||
entities.add(entity);
|
||||
}
|
||||
return new DbEntityBuilder<>(this, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DbEntity for the requested class or creates a new empty DbEntity if none exists.
|
||||
*
|
||||
* @param typeName the class name (including package name) of the model class
|
||||
* @param <T> the type of model class
|
||||
* @return the DbEntity for the requested class or the newly created empty DbEntity if none existed.
|
||||
*/
|
||||
public <T extends SerializableObject> DbEntityBuilder<T> entity(String typeName) {
|
||||
Check.notNull(typeName, "typeName");
|
||||
var entity = (DbEntity<T>) getEntity(typeName);
|
||||
if (entity == null) {
|
||||
entity = new DbEntity<>(typeName);
|
||||
entities.add(entity);
|
||||
}
|
||||
return new DbEntityBuilder<>(this, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the entity from this ModelBuilder. This function also drops referencing foreign keys.
|
||||
*
|
||||
* @param entity the entity to drop
|
||||
* @param <T> the type of the entity
|
||||
* @return whether the entity was present in this ModelBuilder
|
||||
*/
|
||||
public <T extends SerializableObject> boolean dropEntity(DbEntity<T> entity) {
|
||||
Check.notNull(entity, "entity");
|
||||
var removed = this.entities.remove(entity);
|
||||
if (!removed) {
|
||||
return false;
|
||||
}
|
||||
for (DbEntity<? extends SerializableObject> e : this.entities) {
|
||||
e.getForeignKeys().stream()
|
||||
.filter(fk -> fk.getReferencedEntity() == entity)
|
||||
.toList()
|
||||
.forEach(e::dropForeignKey);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public ModelBuilder clone() {
|
||||
//copy entities
|
||||
var entities = new ArrayList<DbEntity>();
|
||||
for (int i = 0; i < this.entities.size(); i++) {
|
||||
var old = this.entities.get(i);
|
||||
var entity = (DbEntity<?>) new DbEntity(old.getTypeName());
|
||||
entity.setName(old.getName());
|
||||
entity.setTypeName(old.getTypeName());
|
||||
|
||||
//add fields
|
||||
old.getFields().stream().map(e -> {
|
||||
var nf = new DbField(entity, e.getName(), e.getTypeName());
|
||||
nf.setField(e.getField());
|
||||
nf.setType(e.getType());
|
||||
nf.setSqlType(e.getSqlType());
|
||||
nf.setNotNull(e.isNotNull());
|
||||
nf.setModelField(e.isModelField());
|
||||
nf.setDatabaseField(e.isDatabaseField());
|
||||
return nf;
|
||||
})
|
||||
.forEach(entity::addIfAbsent);
|
||||
|
||||
//apply exposed foreign keys
|
||||
old.getFields().stream().filter(e -> e.getExposingForeignKeyOf() != null).forEach(e -> {
|
||||
var nf = entity.getFields().stream().filter(f -> f.getName().equals(e.getName())).findFirst().get(); //get(): should always be there
|
||||
nf.setExposingForeignKeyOf(old.getFields().stream().filter(f -> f.getName().equals(e.getExposingForeignKeyOf().getName())).findFirst().get()); //get(): should always be there
|
||||
});
|
||||
|
||||
entity.setName(old.getName());
|
||||
if (old.getPrimaryKey() != null) {
|
||||
var newPkFields = old.getPrimaryKey().getFields().stream()
|
||||
.map(pkfield -> entity.getFields().stream().filter(f -> f.getName().equals(pkfield.getName())).findFirst().get()) //get(): should always be there
|
||||
.toList();
|
||||
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, (List) newPkFields));
|
||||
}
|
||||
entities.add(entity);
|
||||
}
|
||||
|
||||
//copy keys
|
||||
for (int i = 0; i < entities.size(); i++) {
|
||||
var entity = entities.get(i);
|
||||
var old = this.entities.get(i);
|
||||
for (ForeignKeyConstraint foreignKey : old.getForeignKeys()) {
|
||||
var fkFields = foreignKey.getFields().stream()
|
||||
.map(fkField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(fkField.getName())).findFirst().get()) //get(): should always be there
|
||||
.toList();
|
||||
var refEntity = entities.stream().filter(e -> e.getName().equals(foreignKey.getReferencedEntity().getName())).findFirst().get();//should always be there
|
||||
var fkRefFields = foreignKey.getReferencedFields().stream()
|
||||
.map(fkField -> refEntity.getFields().stream().filter(f -> ((DbField) f).getName().equals(fkField.getName())).findFirst().get()) //get(): should always be there
|
||||
.toList();
|
||||
entities.get(i).addForeignKey(new ForeignKeyConstraint(entity, (List) fkFields, refEntity, (List) fkRefFields, foreignKey.getOnUpdate(), foreignKey.getOnDelete()));
|
||||
}
|
||||
|
||||
for (UniqueKeyConstraint uniqueKey : old.getUniqueKeys()) {
|
||||
var newUkFields = uniqueKey.getFields().stream()
|
||||
.map(ukField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(ukField.getName())).findFirst().get()) //get(): should always be there
|
||||
.toList();
|
||||
entity.addUniqueKey(new UniqueKeyConstraint(entity, (List) newUkFields));
|
||||
}
|
||||
for (KeyConstraint key : old.getKeys()) {
|
||||
var newKFields = key.getFields().stream()
|
||||
.map(kField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(kField.getName())).findFirst().get()) //get(): should always be there
|
||||
.toList();
|
||||
entity.addKey(new KeyConstraint(entity, (List) newKFields));
|
||||
}
|
||||
for (IndexConstraint index : old.getIndexes()) {
|
||||
var newIFields = index.getFields().stream()
|
||||
.map(iField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(iField.getName())).findFirst().get()) //get(): should always be there
|
||||
.toList();
|
||||
entity.addIndex(new IndexConstraint(entity, (List) newIFields));
|
||||
}
|
||||
}
|
||||
return new ModelBuilder((List) entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ModelBuilder that = (ModelBuilder) o;
|
||||
return entities.equals(that.entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(entities);
|
||||
}
|
||||
}
|
||||
35
core/src/main/java/jef/model/ModelBuilderOptions.java
Normal file
35
core/src/main/java/jef/model/ModelBuilderOptions.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.annotations.processors.AnnotationProcessor;
|
||||
import jef.model.annotations.processors.ForeignKeyProcessor;
|
||||
import jef.model.annotations.processors.IndexProcessor;
|
||||
import jef.model.annotations.processors.KeyProcessor;
|
||||
import jef.model.annotations.processors.NotNullProcessor;
|
||||
import jef.model.annotations.processors.UniqueProcessor;
|
||||
import 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
|
||||
));
|
||||
this.contextOptions = new DbContextOptions(null);//TODO
|
||||
}
|
||||
}
|
||||
15
core/src/main/java/jef/model/ModelException.java
Normal file
15
core/src/main/java/jef/model/ModelException.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package jef.model;
|
||||
|
||||
public class ModelException extends RuntimeException {
|
||||
public ModelException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ModelException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ModelException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
62
core/src/main/java/jef/model/PrimaryKeyInitializer.java
Normal file
62
core/src/main/java/jef/model/PrimaryKeyInitializer.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.annotations.Id;
|
||||
import jef.model.annotations.Transient;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class PrimaryKeyInitializer {
|
||||
static void initPrimaryKeys(ModelBuilder mb) {
|
||||
var entities = mb.entities();
|
||||
for (int i = 0; i < entities.size(); i++) {
|
||||
var entity = entities.get(i);
|
||||
initPrimaryKeys(mb, entity);
|
||||
}
|
||||
}
|
||||
|
||||
static void initPrimaryKeys(ModelBuilder mb, DbEntityBuilder<?> entity) {
|
||||
var fields = ReflectionUtil.getFieldsRecursive(entity.type().orElseThrow());
|
||||
var idFields = new ArrayList<Field>();
|
||||
|
||||
//search for fields with @Id annotation
|
||||
for (var f : fields) {
|
||||
if (f.getAnnotationsByType(Transient.class).length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (!f.getType().isPrimitive()) {
|
||||
continue;
|
||||
}
|
||||
if (f.getAnnotationsByType(Id.class).length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//fields in same class check
|
||||
if (idFields.size() == 0 || idFields.get(0).getDeclaringClass() == f.getDeclaringClass()) {
|
||||
idFields.add(f);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idFields.isEmpty()) {
|
||||
for (var f : fields) {
|
||||
if (f.getAnnotationsByType(Transient.class).length > 0) {
|
||||
continue;
|
||||
}
|
||||
if (!f.getType().isPrimitive()) {
|
||||
continue;
|
||||
}
|
||||
if (f.getName().equals("id")) {
|
||||
idFields.add(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!idFields.isEmpty()) {
|
||||
var dbfields = idFields.stream().map(e -> entity.field(e).getField().getName()).toList();
|
||||
entity.hasOne(dbfields.toArray(String[]::new)).isPrimaryKey(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
core/src/main/java/jef/model/ReflectionUtil.java
Normal file
31
core/src/main/java/jef/model/ReflectionUtil.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
class ReflectionUtil {
|
||||
/**
|
||||
* Returns a list of all declared fields of the given class.
|
||||
* This also includes fields from super classes up until SerializableObject (exclusive).
|
||||
* The fields within the same are not ordered in any particular way.
|
||||
* The overall ordering is super class fields last, i.e., [...fields of the passed class, ...super class fields, ...super-super class fields, ...]
|
||||
*
|
||||
* @param clazz the class the go the declared fields from
|
||||
* @return a list of all declared fields of the given class, including super class fields
|
||||
*/
|
||||
static Collection<Field> getFieldsRecursive(Class<? extends SerializableObject> clazz) {
|
||||
var fields = new HashMap<String, Field>();
|
||||
do {
|
||||
for (Field f : clazz.getDeclaredFields()) {
|
||||
if (!fields.containsKey(f.getName())) {
|
||||
fields.put(f.getName(), f);
|
||||
}
|
||||
}
|
||||
clazz = (Class<? extends SerializableObject>) clazz.getSuperclass();
|
||||
} while (clazz != SerializableObject.class);
|
||||
return fields.values();
|
||||
}
|
||||
}
|
||||
21
core/src/main/java/jef/model/SqlTypeMapper.java
Normal file
21
core/src/main/java/jef/model/SqlTypeMapper.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package jef.model;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class SqlTypeMapper {
|
||||
public Optional<String> map(String typeName) {
|
||||
if (typeName == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(switch (typeName) {
|
||||
case "java.lang.String" -> "VARCHAR(255)";//TODO add length and precision as param
|
||||
case "int" -> "INT(11)";
|
||||
case "float" -> "FLOAT";
|
||||
case "double" -> "DOUBLE";
|
||||
case "boolean" -> "INT(11)";
|
||||
case "short" -> "INT(11)";
|
||||
case "long" -> "BIGINT";
|
||||
default -> null;
|
||||
});
|
||||
}
|
||||
}
|
||||
14
core/src/main/java/jef/model/annotations/Clazz.java
Normal file
14
core/src/main/java/jef/model/annotations/Clazz.java
Normal file
@@ -0,0 +1,14 @@
|
||||
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 Clazz {
|
||||
Class<?> value();
|
||||
}
|
||||
23
core/src/main/java/jef/model/annotations/ForeignKey.java
Normal file
23
core/src/main/java/jef/model/annotations/ForeignKey.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package jef.model.annotations;
|
||||
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
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 ForeignKey {
|
||||
String getterOrField();
|
||||
|
||||
Class<? extends SerializableObject> entity() default SerializableObject.class;
|
||||
|
||||
ForeignKeyConstraint.Action onUpdate() default ForeignKeyConstraint.Action.RESTRICT;
|
||||
|
||||
ForeignKeyConstraint.Action onDelete() default ForeignKeyConstraint.Action.CASCADE;
|
||||
}
|
||||
13
core/src/main/java/jef/model/annotations/Id.java
Normal file
13
core/src/main/java/jef/model/annotations/Id.java
Normal file
@@ -0,0 +1,13 @@
|
||||
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 Id {
|
||||
}
|
||||
14
core/src/main/java/jef/model/annotations/Index.java
Normal file
14
core/src/main/java/jef/model/annotations/Index.java
Normal file
@@ -0,0 +1,14 @@
|
||||
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, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Index {
|
||||
String[] gettersOrFields() default {};
|
||||
}
|
||||
14
core/src/main/java/jef/model/annotations/Key.java
Normal file
14
core/src/main/java/jef/model/annotations/Key.java
Normal file
@@ -0,0 +1,14 @@
|
||||
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, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Key {
|
||||
String[] gettersOrFields() default {};
|
||||
}
|
||||
13
core/src/main/java/jef/model/annotations/NotNull.java
Normal file
13
core/src/main/java/jef/model/annotations/NotNull.java
Normal file
@@ -0,0 +1,13 @@
|
||||
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 NotNull {
|
||||
}
|
||||
13
core/src/main/java/jef/model/annotations/Transient.java
Normal file
13
core/src/main/java/jef/model/annotations/Transient.java
Normal file
@@ -0,0 +1,13 @@
|
||||
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 Transient {
|
||||
}
|
||||
14
core/src/main/java/jef/model/annotations/Unique.java
Normal file
14
core/src/main/java/jef/model/annotations/Unique.java
Normal file
@@ -0,0 +1,14 @@
|
||||
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, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Unique {
|
||||
String[] gettersOrFields() default {};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.ModelBuilder;
|
||||
|
||||
public interface AnnotationProcessor {
|
||||
|
||||
void apply(ModelBuilder mb);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.ModelException;
|
||||
import jef.model.annotations.ForeignKey;
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ForeignKeyProcessor implements AnnotationProcessor {
|
||||
public static final ForeignKeyProcessor INSTANCE = new ForeignKeyProcessor();
|
||||
|
||||
@Override
|
||||
public void apply(ModelBuilder mb) {
|
||||
for (var entity : mb.entities()) {
|
||||
//annotation on fields
|
||||
for (var field : entity.fields()) {
|
||||
if (field.getField().isModelField() && field.getField().getField().getAnnotationsByType(ForeignKey.class).length > 0) {
|
||||
var anno = field.getField().getField().getAnnotationsByType(ForeignKey.class)[0];
|
||||
// var dbfield = entity.getField(field.getField());
|
||||
// if (dbfield == null) {
|
||||
// throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.getType().getSimpleName());
|
||||
// }
|
||||
var exposeForeignKey = anno.entity() == SerializableObject.class;
|
||||
var refEntity = exposeForeignKey ? entity.getEntity() : mb.getEntity(anno.entity());
|
||||
if (!exposeForeignKey && refEntity == null) {
|
||||
throw new ModelException("Could not find referenced entity " + anno.entity().getSimpleName() + " (via @" + ForeignKey.class.getSimpleName()
|
||||
+ " in " + entity.className() + "::" + field.getField().getName() + ")");
|
||||
}
|
||||
var refEntityBuilder = mb.entity(refEntity.getTypeName());
|
||||
Field refClassField;
|
||||
try {
|
||||
refClassField = getField(refEntityBuilder, anno.getterOrField());
|
||||
} catch (ModelException e) {
|
||||
throw new ModelException(e.getMessage() + " (via @" + ForeignKey.class.getSimpleName()
|
||||
+ " in " + entity.className() + "::" + field.getField().getName() + ")");
|
||||
}
|
||||
var refField = refEntity.getField(refClassField);
|
||||
|
||||
if (exposeForeignKey) {
|
||||
// field.getField().setExposingForeignKeyOf(refField);
|
||||
refField.setExposingForeignKeyOf(field.getField());
|
||||
continue;
|
||||
}
|
||||
|
||||
//check for primary key
|
||||
if (refEntity.getPrimaryKey() == null) {
|
||||
throw new ModelException("Entity " + refEntityBuilder.className() + " does not have a primary key and can "
|
||||
+ "therefore not be referenced (via @" + ForeignKey.class.getSimpleName()
|
||||
+ " in " + entity.className() + "::" + field.getField().getName() + ")");
|
||||
}
|
||||
|
||||
//check if references field is the primary key of the refeneced entity
|
||||
if (!refEntity.getPrimaryKey().getFields().equals(List.of(refField))) {
|
||||
throw new ModelException(refEntityBuilder.className() + "::" + refField.getField().getName()
|
||||
+ " is not equal to the primary key of entity " + refEntityBuilder.className()
|
||||
+ " (" + refEntity.getPrimaryKey().getFields().stream().map(e -> e.getField().getName()).collect(Collectors.joining(", ")) + ")"
|
||||
+ " and can therefore not be referenced (via @" + ForeignKey.class.getSimpleName()
|
||||
+ " in " + entity.className() + "::" + field.getField().getName() + ")");
|
||||
}
|
||||
entity.getEntity().addForeignKey(new ForeignKeyConstraint(entity.getEntity(), List.of(field.getField()), refEntity, List.of(refField), anno.onUpdate(), anno.onDelete()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Field getField(DbEntityBuilder<? extends SerializableObject> entity, String fieldOrGetter) {
|
||||
Method getter = null;
|
||||
try {
|
||||
getter = entity.type().get().getDeclaredMethod(fieldOrGetter);
|
||||
} catch (NoSuchMethodException e) {
|
||||
}
|
||||
|
||||
Field field = null;
|
||||
try {
|
||||
if (getter != null && getter.getName().length() > 3 && getter.getName().startsWith("get")) {
|
||||
var name = getter.getName().substring(3, 4).toLowerCase(Locale.ROOT) + getter.getName().substring(4); //HACK
|
||||
field = entity.type().get().getDeclaredField(name);
|
||||
} else {
|
||||
field = entity.type().get().getDeclaredField(fieldOrGetter);
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
}
|
||||
|
||||
if (field == null) {
|
||||
throw new ModelException("Cannot find getter/field " + entity.className() + "::" + fieldOrGetter);
|
||||
}
|
||||
return field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.annotations.Index;
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
|
||||
public class IndexProcessor extends KeyProcessorBase<IndexConstraint, Index> {
|
||||
public static final IndexProcessor INSTANCE = new IndexProcessor();
|
||||
|
||||
private IndexProcessor() {
|
||||
super(Index.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IndexConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
|
||||
return new IndexConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConstraint(IndexConstraint constr) {
|
||||
constr.getEntity().addIndex(constr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getGettersOrFields(Annotation annotation) {
|
||||
return ((Index) annotation).gettersOrFields();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.annotations.Key;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
|
||||
public class KeyProcessor extends KeyProcessorBase<KeyConstraint, Key> {
|
||||
public static final KeyProcessor INSTANCE = new KeyProcessor();
|
||||
|
||||
private KeyProcessor() {
|
||||
super(Key.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
|
||||
return new KeyConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConstraint(KeyConstraint constr) {
|
||||
constr.getEntity().addKey(constr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getGettersOrFields(Annotation annotation) {
|
||||
return ((Key) annotation).gettersOrFields();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.asm.OptimizedAsmParser;
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.ModelException;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
abstract class KeyProcessorBase<K, T extends Annotation> implements AnnotationProcessor {
|
||||
private final Class<T> annotationClass;
|
||||
|
||||
protected KeyProcessorBase(Class<T> annotationClass) {
|
||||
this.annotationClass = annotationClass;
|
||||
}
|
||||
|
||||
protected abstract K initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields);
|
||||
|
||||
protected abstract void addConstraint(K constr);
|
||||
|
||||
protected abstract String[] getGettersOrFields(Annotation annotation);
|
||||
|
||||
@Override
|
||||
public void apply(ModelBuilder mb) {
|
||||
for (DbEntityBuilder<? extends SerializableObject> entity : mb.entities()) {
|
||||
//class annotation
|
||||
var classAnno = entity.type().get().getAnnotationsByType(annotationClass);
|
||||
for (T t : classAnno) {
|
||||
var classFields = Arrays.stream(getGettersOrFields(t))
|
||||
.map(name -> getField(entity, name))
|
||||
.map(field -> {
|
||||
var dbfield = (DbFieldBuilder<?>) entity.field(field);
|
||||
if (dbfield == null) {
|
||||
throw new ModelException("DbField with field " + field.getName() + " not found in " + entity.className());
|
||||
}
|
||||
return dbfield;
|
||||
}).toList();
|
||||
addConstraint(initConstraint(entity, new ArrayList<>(classFields)));
|
||||
}
|
||||
|
||||
//annotation on fields
|
||||
for (DbFieldBuilder<?> field : entity.fields()) {
|
||||
if (field.getField().getField() != null && field.getField().getField().getAnnotationsByType(annotationClass).length > 0) {
|
||||
var dbfield = entity.field(field.getField().getField());
|
||||
if (dbfield == null) {
|
||||
throw new ModelException("DbField with field " + field.getField().getName() + " not found in " + entity.className());
|
||||
}
|
||||
addConstraint(initConstraint(entity, List.of(dbfield)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Field getField(DbEntityBuilder<? extends SerializableObject> entity, String fieldOrGetter) {
|
||||
Method getter = null;
|
||||
try {
|
||||
getter = entity.type().orElseThrow().getDeclaredMethod(fieldOrGetter);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
Field field = null;
|
||||
try {
|
||||
if (getter != null) {
|
||||
var res = new OptimizedAsmParser(getter ).parse();
|
||||
var name = res.getAccessedFields().stream().findFirst().orElseThrow().getName();
|
||||
field = entity.type().orElseThrow().getDeclaredField(name);
|
||||
} else {
|
||||
field = entity.type().orElseThrow().getDeclaredField(fieldOrGetter);
|
||||
}
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
|
||||
if (field == null) {
|
||||
throw new ModelException("Cannot find getter/field '" + fieldOrGetter + "' in " + entity.className());
|
||||
}
|
||||
return field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.annotations.NotNull;
|
||||
import jef.serializable.SerializableObject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class NotNullProcessor implements AnnotationProcessor {
|
||||
public static final NotNullProcessor INSTANCE = new NotNullProcessor();
|
||||
|
||||
@Override
|
||||
public void apply(ModelBuilder mb) {
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
if (field.getField() != null && field.getField().getAnnotationsByType(NotNull.class).length > 0) {
|
||||
field.setNotNull(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jef.model.annotations.processors;
|
||||
|
||||
import jef.model.DbEntityBuilder;
|
||||
import jef.model.DbFieldBuilder;
|
||||
import jef.model.annotations.Unique;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
|
||||
public class UniqueProcessor extends KeyProcessorBase<UniqueKeyConstraint, Unique> {
|
||||
public static final UniqueProcessor INSTANCE = new UniqueProcessor();
|
||||
|
||||
private UniqueProcessor() {
|
||||
super(Unique.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UniqueKeyConstraint initConstraint(DbEntityBuilder<? extends SerializableObject> entity, List<DbFieldBuilder<?>> fields) {
|
||||
return new UniqueKeyConstraint(entity.getEntity(), (List)fields.stream().map(DbFieldBuilder::getField).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConstraint(UniqueKeyConstraint constr) {
|
||||
constr.getEntity().addUniqueKey(constr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getGettersOrFields(Annotation annotation) {
|
||||
return ((Unique) annotation).gettersOrFields();
|
||||
}
|
||||
}
|
||||
29
core/src/main/java/jef/model/constraints/Constraint.java
Normal file
29
core/src/main/java/jef/model/constraints/Constraint.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface Constraint {
|
||||
/**
|
||||
* Returns the DbEntity containing the constraint.
|
||||
*
|
||||
* @return he DbEntity containing the constraint
|
||||
*/
|
||||
DbEntity<?> getEntity();
|
||||
|
||||
/**
|
||||
* Returns the name of the constraint.
|
||||
*
|
||||
* @return the name of the constraint
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the fields of {@code getEntity()} involved in this constraint.
|
||||
*
|
||||
* @return the fields of {@code getEntity()} involved in this constraint
|
||||
*/
|
||||
List<DbField<?>> getFields();
|
||||
}
|
||||
37
core/src/main/java/jef/model/constraints/ConstraintBase.java
Normal file
37
core/src/main/java/jef/model/constraints/ConstraintBase.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.util.Check;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
public abstract class ConstraintBase implements Constraint {
|
||||
protected final DbEntity<?> entity;
|
||||
protected final List<DbField<?>> fields;
|
||||
|
||||
public ConstraintBase(DbEntity<?> entity, List<DbField<?>> fields) {
|
||||
this.entity = Check.notNull(entity, "entity");
|
||||
this.fields = Check.notNull(fields, "fields");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ConstraintBase that = (ConstraintBase) o;
|
||||
|
||||
//only compare names
|
||||
return entity.getName().equals(that.entity.getName())
|
||||
&& fields.stream().map(DbField::getName).toList().equals(that.fields.stream().map(DbField::getName).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
//only hash names
|
||||
return Objects.hash(entity.getName(), fields.stream().map(DbField::getName).toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import jef.util.Check;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class ForeignKeyConstraint extends ConstraintBase {
|
||||
private final DbEntity<?> referencedEntity;
|
||||
private final List<DbField<?>> referencedFields;
|
||||
private Action onUpdate;
|
||||
private Action onDelete;
|
||||
|
||||
public ForeignKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields, DbEntity<?> referencedEntity, List<DbField<?>> referencedFields, Action onUpdate, Action onDelete) {
|
||||
super(entity, fields);
|
||||
this.referencedEntity = Check.notNull(referencedEntity, "referencedEntity");
|
||||
this.referencedFields = Check.notNull(referencedFields, "referencedFields");
|
||||
this.onUpdate = Check.notNull(onUpdate, "onUpdate");
|
||||
this.onDelete = Check.notNull(onDelete, "onDelete");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
ForeignKeyConstraint that = (ForeignKeyConstraint) o;
|
||||
|
||||
//only compare names
|
||||
return referencedEntity.getName().equals(that.referencedEntity.getName())
|
||||
&& referencedFields.stream().map(DbField::getName).toList().equals(that.referencedFields.stream().map(DbField::getName).toList())
|
||||
&& onUpdate == that.onUpdate
|
||||
&& onDelete == that.onDelete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
//only hash names
|
||||
return Objects.hash(super.hashCode(), referencedEntity.getName(), referencedFields.stream().map(DbField::getName).toList(), onUpdate, onDelete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "FK_" + entity.getName() + "_" + referencedEntity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CONSTRAINT " + getName() + " FOREIGN KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ") "
|
||||
+ "REFERENCES " + referencedEntity.getName() + "(" + referencedFields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
CASCADE,
|
||||
RESTRICT,
|
||||
SET_NULL,
|
||||
NO_ACTION,
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class IndexConstraint extends ConstraintBase {
|
||||
public IndexConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
|
||||
super(entity, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "I_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CONSTRAINT " + getName() + " INDEX (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
}
|
||||
27
core/src/main/java/jef/model/constraints/KeyConstraint.java
Normal file
27
core/src/main/java/jef/model/constraints/KeyConstraint.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class KeyConstraint extends ConstraintBase {
|
||||
public KeyConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
|
||||
super(entity, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "K_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CONSTRAINT " + getName() + " KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PrimaryKeyConstraint extends ConstraintBase {
|
||||
public PrimaryKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
|
||||
super(entity, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "PRIMARY";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + " KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package jef.model.constraints;
|
||||
|
||||
import jef.model.DbEntity;
|
||||
import jef.model.DbField;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UniqueKeyConstraint extends ConstraintBase {
|
||||
public UniqueKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
|
||||
super(entity, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "U_" + entity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CONSTRAINT " + getName() + " UNIQUE KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
}
|
||||
7
core/src/main/java/jef/model/migration/Migration.java
Normal file
7
core/src/main/java/jef/model/migration/Migration.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package jef.model.migration;
|
||||
|
||||
public interface Migration {
|
||||
void up(MigrationBuilder migrationBuilder);
|
||||
|
||||
void down(MigrationBuilder migrationBuilder);
|
||||
}
|
||||
104
core/src/main/java/jef/model/migration/MigrationBuilder.java
Normal file
104
core/src/main/java/jef/model/migration/MigrationBuilder.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package jef.model.migration;
|
||||
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
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 lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class MigrationBuilder {
|
||||
private final List<MigrationOperation.Builder<?>> operations = new ArrayList<>();
|
||||
|
||||
public AddTableOperation.Builder addTable(String table, List<AddFieldOperation.Builder> fields) {
|
||||
var op = new AddTableOperation.Builder(table, fields);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public RenameTableOperation.Builder renameTable(String oldName, String newName) {
|
||||
var op = new RenameTableOperation.Builder(oldName, newName);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public DropTableOperation.Builder dropTable(String table) {
|
||||
var op = new DropTableOperation.Builder(table);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public AddFieldOperation.Builder addField(String table, String field) {
|
||||
var op = new AddFieldOperation.Builder(table, field);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public RenameFieldOperation.Builder renameField(String table, String oldName, String newName) {
|
||||
var op = new RenameFieldOperation.Builder(table, oldName, newName);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public UpdateFieldOperation.Builder updateField(String table, String field) {
|
||||
var op = new UpdateFieldOperation.Builder(table, field);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public DropFieldOperation.Builder dropField(String table, String field) {
|
||||
var op = new DropFieldOperation.Builder(table, field);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public AddPrimaryKeyOperation.Builder addPrimaryKey(String name, String table, List<String> fields) {
|
||||
var op = new AddPrimaryKeyOperation.Builder(name, table, fields);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public AddForeignKeyOperation.Builder addForeignKey(String name, String table, List<String> fields, String referencedTable, List<String> referencedFields, ForeignKeyConstraint.Action onUpdate, ForeignKeyConstraint.Action onDelete) {
|
||||
var op = new AddForeignKeyOperation.Builder(name, table, fields, referencedTable, referencedFields, onUpdate, onDelete);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public AddUniqueKeyOperation.Builder addUniqueKey(String name, String table, List<String> fields) {
|
||||
var op = new AddUniqueKeyOperation.Builder(name, table, fields);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public AddKeyOperation.Builder addKey(String name, String table, List<String> fields) {
|
||||
var op = new AddKeyOperation.Builder(name, table, fields);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public AddIndexOperation.Builder addIndex(String name, String table, List<String> fields) {
|
||||
var op = new AddIndexOperation.Builder(name, table, fields);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
|
||||
public DropConstraintOperation.Builder dropConstraint(String name, String table) {
|
||||
var op = new DropConstraintOperation.Builder(name, table);
|
||||
operations.add(op);
|
||||
return op;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package jef.model.migration.creator;
|
||||
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.model.migration.Migration;
|
||||
import jef.model.migration.MigrationBuilder;
|
||||
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 lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MigrationBuilderGenerator {
|
||||
private final List<MigrationOperation> opsUp;
|
||||
private final List<MigrationOperation> opsDown;
|
||||
private final String name;
|
||||
private final String packageName;
|
||||
|
||||
private final Set<Class<?>> imports = new HashSet<>();
|
||||
@Getter
|
||||
private String java = null;
|
||||
|
||||
public MigrationBuilderGenerator generate() {
|
||||
if (java == null) {
|
||||
java = generateMigrationBuilderJava();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private String generateMigrationBuilderJava() {
|
||||
imports.add(Migration.class);
|
||||
imports.add(MigrationBuilder.class);
|
||||
|
||||
//generate java for migration step and concat
|
||||
var migrationUp = String.join("\n\n", opsUp.stream().map(this::getMigrationJava).toList());
|
||||
var migrationDown = String.join("\n\n", opsDown.stream().map(this::getMigrationJava).toList());
|
||||
|
||||
//generate migration class file
|
||||
var normalImports = imports.stream().filter(e -> !e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
|
||||
var javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
|
||||
var normalImportsString = normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
|
||||
var javaImportsString = javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n"));
|
||||
|
||||
var java = (packageName != null ? "package " + packageName + ";\n\n" : "")
|
||||
+ normalImportsString + (normalImportsString.length() > 0 ? "\n\n" : "")
|
||||
+ javaImportsString + (javaImportsString.length() > 0 ? "\n\n" : "")
|
||||
+ "public class " + name + " implements Migration {\n"
|
||||
+ " public void up(MigrationBuilder mb) {\n"
|
||||
+ " " + migrationUp.replace("\n", "\n ") + "\n"
|
||||
+ " }\n"
|
||||
+ "\n"
|
||||
+ " public void down(MigrationBuilder mb) {\n"
|
||||
+ " " + migrationDown.replace("\n", "\n ") + "\n"
|
||||
+ " }\n"
|
||||
+ "}\n";
|
||||
return java;
|
||||
}
|
||||
|
||||
//mapper stuff
|
||||
private final Map<Class<? extends MigrationOperation>, Function<? extends MigrationOperation, String>> OP_TO_STRING_MAPPERS = initMapper();
|
||||
private final Function<MigrationOperation, String> UNSUPPORTED_MIGRATION_OPERATION_FUNCTION = (MigrationOperation i) -> {
|
||||
throw new RuntimeException(new UnsupportedOperationException("Unsupported migration operation: " + i.getClass().getSimpleName()));
|
||||
};
|
||||
|
||||
private Map<Class<? extends MigrationOperation>, Function<? extends MigrationOperation, String>> initMapper() {
|
||||
Map<Class<? extends MigrationOperation>, Function<? extends MigrationOperation, String>> map = new HashMap<>();
|
||||
map.put(AddFieldOperation.class, (AddFieldOperation op) -> addFieldOp(op));
|
||||
map.put(AddForeignKeyOperation.class, (AddForeignKeyOperation op) -> addForeignKeyOp(op));
|
||||
map.put(AddIndexOperation.class, (AddIndexOperation op) -> addIndexOp(op));
|
||||
map.put(AddKeyOperation.class, (AddKeyOperation op) -> addKeyOp(op));
|
||||
map.put(AddPrimaryKeyOperation.class, (AddPrimaryKeyOperation op) -> addPrimaryKeyOp(op));
|
||||
map.put(AddTableOperation.class, (AddTableOperation op) -> addTableOp(op));
|
||||
map.put(AddUniqueKeyOperation.class, (AddUniqueKeyOperation op) -> addUniqueKeyOp(op));
|
||||
map.put(DropConstraintOperation.class, (DropConstraintOperation op) -> dropConstraintOp(op));
|
||||
map.put(DropFieldOperation.class, (DropFieldOperation op) -> dropFieldOp(op));
|
||||
map.put(DropTableOperation.class, (DropTableOperation op) -> dropTableOp(op));
|
||||
map.put(RenameFieldOperation.class, (RenameFieldOperation op) -> renameFieldOp(op));
|
||||
map.put(RenameTableOperation.class, (RenameTableOperation op) -> renameTableOp(op));
|
||||
map.put(UpdateFieldOperation.class, (UpdateFieldOperation op) -> updateField(op));
|
||||
return map;
|
||||
}
|
||||
|
||||
private String getMigrationJava(MigrationOperation migrationOperation) {
|
||||
var mapper = (Function<MigrationOperation, String>) OP_TO_STRING_MAPPERS.getOrDefault(migrationOperation.getClass(), UNSUPPORTED_MIGRATION_OPERATION_FUNCTION);
|
||||
return mapper.apply(migrationOperation);
|
||||
}
|
||||
|
||||
private String addFieldOp(AddFieldOperation op) {
|
||||
return "mb.addField(\"" + op.getTable() + "\", \"" + op.getField() + "\")" + addFieldOpOptional(op) + ";";
|
||||
}
|
||||
|
||||
private String addFieldOpOptional(AddFieldOperation op) {
|
||||
return "\n"
|
||||
+ " .notNull(" + op.isNotNull() + ")\n"
|
||||
+ " .sqlType(\"" + op.getSqlType() + "\")";
|
||||
}
|
||||
|
||||
private String addForeignKeyOp(AddForeignKeyOperation op) {
|
||||
imports.add(List.class);
|
||||
imports.add(ForeignKeyConstraint.class);
|
||||
imports.add(ForeignKeyConstraint.Action.class);
|
||||
return "mb.addForeignKey(\"" + op.getName() + "\",\n"
|
||||
+ " \"" + op.getTable() + "\",\n"
|
||||
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "),\n"
|
||||
+ " \"" + op.getReferencedTable() + "\",\n"
|
||||
+ " List.of(" + op.getReferencedFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "),\n"
|
||||
+ " ForeignKeyConstraint.Action." + op.getOnUpdate().name() + ",\n"
|
||||
+ " ForeignKeyConstraint.Action." + op.getOnDelete().name() + ");";
|
||||
}
|
||||
|
||||
private String addIndexOp(AddIndexOperation op) {
|
||||
imports.add(List.class);
|
||||
return "mb.addIndex(\"" + op.getName() + "\",\n"
|
||||
+ " \"" + op.getTable() + "\",\n"
|
||||
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
|
||||
}
|
||||
|
||||
private String addKeyOp(AddKeyOperation op) {
|
||||
imports.add(List.class);
|
||||
return "mb.addKey(\"" + op.getName() + "\",\n"
|
||||
+ " \"" + op.getTable() + "\",\n"
|
||||
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
|
||||
}
|
||||
|
||||
private String addPrimaryKeyOp(AddPrimaryKeyOperation op) {
|
||||
imports.add(List.class);
|
||||
return "mb.addPrimaryKey(\"" + op.getName() + "\",\n"
|
||||
+ " \"" + op.getTable() + "\",\n"
|
||||
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
|
||||
}
|
||||
|
||||
private String addTableOp(AddTableOperation op) {
|
||||
imports.add(List.class);
|
||||
imports.add(AddFieldOperation.class);
|
||||
imports.add(AddFieldOperation.Builder.class);
|
||||
return "mb.addTable(\"" + op.getTable() + "\", List.of(\n"
|
||||
+ op.getFields().stream()
|
||||
/**/.map(f -> " new AddFieldOperation.Builder(\"" + op.getTable() + "\", \"" + f.build().getField() + "\")" + addFieldOpOptional(f.build()).replace("\n", "\n "))
|
||||
/**/.collect(Collectors.joining(",\n")) + "\n"
|
||||
+ "));";
|
||||
}
|
||||
|
||||
private String addUniqueKeyOp(AddUniqueKeyOperation op) {
|
||||
imports.add(List.class);
|
||||
return "mb.addUniqueKey(\"" + op.getName() + "\",\n"
|
||||
+ " \"" + op.getTable() + "\",\n"
|
||||
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
|
||||
}
|
||||
|
||||
private String dropConstraintOp(DropConstraintOperation op) {
|
||||
return "mb.dropConstraint(\"" + op.getTable() + "\", \"" + op.getName() + "\");";
|
||||
}
|
||||
|
||||
private String dropFieldOp(DropFieldOperation op) {
|
||||
return "mb.dropField(\"" + op.getTable() + "\", \"" + op.getField() + "\");";
|
||||
}
|
||||
|
||||
private String dropTableOp(DropTableOperation op) {
|
||||
return "mb.dropTable(\"" + op.getTable() + "\");";
|
||||
}
|
||||
|
||||
private String renameFieldOp(RenameFieldOperation op) {
|
||||
return "mb.renameField(\"" + op.getTable() + "\", \"" + op.getOldName() + "\", \"" + op.getNewName() + "\");";
|
||||
}
|
||||
|
||||
private String renameTableOp(RenameTableOperation op) {
|
||||
return "mb.renameTable(\"" + op.getOldName() + "\", \"" + op.getNewName() + "\");";
|
||||
}
|
||||
|
||||
private String updateField(UpdateFieldOperation op) {
|
||||
return "mb.updateField(\"" + op.getTable() + "\", \"" + op.getField() + "\")\n"
|
||||
+ (op.getNewName() != null ? " .newName(\"" + op.getNewName() + "\")\n" : "")
|
||||
+ " .notNull(" + op.isNotNull() + ")\n"
|
||||
+ " .sqlType(" + op.getSqlType() + ");";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
package jef.model.migration.creator;
|
||||
|
||||
import jef.model.DbField;
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.model.SqlTypeMapper;
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.model.constraints.IndexConstraint;
|
||||
import jef.model.constraints.KeyConstraint;
|
||||
import jef.model.constraints.UniqueKeyConstraint;
|
||||
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.util.Check;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MigrationCreator {
|
||||
public Result createMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, String currentSnapshotJava) {
|
||||
var result = new Result();
|
||||
|
||||
//create pre-migration model snapshot class
|
||||
result = generatePreMigrationSnapshot(name, packageName, currentSnapshotJava, result);
|
||||
|
||||
//create migration
|
||||
result = generateMigration(from, to, name, packageName, result);
|
||||
|
||||
//create current model snapshot class
|
||||
result = generatePostMigrationSnapshot(to, packageName, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Result generatePreMigrationSnapshot(String name, String packageName, String currentSnapshotJava, Result result) {
|
||||
String preMigrationSnapshot;
|
||||
if (currentSnapshotJava == null || currentSnapshotJava.isBlank()) {
|
||||
preMigrationSnapshot = generateModelBuilderJava(new ModelBuilder(), name, packageName);
|
||||
} else {
|
||||
preMigrationSnapshot = currentSnapshotJava.replace("CurrentSnapshot", name + "Snapshot");
|
||||
}
|
||||
result.setMigrationSnapshot(preMigrationSnapshot);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Result generatePostMigrationSnapshot(ModelBuilder to, String packageName, Result result) {
|
||||
result.setCurrentSnapshot(generateModelBuilderJava(to, "Current", packageName));
|
||||
return result;
|
||||
}
|
||||
|
||||
private String generateModelBuilderJava(ModelBuilder mb, String name, String packageName) {
|
||||
return new ModelBuilderGenerator(mb, name, packageName, new SqlTypeMapper()).generate().getJava();//TODO mapper
|
||||
}
|
||||
|
||||
private Result generateMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, Result result) {
|
||||
//reduce model builders to changes
|
||||
var mcd = new ModelChangeDetector(from, to).detect();
|
||||
var fromReduced = mcd.getFrom();
|
||||
var toReduced = mcd.getTo();
|
||||
|
||||
//create migration steps
|
||||
var migrationUpOps = new ArrayList<MigrationOperation.Builder>();
|
||||
var migrationDownOps = new ArrayList<MigrationOperation.Builder>();
|
||||
|
||||
generateMigrationSteps(fromReduced, toReduced, from, to, migrationUpOps);
|
||||
generateMigrationSteps(toReduced, fromReduced, to, from, migrationDownOps);
|
||||
|
||||
var builtUpOps = migrationUpOps.stream().map(e -> e.build()).toList();
|
||||
var builtDownOps = migrationDownOps.stream().map(e -> e.build()).toList();
|
||||
|
||||
result.setMigration(new MigrationBuilderGenerator(builtUpOps, builtDownOps, name, packageName).generate().getJava());
|
||||
result.setStepsUp(builtUpOps);
|
||||
result.setStepsDown(builtDownOps);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void generateMigrationSteps(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
//key drop
|
||||
addForeignKeyDropGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addPrimaryKeyDropGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addUniqueKeyDropGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addKeyDropGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addIndexDropGeneration(fromReduced, toReduced, from, to, steps);
|
||||
|
||||
//table
|
||||
addTableDropGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addTableRenameGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addTableAddGeneration(fromReduced, toReduced, from, to, steps);
|
||||
|
||||
//fields
|
||||
addFieldAddRenameUpdateDropGeneration(fromReduced, toReduced, from, to, steps);
|
||||
|
||||
//key add
|
||||
addPrimaryKeyAddGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addForeignKeyAddGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addUniqueKeyAddGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addKeyAddGeneration(fromReduced, toReduced, from, to, steps);
|
||||
addIndexAddGeneration(fromReduced, toReduced, from, to, steps);
|
||||
}
|
||||
|
||||
private void addTableAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var fromEntity = fromReduced.getEntity(toEntity.getTypeName());
|
||||
|
||||
//new entity
|
||||
if (fromEntity == null) {
|
||||
steps.add(new AddTableOperation.Builder(
|
||||
toEntity.getName(),
|
||||
toEntity.getFields().stream()
|
||||
.filter(DbField::isDatabaseField)
|
||||
.map(e -> new AddFieldOperation.Builder(toEntity.getName(), e.getName())
|
||||
.notNull(e.isNotNull())
|
||||
.sqlType(getSqlType(e)))
|
||||
.toList()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getSqlType(DbField<?> e) {
|
||||
return Optional.ofNullable(e.getSqlType()).or(() -> new SqlTypeMapper().map(e.getTypeName())).orElse(null);
|
||||
}
|
||||
|
||||
private void addTableRenameGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var fromEntity = fromReduced.getEntity(toEntity.getTypeName());
|
||||
// entity added
|
||||
if (fromEntity == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//entity rename
|
||||
if (!fromEntity.getName().equals(toEntity.getName())) {
|
||||
steps.add(new RenameTableOperation.Builder(fromEntity.getName(), toEntity.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addTableDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var fromEntity : fromReduced.getEntities()) {
|
||||
var toEntity = toReduced.getEntity(fromEntity.getTypeName());
|
||||
if (toEntity == null) {
|
||||
steps.add(new DropTableOperation.Builder(fromEntity.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFieldAddRenameUpdateDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var fromEntity = fromReduced.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
|
||||
|
||||
//entity added -> nothing to do here
|
||||
if (fromEntity == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var remainingFromFields = new ArrayList<>(fromEntity.getFields());
|
||||
var remainingToFields = new ArrayList<>(toEntity.getFields());
|
||||
|
||||
var handledFrom = new ArrayList<DbField<?>>();
|
||||
var handledTo = new ArrayList<DbField<?>>();
|
||||
|
||||
//fields with same name but different type parameters
|
||||
for (DbField<?> toField : toEntity.getFields()) {
|
||||
var fromField = fromEntity.getFields().stream().filter(e -> e.getName().equals(toField.getName())).findFirst().orElse(null);
|
||||
if (fromField != null) {
|
||||
handledFrom.add(fromField);
|
||||
handledTo.add(toField);
|
||||
//this assumes the reduced ModelBuilders exclude exactly matching entities
|
||||
steps.add(new UpdateFieldOperation.Builder(toField.getEntity().getName(), toField.getName())
|
||||
.notNull(toField.isNotNull()));
|
||||
}
|
||||
}
|
||||
|
||||
//fields with different name but same type parameters
|
||||
remainingFromFields.removeAll(handledFrom);
|
||||
remainingToFields.removeAll(handledTo);
|
||||
|
||||
var map = new ArrayList<FieldCompare>();
|
||||
for (DbField<?> toField : remainingToFields) {
|
||||
for (DbField<?> FromField : remainingFromFields) {
|
||||
map.add(new FieldCompare(FromField, toField));
|
||||
}
|
||||
}
|
||||
map.removeIf(e -> e.getSimilarity() <= 0);
|
||||
map.sort(Comparator.comparingInt(FieldCompare::getSimilarity).reversed());
|
||||
|
||||
for (FieldCompare compare : map) {
|
||||
var toField = compare.getTo();
|
||||
var fromField = compare.getFrom();
|
||||
if (handledFrom.contains(fromField) || handledTo.contains(toField)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
handledFrom.add(fromField);
|
||||
handledTo.add(toField);
|
||||
steps.add(new RenameFieldOperation.Builder(toField.getEntity().getName(), fromField.getName(), toField.getName()));
|
||||
}
|
||||
|
||||
//drop
|
||||
remainingFromFields.removeAll(handledFrom);
|
||||
remainingToFields.removeAll(handledTo);
|
||||
for (DbField<?> fromField : remainingFromFields) {
|
||||
var toField = remainingToFields.stream().filter(e -> e.getName().equals(fromField.getName())).findFirst().orElse(null);
|
||||
if (toField == null) {
|
||||
handledFrom.add(fromField);
|
||||
steps.add(new DropFieldOperation.Builder(fromField.getEntity().getName(), fromField.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
//add
|
||||
remainingFromFields.removeAll(handledFrom);
|
||||
remainingToFields.removeAll(handledTo);
|
||||
for (DbField<?> toField : remainingToFields) {
|
||||
var fromField = remainingFromFields.stream().filter(e -> e.getName().equals(toField.getName())).findFirst().orElse(null);
|
||||
if (fromField == null) {
|
||||
handledTo.add(toField);
|
||||
steps.add(new AddFieldOperation.Builder(toField.getEntity().getName(), toField.getName())
|
||||
.notNull(toField.isNotNull()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPrimaryKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = toEntity.getFields();
|
||||
var involvedForeignKeys = originalEntity.getPrimaryKey() != null
|
||||
&& originalEntity.getPrimaryKey().getFields().stream().anyMatch(involvedFields::contains);
|
||||
if (involvedForeignKeys) {
|
||||
steps.add(new AddPrimaryKeyOperation.Builder(
|
||||
originalEntity.getPrimaryKey().getName(),
|
||||
originalEntity.getPrimaryKey().getEntity().getName(),
|
||||
originalEntity.getPrimaryKey().getFields().stream().map(DbField::getName).collect(Collectors.toList())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addForeignKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = toEntity.getFields();
|
||||
var involvedForeignKeys = originalEntity.getForeignKeys().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getForeignKeys().contains(e))
|
||||
.toList();
|
||||
for (ForeignKeyConstraint foreignKey : involvedForeignKeys) {
|
||||
steps.add(new AddForeignKeyOperation.Builder(
|
||||
foreignKey.getName(),
|
||||
foreignKey.getEntity().getName(),
|
||||
foreignKey.getFields().stream().map(DbField::getName).collect(Collectors.toList()),
|
||||
foreignKey.getReferencedEntity().getName(),
|
||||
foreignKey.getReferencedFields().stream().map(DbField::getName).collect(Collectors.toList()),
|
||||
foreignKey.getOnUpdate(), foreignKey.getOnDelete()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addUniqueKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = toEntity.getFields();
|
||||
var involvedUniqueKeys = originalEntity.getUniqueKeys().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getUniqueKeys().contains(e))
|
||||
.toList();
|
||||
for (UniqueKeyConstraint uniqueKey : involvedUniqueKeys) {
|
||||
steps.add(new AddUniqueKeyOperation.Builder(
|
||||
uniqueKey.getName(),
|
||||
uniqueKey.getEntity().getName(),
|
||||
uniqueKey.getFields().stream().map(DbField::getName).collect(Collectors.toList())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = toEntity.getFields();
|
||||
var involvedKeys = originalEntity.getKeys().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getKeys().contains(e))
|
||||
.toList();
|
||||
for (KeyConstraint key : involvedKeys) {
|
||||
steps.add(new AddKeyOperation.Builder(
|
||||
key.getName(),
|
||||
key.getEntity().getName(),
|
||||
key.getFields().stream().map(DbField::getName).collect(Collectors.toList())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addIndexAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var toEntity : toReduced.getEntities()) {
|
||||
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = toEntity.getFields();
|
||||
var involvedIndexes = originalEntity.getIndexes().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getIndexes().contains(e))
|
||||
.toList();
|
||||
for (IndexConstraint index : involvedIndexes) {
|
||||
steps.add(new AddIndexOperation.Builder(
|
||||
index.getName(),
|
||||
index.getEntity().getName(),
|
||||
index.getFields().stream().map(DbField::getName).collect(Collectors.toList())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPrimaryKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var fromEntity : fromReduced.getEntities()) {
|
||||
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = fromEntity.getFields();
|
||||
if (originalEntity.getPrimaryKey() == null) {
|
||||
continue;
|
||||
}
|
||||
var isPrimaryKeyInvolved = originalEntity.getPrimaryKey().getFields().stream().anyMatch(involvedFields::contains);
|
||||
if (isPrimaryKeyInvolved) {
|
||||
//drop all referencing foreign keys
|
||||
from.getEntities().stream().flatMap(e -> e.getForeignKeys().stream())
|
||||
.filter(foreignKey -> foreignKey.getReferencedEntity() == fromEntity
|
||||
&& foreignKey.getReferencedFields().stream().anyMatch(involvedFields::contains))
|
||||
.forEach(foreignKey -> steps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName())));
|
||||
|
||||
//drop primary
|
||||
steps.add(new DropConstraintOperation.Builder(originalEntity.getPrimaryKey().getName(), fromEntity.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addForeignKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var fromEntity : fromReduced.getEntities()) {
|
||||
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = fromEntity.getFields();
|
||||
var involvedForeignKeys = originalEntity.getForeignKeys().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getForeignKeys().contains(e))
|
||||
.toList();
|
||||
for (ForeignKeyConstraint foreignKey : involvedForeignKeys) {
|
||||
steps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
// for (var fromEntity : from.getEntities()) {
|
||||
// var toEntity = to.getEntity(fromEntity.getType());
|
||||
//
|
||||
// //foreign keys
|
||||
// for (var foreignKey : fromEntity.getForeignKeys()) {
|
||||
// if (toEntity == null || toEntity.getForeignKeys().stream().noneMatch(fk -> compareForeignKey(foreignKey, fk))) {
|
||||
// migrationUpOps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private void addUniqueKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var fromEntity : fromReduced.getEntities()) {
|
||||
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = fromEntity.getFields();
|
||||
var involvedForeignKeys = originalEntity.getUniqueKeys().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getUniqueKeys().contains(e))
|
||||
.toList();
|
||||
for (UniqueKeyConstraint uniqueKey : involvedForeignKeys) {
|
||||
steps.add(new DropConstraintOperation.Builder(uniqueKey.getName(), fromEntity.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
// for (var fromEntity : from.getEntities()) {
|
||||
// var toEntity = to.getEntity(fromEntity.getType());
|
||||
//
|
||||
// //unique keys
|
||||
// for (var uniqueKey : fromEntity.getUniqueKeys()) {
|
||||
// if (toEntity == null || toEntity.getUniqueKeys().stream().noneMatch(uk -> compareConstraint(uniqueKey, uk))) {
|
||||
// steps.add(new DropConstraintOperation.Builder(uniqueKey.getName(), fromEntity.getName()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private void addKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var fromEntity : fromReduced.getEntities()) {
|
||||
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = fromEntity.getFields();
|
||||
var involvedForeignKeys = originalEntity.getKeys().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getKeys().contains(e))
|
||||
.toList();
|
||||
for (KeyConstraint key : involvedForeignKeys) {
|
||||
steps.add(new DropConstraintOperation.Builder(key.getName(), fromEntity.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
// for (var fromEntity : from.getEntities()) {
|
||||
// var toEntity = to.getEntity(fromEntity.getType());
|
||||
//
|
||||
// //keys
|
||||
// for (var key : fromEntity.getKeys()) {
|
||||
// if (toEntity == null || toEntity.getKeys().stream().noneMatch(k -> compareConstraint(key, k))) {
|
||||
// migrationUpOps.add(new DropConstraintOperation.Builder(key.getName(), fromEntity.getName()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private void addIndexDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
|
||||
for (var fromEntity : fromReduced.getEntities()) {
|
||||
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
|
||||
Check.notNull(originalEntity, "originalEntity"); //may never be null
|
||||
var involvedFields = fromEntity.getFields();
|
||||
var involvedForeignKeys = originalEntity.getIndexes().stream()
|
||||
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getIndexes().contains(e))
|
||||
.toList();
|
||||
for (IndexConstraint foreignKey : involvedForeignKeys) {
|
||||
steps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
// for (var fromEntity : from.getEntities()) {
|
||||
// var toEntity = to.getEntity(fromEntity.getType());
|
||||
//
|
||||
// //indexes
|
||||
// for (var index : fromEntity.getIndexes()) {
|
||||
// if (toEntity == null || toEntity.getIndexes().stream().noneMatch(i -> compareConstraint(index, i))) {
|
||||
// steps.add(new DropConstraintOperation.Builder(index.getName(), fromEntity.getName()));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
@NoArgsConstructor(access = AccessLevel.PACKAGE)
|
||||
public static class Result {
|
||||
private List<MigrationOperation> stepsUp = new ArrayList<>();
|
||||
private List<MigrationOperation> stepsDown = new ArrayList<>();
|
||||
private String migration = "";
|
||||
private String migrationSnapshot = "";
|
||||
private String currentSnapshot = "";
|
||||
}
|
||||
|
||||
@Getter
|
||||
private class FieldCompare {
|
||||
private final DbField<?> from;
|
||||
private final DbField<?> to;
|
||||
private final int similarity;
|
||||
|
||||
public FieldCompare(DbField<?> from, DbField<?> to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.similarity = compute();
|
||||
}
|
||||
|
||||
private int compute() {
|
||||
return from.equalsExceptName(to) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
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.SqlTypeMapper;
|
||||
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.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 name;
|
||||
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 " + name + "Snapshot extends DbContext {\n"
|
||||
+ " @Override\n"
|
||||
+ " public void onModelCreate(ModelBuilder mb) {\n"
|
||||
+ indent + "DbEntityBuilder entity;\n"
|
||||
+ indent + "DbEntityBuilder referencedEntity;\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()) {
|
||||
java += indent + "mb.entity(\"" + entity.getTypeName() + "\")\n"
|
||||
+ indent + " .field(\"" + field.getName() + "\", \"" + field.getTypeName() + "\")"
|
||||
+ "\n" + indent + " .sqlType(" + getSqlType(field) + ")"
|
||||
+ (field.isNotNull() ? "\n" + indent + " .isNotNull()" : "")
|
||||
+ "\n" + indent + " .isDatabaseField(" + field.isDatabaseField() + ")"
|
||||
+ "\n" + indent + " .isModelField(" + field.isModelField() + ");\n";
|
||||
}
|
||||
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();
|
||||
|
||||
//finalize
|
||||
java = (packageName != null ? "package " + packageName + ";\n\n" : "")
|
||||
+ normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
|
||||
+ javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
|
||||
+ java;
|
||||
return java;
|
||||
}
|
||||
|
||||
private String getSqlType(DbField<?> f) {
|
||||
return Optional.ofNullable(f.getSqlType()).or(() -> sqlTypeMapper.map(f.getTypeName())).map(e -> "\"" + e + "\"").orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package jef.model.migration.creator;
|
||||
|
||||
import jef.model.ModelBuilder;
|
||||
import jef.util.Check;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Getter
|
||||
public class ModelChangeDetector {
|
||||
private final ModelBuilder from;
|
||||
private final ModelBuilder to;
|
||||
|
||||
public ModelChangeDetector(ModelBuilder from, ModelBuilder to) {
|
||||
this.from = Check.notNull(from, "from").clone();
|
||||
this.to = Check.notNull(to, "to").clone();
|
||||
}
|
||||
|
||||
public ModelChangeDetector detect() {
|
||||
extractChanges();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void extractChanges() {
|
||||
extractChangesFromTo(from, to);
|
||||
extractChangesFromTo(to, from);
|
||||
|
||||
for (int i = 0; i < to.getEntities().size(); i++) {
|
||||
var toEntity = to.getEntities().get(i);
|
||||
var fromEntity = from.getEntities().stream().filter(e -> e.getTypeName().equals(toEntity.getTypeName())).findFirst().orElse(null);
|
||||
if (fromEntity != null) {
|
||||
//entity empty
|
||||
if (toEntity.getName().equals(fromEntity.getName())
|
||||
&& toEntity.getFields().isEmpty()
|
||||
&& toEntity.getPrimaryKey() == null
|
||||
&& toEntity.getForeignKeys().isEmpty()
|
||||
&& toEntity.getUniqueKeys().isEmpty()
|
||||
&& toEntity.getKeys().isEmpty()
|
||||
&& toEntity.getIndexes().isEmpty()
|
||||
&& fromEntity.getFields().isEmpty()
|
||||
&& fromEntity.getPrimaryKey() == null
|
||||
&& fromEntity.getForeignKeys().isEmpty()
|
||||
&& fromEntity.getUniqueKeys().isEmpty()
|
||||
&& fromEntity.getKeys().isEmpty()
|
||||
&& fromEntity.getIndexes().isEmpty()) {
|
||||
to.dropEntity(toEntity);
|
||||
from.dropEntity(fromEntity);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void extractChangesFromTo(ModelBuilder from, ModelBuilder to) {
|
||||
for (int i = 0; i < to.getEntities().size(); i++) {
|
||||
var toEntity = to.getEntities().get(i);
|
||||
var fromEntity = from.getEntities().stream().filter(e -> e.getTypeName().equals(toEntity.getTypeName())).findFirst().orElse(null);
|
||||
if (fromEntity != null) {
|
||||
for (int j = 0; j < toEntity.getFields().size(); j++) {
|
||||
var toField = toEntity.getFields().get(j);
|
||||
var fromField = fromEntity.getFields().stream().filter(e -> e.equals(toField)).findFirst().orElse(null);
|
||||
if (fromField != null) {
|
||||
toEntity.dropField(toField);
|
||||
fromEntity.dropField(fromField);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
if (Objects.equals(fromEntity.getPrimaryKey(), toEntity.getPrimaryKey())) {
|
||||
fromEntity.setPrimaryKey(null);
|
||||
toEntity.setPrimaryKey(null);
|
||||
}
|
||||
|
||||
for (int j = 0; j < toEntity.getForeignKeys().size(); j++) {
|
||||
var toFk = toEntity.getForeignKeys().get(j);
|
||||
var fromFk = fromEntity.getForeignKeys().stream().filter(e -> e.equals(toFk)).findFirst().orElse(null);
|
||||
if (fromFk != null) {
|
||||
toEntity.dropForeignKey(toFk);
|
||||
fromEntity.dropForeignKey(fromFk);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < toEntity.getUniqueKeys().size(); j++) {
|
||||
var toUnique = toEntity.getUniqueKeys().get(j);
|
||||
var fromUnique = fromEntity.getUniqueKeys().stream().filter(e -> e.equals(toUnique)).findFirst().orElse(null);
|
||||
if (fromUnique != null) {
|
||||
toEntity.dropUniqueKey(toUnique);
|
||||
fromEntity.dropUniqueKey(fromUnique);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < toEntity.getKeys().size(); j++) {
|
||||
var toKey = toEntity.getKeys().get(j);
|
||||
var fromKey = fromEntity.getKeys().stream().filter(e -> e.equals(toKey)).findFirst().orElse(null);
|
||||
if (fromKey != null) {
|
||||
toEntity.dropKey(toKey);
|
||||
fromEntity.dropKey(fromKey);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < toEntity.getIndexes().size(); j++) {
|
||||
var toIx = toEntity.getIndexes().get(j);
|
||||
var fromIx = fromEntity.getIndexes().stream().filter(e -> e.equals(toIx)).findFirst().orElse(null);
|
||||
if (fromIx != null) {
|
||||
toEntity.dropIndex(toIx);
|
||||
fromEntity.dropIndex(fromIx);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AddFieldOperation implements MigrationOperation {
|
||||
protected final String table;
|
||||
protected final String field;
|
||||
protected final String sqlType;
|
||||
protected final boolean notNull;
|
||||
|
||||
public static class Builder implements MigrationOperation.Builder<AddFieldOperation> {
|
||||
protected final String table;
|
||||
protected final String field;
|
||||
protected String sqlType;
|
||||
protected boolean notNull;
|
||||
|
||||
public Builder(String table, String field) {
|
||||
this.table = table;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public Builder sqlType(String sqlType) {
|
||||
this.sqlType = sqlType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder notNull(boolean notNull) {
|
||||
this.notNull = notNull;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AddFieldOperation build() {
|
||||
return new AddFieldOperation(table, field, sqlType, notNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AddForeignKeyOperation implements MigrationOperation {
|
||||
private final String name;
|
||||
private final String table;
|
||||
private final List<String> fields;
|
||||
private final String referencedTable;
|
||||
private final List<String> referencedFields;
|
||||
private final ForeignKeyConstraint.Action onUpdate;
|
||||
private final ForeignKeyConstraint.Action onDelete;
|
||||
|
||||
public static class Builder implements MigrationOperation.Builder<AddForeignKeyOperation> {
|
||||
private final String name;
|
||||
private final String table;
|
||||
private final List<String> fields;
|
||||
private final String referencedTable;
|
||||
private final List<String> referencedFields;
|
||||
private final ForeignKeyConstraint.Action onUpdate;
|
||||
private final ForeignKeyConstraint.Action onDelete;
|
||||
|
||||
public Builder(String name, String table, List<String> fields, String referencedTable, List<String> referencedFields, ForeignKeyConstraint.Action onUpdate, ForeignKeyConstraint.Action onDelete) {
|
||||
this.name = name;
|
||||
this.table = table;
|
||||
this.fields = fields;
|
||||
this.referencedTable = referencedTable;
|
||||
this.referencedFields = referencedFields;
|
||||
this.onUpdate = onUpdate;
|
||||
this.onDelete = onDelete;
|
||||
}
|
||||
|
||||
public AddForeignKeyOperation build() {
|
||||
return new AddForeignKeyOperation(name, table, fields, referencedTable, referencedFields, onUpdate, onDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class AddIndexOperation extends AddKeyOperationBase {
|
||||
public AddIndexOperation(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public static class Builder extends AddKeyOperationBase.Builder<AddIndexOperation> {
|
||||
public Builder(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public AddIndexOperation build() {
|
||||
return new AddIndexOperation(name, table, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class AddKeyOperation extends AddKeyOperationBase {
|
||||
public AddKeyOperation(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public static class Builder extends AddKeyOperationBase.Builder<AddKeyOperation> {
|
||||
public Builder(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public AddKeyOperation build() {
|
||||
return new AddKeyOperation(name, table, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public abstract class AddKeyOperationBase implements MigrationOperation {
|
||||
protected final String name;
|
||||
protected final String table;
|
||||
protected final List<String> fields;
|
||||
|
||||
public abstract static class Builder<T extends AddKeyOperationBase> implements MigrationOperation.Builder<T> {
|
||||
protected final String name;
|
||||
protected final String table;
|
||||
protected final List<String> fields;
|
||||
|
||||
public Builder(String name, String table, List<String> fields) {
|
||||
this.name = name;
|
||||
this.table = table;
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class AddPrimaryKeyOperation extends AddKeyOperationBase {
|
||||
public AddPrimaryKeyOperation(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public static class Builder extends AddKeyOperationBase.Builder<AddPrimaryKeyOperation> {
|
||||
public Builder(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public AddPrimaryKeyOperation build() {
|
||||
return new AddPrimaryKeyOperation(name, table, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AddTableOperation implements MigrationOperation {
|
||||
private final String table;
|
||||
private final List<AddFieldOperation.Builder> fields;
|
||||
|
||||
public static class Builder implements MigrationOperation.Builder<AddTableOperation> {
|
||||
private final String table;
|
||||
private final List<AddFieldOperation.Builder> fields;
|
||||
|
||||
public Builder(String table, List<AddFieldOperation.Builder> fields) {
|
||||
this.table = table;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public AddTableOperation build() {
|
||||
return new AddTableOperation(table, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class AddUniqueKeyOperation extends AddKeyOperationBase {
|
||||
public AddUniqueKeyOperation(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public static class Builder extends AddKeyOperationBase.Builder<AddUniqueKeyOperation> {
|
||||
public Builder(String name, String table, List<String> fields) {
|
||||
super(name, table, fields);
|
||||
}
|
||||
|
||||
public AddUniqueKeyOperation build() {
|
||||
return new AddUniqueKeyOperation(name, table, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class DropConstraintOperation implements MigrationOperation {
|
||||
private final String name;
|
||||
private final String table;
|
||||
|
||||
public static class Builder implements MigrationOperation.Builder<DropConstraintOperation> {
|
||||
private final String name;
|
||||
private final String table;
|
||||
|
||||
public Builder(String name, String table) {
|
||||
this.name = name;
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public DropConstraintOperation build() {
|
||||
return new DropConstraintOperation(name, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class DropFieldOperation implements MigrationOperation {
|
||||
private final String table;
|
||||
private final String field;
|
||||
|
||||
public static class Builder implements MigrationOperation.Builder<DropFieldOperation> {
|
||||
private final String table;
|
||||
private final String field;
|
||||
|
||||
public Builder(String table, String field) {
|
||||
this.table = table;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public DropFieldOperation build() {
|
||||
return new DropFieldOperation(table, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class DropTableOperation implements MigrationOperation {
|
||||
private final String table;
|
||||
|
||||
public static class Builder implements MigrationOperation.Builder<DropTableOperation> {
|
||||
private final String table;
|
||||
|
||||
public Builder(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public DropTableOperation build() {
|
||||
return new DropTableOperation(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package jef.model.migration.operation;
|
||||
|
||||
public interface MigrationOperation {
|
||||
public interface Builder<T extends MigrationOperation> {
|
||||
T build();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user