diff --git a/src/main/java/jef/Queryable.java b/src/main/java/jef/Queryable.java index 1e3df62..35a3d08 100644 --- a/src/main/java/jef/Queryable.java +++ b/src/main/java/jef/Queryable.java @@ -1,9 +1,11 @@ package jef; import jef.expressions.Expression; -import jef.operations.CountOp; +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; @@ -93,14 +95,19 @@ public interface Queryable { // return null; // } // -// default Queryable sorted() { -// return null; +// default SortOp sorted() {//TODO sort by id +// return new SortOp(this); // } -// -// default Queryable sorted(Comparator comparator) { -// return null; -// } -// + + default SortOp sorted(SerializableFunction fieldSelector) { + return new SortOp(this, fieldSelector, OrderExpression.SortDirection.ASCENDING); + } + + default SortOp sortedDescending(SerializableFunction fieldSelector) { + return new SortOp(this, fieldSelector, OrderExpression.SortDirection.DESCENDING); + } + + // // default Queryable peek(Consumer consumer) { // return null; // } diff --git a/src/main/java/jef/asm/PredicateParser.java b/src/main/java/jef/asm/AsmParser.java similarity index 81% rename from src/main/java/jef/asm/PredicateParser.java rename to src/main/java/jef/asm/AsmParser.java index 23fad31..1e49fa2 100644 --- a/src/main/java/jef/asm/PredicateParser.java +++ b/src/main/java/jef/asm/AsmParser.java @@ -1,21 +1,27 @@ package jef.asm; import jef.expressions.Expression; +import jef.serializable.SerializableFunction; import jef.serializable.SerializablePredicate; import lombok.Getter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import java.io.InputStream; +import java.io.Serializable; import java.lang.invoke.SerializedLambda; import java.util.stream.IntStream; @Getter -public class PredicateParser { - private final SerializablePredicate predicate; +public class AsmParser { + private final Serializable lambda; - public PredicateParser(SerializablePredicate predicate) { - this.predicate = predicate; + public AsmParser(SerializablePredicate predicate) { + this.lambda = predicate; + } + + public AsmParser(SerializableFunction function) { + this.lambda = function; } public Expression parse() throws AsmParseException { @@ -27,7 +33,7 @@ public class PredicateParser { } private Expression parseExpression() throws Exception { - var cls = predicate.getClass(); + var cls = lambda.getClass(); var loader = cls.getClassLoader(); InputStream is; // System.out.println(cls); @@ -42,7 +48,7 @@ public class PredicateParser { var x = cls.getDeclaredMethod("writeReplace"); // System.out.println(x); x.setAccessible(true); - var serlambda = (SerializedLambda) x.invoke(predicate); + var serlambda = (SerializedLambda) x.invoke(lambda); Object[] args = IntStream.range(0, serlambda.getCapturedArgCount()).mapToObj(serlambda::getCapturedArg).toArray(); // System.out.println(serlambda); diff --git a/src/main/java/jef/asm/FilterMethodVisitor.java b/src/main/java/jef/asm/FilterMethodVisitor.java index 4b74c2e..9134717 100644 --- a/src/main/java/jef/asm/FilterMethodVisitor.java +++ b/src/main/java/jef/asm/FilterMethodVisitor.java @@ -38,7 +38,7 @@ class FilterMethodVisitor extends MethodVisitor { private final String[] parameterClasses; private final Object[] args; private final Consumer exprConsumer; - private Expression prediacteExpr; + private Expression lambdaExpr; protected FilterMethodVisitor(int api, String descriptor, Object[] args, Consumer exprConsumer) { super(api); @@ -153,7 +153,7 @@ class FilterMethodVisitor extends MethodVisitor { 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 -> { + 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(); @@ -161,7 +161,7 @@ class FilterMethodVisitor extends MethodVisitor { evalCond(condStack.get(i)); } // condStack.clear(); - prediacteExpr = varStack.pop(); + lambdaExpr = varStack.pop(); } case Opcodes.FCMPL, Opcodes.FCMPG, Opcodes.DCMPL, Opcodes.DCMPG, Opcodes.LCMP -> { var var2 = varStack.pop(); @@ -386,7 +386,7 @@ class FilterMethodVisitor extends MethodVisitor { System.out.println("end"); super.visitEnd(); - exprConsumer.accept(prediacteExpr); + exprConsumer.accept(lambdaExpr); } @Override @@ -445,6 +445,10 @@ class FilterMethodVisitor extends MethodVisitor { 169, "RET", 172, "IRETURN", + 173, "LRETURN", + 174, "FRETURN", + 175, "DRETURN", + 176, "ARETURN", //field 180, "GETFIELD", diff --git a/src/main/java/jef/expressions/Expression.java b/src/main/java/jef/expressions/Expression.java index 2f11dd5..f2d94e3 100644 --- a/src/main/java/jef/expressions/Expression.java +++ b/src/main/java/jef/expressions/Expression.java @@ -17,6 +17,7 @@ public interface Expression { LIMIT, NULL, OR, + ORDER, PARAMETER, SELECT, TABLE, diff --git a/src/main/java/jef/expressions/OrderExpression.java b/src/main/java/jef/expressions/OrderExpression.java new file mode 100644 index 0000000..4c73d44 --- /dev/null +++ b/src/main/java/jef/expressions/OrderExpression.java @@ -0,0 +1,53 @@ +package jef.expressions; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@AllArgsConstructor +public class OrderExpression implements Expression { + private final Expression expr; + private final List 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(sorts.size()); + for (Sort sort : sorts) { + sortStrings.add(sort.getExpr() + " " + sort.getDirection().getS()); + } + ret += " ORDER BY " + String.join(", ", sortStrings); + } + return ret; + } + + @Getter + @AllArgsConstructor + public static class Sort { + private final FieldExpression expr; + private final SortDirection direction; + } + + @Getter + @AllArgsConstructor + public enum SortDirection { + ASCENDING("ASC"), + DESCENDING("DESC"), + ; + private final String s; + } +} diff --git a/src/main/java/jef/expressions/modifier/ExpressionModifier.java b/src/main/java/jef/expressions/modifier/ExpressionModifier.java index 1a38749..87ba917 100644 --- a/src/main/java/jef/expressions/modifier/ExpressionModifier.java +++ b/src/main/java/jef/expressions/modifier/ExpressionModifier.java @@ -9,6 +9,7 @@ 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; @@ -31,6 +32,7 @@ public abstract class ExpressionModifier { 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); @@ -85,6 +87,10 @@ public abstract class ExpressionModifier { return expr; } + public Expression modifyOrder(OrderExpression expr) { + return expr; + } + public Expression modifySelect(SelectExpression expr) { return new SelectExpression(expr.getFields(), modify(expr.getFrom()), expr.getFromAlias()); } diff --git a/src/main/java/jef/expressions/visitors/DebugExpressionVisitor.java b/src/main/java/jef/expressions/visitors/DebugExpressionVisitor.java index 2d340b0..f521fe4 100644 --- a/src/main/java/jef/expressions/visitors/DebugExpressionVisitor.java +++ b/src/main/java/jef/expressions/visitors/DebugExpressionVisitor.java @@ -8,6 +8,7 @@ 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; @@ -16,6 +17,7 @@ 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; @@ -87,6 +89,18 @@ public class DebugExpressionVisitor extends ExpressionVisitor { } } + @Override + public void visitOrder(OrderExpression expr) { + visit(expr.getExpr()); + if (!expr.getSorts().isEmpty()) { + var sortStrings = new ArrayList(expr.getSorts().size()); + for (OrderExpression.Sort sort : expr.getSorts()) { + sortStrings.add(sort.getExpr() + " " + sort.getDirection().getS()); + } + System.out.println(i() + " ORDER BY " + String.join(", ", sortStrings)); + } + } + @Override public void visitParameter(ParameterExpression expr) { if (expr.getValue() instanceof Collection c) { diff --git a/src/main/java/jef/expressions/visitors/ExpressionVisitor.java b/src/main/java/jef/expressions/visitors/ExpressionVisitor.java index db20fdf..0e19e73 100644 --- a/src/main/java/jef/expressions/visitors/ExpressionVisitor.java +++ b/src/main/java/jef/expressions/visitors/ExpressionVisitor.java @@ -9,6 +9,7 @@ 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; @@ -27,6 +28,7 @@ public abstract class ExpressionVisitor { 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); @@ -70,6 +72,9 @@ public abstract class ExpressionVisitor { } } + public void visitOrder(OrderExpression expr) { + } + public void visitParameter(ParameterExpression expr) { } diff --git a/src/main/java/jef/operations/FilterOp.java b/src/main/java/jef/operations/FilterOp.java index ce634cf..c503cea 100644 --- a/src/main/java/jef/operations/FilterOp.java +++ b/src/main/java/jef/operations/FilterOp.java @@ -2,7 +2,7 @@ package jef.operations; import jef.Queryable; import jef.asm.AsmParseException; -import jef.asm.PredicateParser; +import jef.asm.AsmParser; import jef.expressions.Expression; import jef.expressions.SelectExpression; import jef.expressions.WhereExpression; @@ -26,7 +26,7 @@ public class FilterOp implements Queryable, Operation public FilterOp(Queryable queryable, SerializablePredicate predicate) { this.queryable = queryable; this.predicate = predicate; - var parser = new PredicateParser(predicate); + var parser = new AsmParser(predicate); Expression expr; try { expr = parser.parse(); diff --git a/src/main/java/jef/operations/SortOp.java b/src/main/java/jef/operations/SortOp.java new file mode 100644 index 0000000..86d362c --- /dev/null +++ b/src/main/java/jef/operations/SortOp.java @@ -0,0 +1,82 @@ +package jef.operations; + +import jef.Queryable; +import jef.asm.AsmParseException; +import jef.asm.AsmParser; +import jef.expressions.Expression; +import jef.expressions.FieldExpression; +import jef.expressions.OrderExpression; +import jef.expressions.SelectExpression; +import jef.expressions.modifier.ExpressionOptimizerBottomUp; +import jef.expressions.modifier.IConst0Fixer; +import jef.expressions.modifier.TableAliasInjector; +import jef.expressions.modifier.TernaryRewriter; +import jef.expressions.selectable.DatabaseSelectAllExpression; +import jef.serializable.SerializableFunction; +import lombok.AllArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor +public class SortOp implements Queryable { + private final Queryable queryable; + private final List sorts = new ArrayList<>(); + + public SortOp(Queryable queryable, SerializableFunction fieldSelector, OrderExpression.SortDirection direction) { + this.queryable = queryable; + var f = parseFunction(fieldSelector); + this.sorts.add(new OrderExpression.Sort(f, direction)); + } + + @Override + public String getTableAlias() { + return String.valueOf((char) (queryable.getTableAlias().charAt(0) + (char) 1)); + } + + @Override + public Expression getExpression() { + return new OrderExpression(new SelectExpression(List.of(new DatabaseSelectAllExpression()), queryable.getExpression(), getTableAlias()), sorts); + } + + @Override + public String toString() { + return getExpression().toString(); + } + + public SortOp thenSorted(SerializableFunction fieldSelector) { + var f = parseFunction(fieldSelector); + this.sorts.add(new OrderExpression.Sort(f, OrderExpression.SortDirection.ASCENDING)); + return this; + } + + public SortOp thenSortedDescending(SerializableFunction fieldSelector) { + var f = parseFunction(fieldSelector); + this.sorts.add(new OrderExpression.Sort(f, OrderExpression.SortDirection.DESCENDING)); + return this; + } + + private FieldExpression parseFunction(SerializableFunction fieldSelector) { + var parser = new AsmParser(fieldSelector); + Expression expr; + try { + expr = parser.parse(); + } catch (AsmParseException e) { + throw new RuntimeException(e); + } + System.out.println(expr); + expr = new TernaryRewriter().modify(expr); + System.out.println(expr); + expr = new IConst0Fixer().modify(expr); + System.out.println(expr); +// expr = new ExpressionOptimizer().modify(expr); + expr = new ExpressionOptimizerBottomUp().modify(expr); + expr = new TableAliasInjector(getTableAlias()).modify(expr); + if (expr instanceof FieldExpression f) { + return f; + } else { + throw new RuntimeException(expr + " is not a field"); + } + } +} diff --git a/src/test/java/jef/operations/SortOpTest.java b/src/test/java/jef/operations/SortOpTest.java new file mode 100644 index 0000000..65de68e --- /dev/null +++ b/src/test/java/jef/operations/SortOpTest.java @@ -0,0 +1,37 @@ +package jef.operations; + +import jef.DBSet; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class SortOpTest { + @Test + public void test() { + String act; + act = new DBSet("table1") + .sorted(e -> e.getI()).thenSorted(e -> e.getD()).thenSorted(e -> e.getF()).thenSorted(e -> e.getL()).thenSorted(e -> e.getO()) + .toString(); + Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` ASC, `a`.`d` ASC, `a`.`f` ASC, `a`.`l` ASC, `a`.`o` ASC", act); + + + act = new DBSet("table1") + .sortedDescending(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSortedDescending(e -> e.getF()) + /**/.thenSortedDescending(e -> e.getL()).thenSortedDescending(e -> e.getO()) + .toString(); + Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` DESC, `a`.`d` DESC, `a`.`f` DESC, `a`.`l` DESC, `a`.`o` DESC", act); + + //alternating patterns + act = new DBSet("table1") + .sortedDescending(e -> e.getI()).thenSorted(e -> e.getD()).thenSortedDescending(e -> e.getF()).thenSorted(e -> e.getL()) + /**/.thenSortedDescending(e -> e.getO()) + .toString(); + Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` DESC, `a`.`d` ASC, `a`.`f` DESC, `a`.`l` ASC, `a`.`o` DESC", act); + + + act = new DBSet("table1") + .sorted(e -> e.getI()).thenSortedDescending(e -> e.getD()).thenSorted(e -> e.getF()).thenSortedDescending(e -> e.getL()) + /**/.thenSorted(e -> e.getO()) + .toString(); + Assertions.assertEquals("SELECT * FROM (SELECT * FROM `table1`) a ORDER BY `a`.`i` ASC, `a`.`d` DESC, `a`.`f` ASC, `a`.`l` DESC, `a`.`o` ASC", act); + } +} \ No newline at end of file