From e2364f02addfe3afd0b5448be7717de51a369dce Mon Sep 17 00:00:00 2001 From: Collin Alpert Date: Tue, 10 Aug 2021 16:38:14 +0200 Subject: [PATCH] Java 17 and more date methods. --- README.MD | 4 +- pom.xml | 12 +- .../collinalpert/lambda2sql/Lambda2Sql.java | 2 +- .../collinalpert/lambda2sql/SqlFunctions.java | 2 + .../collinalpert/lambda2sql/SqlVisitor.java | 110 +++++++++++------- .../lambda2sql/test/Lambda2SqlTest.java | 4 + 6 files changed, 86 insertions(+), 48 deletions(-) diff --git a/README.MD b/README.MD index ff00232..29acc46 100644 --- a/README.MD +++ b/README.MD @@ -1,7 +1,7 @@ Lambda2sql (lambda) -> "sql" ========== -**Please note:** This is a Java 13 library so make sure you have at least Java 13 installed when using it. +**Please note:** This is a Java 11 library so make sure you have at least Java 13 installed when using it. Convert Java 8 lambdas to SQL statements. @@ -77,7 +77,7 @@ You can include the Maven dependency: com.github.collinalpert lambda2sql - 2.4.0 + 2.5.0 ``` diff --git a/pom.xml b/pom.xml index 779e10c..7d04e26 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.collinalpert lambda2sql - 2.4.0 + 2.5.0 jar lambda2sql @@ -34,7 +34,7 @@ scm:git:git://github.com/CollinAlpert/lambda2sql.git scm:git:ssh://github.com:CollinAlpert/lambda2sql.git - http://github.com/CollinAlpert/lambda2sql/tree/master + https://github.com/CollinAlpert/lambda2sql/tree/master @@ -49,7 +49,7 @@ - 13 + 11 ${java-version} ${java-version} ${java-version} @@ -61,13 +61,13 @@ com.github.collinalpert expressions - 2.6.1 + 2.7.0 org.junit.jupiter junit-jupiter-api - 5.7.0 + 5.8.0-M1 test @@ -113,7 +113,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.3.0 -html5 diff --git a/src/main/java/com/github/collinalpert/lambda2sql/Lambda2Sql.java b/src/main/java/com/github/collinalpert/lambda2sql/Lambda2Sql.java index 2db7950..e0cbb31 100644 --- a/src/main/java/com/github/collinalpert/lambda2sql/Lambda2Sql.java +++ b/src/main/java/com/github/collinalpert/lambda2sql/Lambda2Sql.java @@ -22,7 +22,7 @@ public class Lambda2Sql { * @return A {@link String} describing the SQL where condition. */ public static String toSql(SerializedFunctionalInterface functionalInterface, String tableName, boolean withBackticks) { - var lambdaExpression = LambdaExpression.parse(functionalInterface); + LambdaExpression lambdaExpression = LambdaExpression.parse(functionalInterface); return lambdaExpression.accept(new SqlVisitor(tableName, withBackticks)).toString(); } diff --git a/src/main/java/com/github/collinalpert/lambda2sql/SqlFunctions.java b/src/main/java/com/github/collinalpert/lambda2sql/SqlFunctions.java index 0b896bf..4d5a71b 100644 --- a/src/main/java/com/github/collinalpert/lambda2sql/SqlFunctions.java +++ b/src/main/java/com/github/collinalpert/lambda2sql/SqlFunctions.java @@ -1,6 +1,8 @@ package com.github.collinalpert.lambda2sql; /** + * This class contains placeholder functions which can be used within an expression to make use of the respective SQL function. + * * @author Collin Alpert */ public class SqlFunctions { diff --git a/src/main/java/com/github/collinalpert/lambda2sql/SqlVisitor.java b/src/main/java/com/github/collinalpert/lambda2sql/SqlVisitor.java index 5b5fea0..b561f55 100755 --- a/src/main/java/com/github/collinalpert/lambda2sql/SqlVisitor.java +++ b/src/main/java/com/github/collinalpert/lambda2sql/SqlVisitor.java @@ -29,9 +29,11 @@ public class SqlVisitor implements ExpressionVisitor { try { operatorMethods.put(String.class.getDeclaredMethod("equals", Object.class), ExpressionType.Equal); operatorMethods.put(Object.class.getDeclaredMethod("equals", Object.class), ExpressionType.Equal); - operatorMethods.put(LocalDate.class.getDeclaredMethod("isAfter", ChronoLocalDate.class), ExpressionType.GreaterThan); + operatorMethods.put(LocalTime.class.getDeclaredMethod("isAfter", LocalTime.class), ExpressionType.GreaterThan); + operatorMethods.put(LocalDate.class.getDeclaredMethod("isAfter", ChronoLocalDate.class), ExpressionType.GreaterThan); operatorMethods.put(LocalDateTime.class.getDeclaredMethod("isAfter", ChronoLocalDateTime.class), ExpressionType.GreaterThan); + operatorMethods.put(LocalDate.class.getDeclaredMethod("isBefore", ChronoLocalDate.class), ExpressionType.LessThan); operatorMethods.put(LocalTime.class.getDeclaredMethod("isBefore", LocalTime.class), ExpressionType.LessThan); operatorMethods.put(LocalDateTime.class.getDeclaredMethod("isBefore", ChronoLocalDateTime.class), ExpressionType.LessThan); @@ -52,7 +54,7 @@ public class SqlVisitor implements ExpressionVisitor { /** * More complex methods that can be used on Java objects inside the lambda expressions. */ - private final Map> complexMethods; + private final Map> javaMethods; private final StringBuilder sb; private Expression body; @@ -70,33 +72,61 @@ private SqlVisitor(String tableName, boolean withBackticks, Expression body, Lin this.sb = new StringBuilder(); this.parameterConsumptionCount = new HashMap<>(); - this.complexMethods = new HashMap<>(32, 1); + this.javaMethods = new HashMap<>(1 << 6, 1); try { - this.complexMethods.put(String.class.getDeclaredMethod("startsWith", String.class), this::stringStartsWith); - this.complexMethods.put(String.class.getDeclaredMethod("endsWith", String.class), this::stringEndsWith); - this.complexMethods.put(String.class.getDeclaredMethod("contains", CharSequence.class), this::stringContains); - this.complexMethods.put(String.class.getDeclaredMethod("length"), (string, argument, isNegated) -> applySqlFunction(string, "LENGTH")); - - this.complexMethods.put(List.class.getDeclaredMethod("contains", Object.class), this::listContains); - this.complexMethods.put(ArrayList.class.getDeclaredMethod("contains", Object.class), this::listContains); - this.complexMethods.put(LinkedList.class.getDeclaredMethod("contains", Object.class), this::listContains); - - this.complexMethods.put(LocalTime.class.getDeclaredMethod("getSecond"), (date, argument, isNegated) -> applySqlFunction(date, "SECOND")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getSecond"), (date, argument, isNegated) -> applySqlFunction(date, "SECOND")); - this.complexMethods.put(LocalTime.class.getDeclaredMethod("getMinute"), (date, argument, isNegated) -> applySqlFunction(date, "MINUTE")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getMinute"), (date, argument, isNegated) -> applySqlFunction(date, "MINUTE")); - this.complexMethods.put(LocalTime.class.getDeclaredMethod("getHour"), (date, argument, isNegated) -> applySqlFunction(date, "HOUR")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getHour"), (date, argument, isNegated) -> applySqlFunction(date, "HOUR")); - this.complexMethods.put(LocalDate.class.getDeclaredMethod("getDayOfWeek"), (date, argument, isNegated) -> applySqlFunction(date, "DAYOFWEEK")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getDayOfWeek"), (date, argument, isNegated) -> applySqlFunction(date, "DAYOFWEEK")); - this.complexMethods.put(LocalDate.class.getDeclaredMethod("getDayOfMonth"), (date, argument, isNegated) -> applySqlFunction(date, "DAY")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getDayOfWeek"), (date, argument, isNegated) -> applySqlFunction(date, "DAY")); - this.complexMethods.put(LocalDate.class.getDeclaredMethod("getDayOfYear"), (date, argument, isNegated) -> applySqlFunction(date, "DAYOFYEAR")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getDayOfYear"), (date, argument, isNegated) -> applySqlFunction(date, "DAYOFYEAR")); - this.complexMethods.put(LocalDate.class.getDeclaredMethod("getMonthValue"), (date, argument, isNegated) -> applySqlFunction(date, "MONTH")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getMonthValue"), (date, argument, isNegated) -> applySqlFunction(date, "MONTH")); - this.complexMethods.put(LocalDate.class.getDeclaredMethod("getYear"), (date, argument, isNegated) -> applySqlFunction(date, "YEAR")); - this.complexMethods.put(LocalDateTime.class.getDeclaredMethod("getYear"), (date, argument, isNegated) -> applySqlFunction(date, "YEAR")); + this.javaMethods.put(String.class.getDeclaredMethod("startsWith", String.class), this::stringStartsWith); + this.javaMethods.put(String.class.getDeclaredMethod("endsWith", String.class), this::stringEndsWith); + this.javaMethods.put(String.class.getDeclaredMethod("contains", CharSequence.class), this::stringContains); + this.javaMethods.put(String.class.getDeclaredMethod("length"), applySqlFunction("LENGTH")); + this.javaMethods.put(String.class.getDeclaredMethod("toLowerCase"), applySqlFunction("UPPER")); + this.javaMethods.put(String.class.getDeclaredMethod("toUpperCase"), applySqlFunction("LOWER")); + + this.javaMethods.put(List.class.getDeclaredMethod("contains", Object.class), this::listContains); + this.javaMethods.put(ArrayList.class.getDeclaredMethod("contains", Object.class), this::listContains); + this.javaMethods.put(LinkedList.class.getDeclaredMethod("contains", Object.class), this::listContains); + + this.javaMethods.put(LocalTime.class.getDeclaredMethod("getSecond"), applySqlFunction("SECOND")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getSecond"), applySqlFunction("SECOND")); + this.javaMethods.put(OffsetTime.class.getDeclaredMethod("getSecond"), applySqlFunction("SECOND")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getSecond"), applySqlFunction("SECOND")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getSecond"), applySqlFunction("SECOND")); + + this.javaMethods.put(LocalTime.class.getDeclaredMethod("getMinute"), applySqlFunction("MINUTE")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getMinute"), applySqlFunction("MINUTE")); + this.javaMethods.put(OffsetTime.class.getDeclaredMethod("getMinute"), applySqlFunction("MINUTE")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getMinute"), applySqlFunction("MINUTE")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getMinute"), applySqlFunction("MINUTE")); + + this.javaMethods.put(LocalTime.class.getDeclaredMethod("getHour"), applySqlFunction("HOUR")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getHour"), applySqlFunction("HOUR")); + this.javaMethods.put(OffsetTime.class.getDeclaredMethod("getHour"), applySqlFunction("HOUR")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getHour"), applySqlFunction("HOUR")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getHour"), applySqlFunction("HOUR")); + + this.javaMethods.put(LocalDate.class.getDeclaredMethod("getDayOfWeek"), applySqlFunction("DAYOFWEEK")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getDayOfWeek"), applySqlFunction("DAYOFWEEK")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getDayOfWeek"), applySqlFunction("DAYOFWEEK")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getDayOfWeek"), applySqlFunction("DAYOFWEEK")); + + this.javaMethods.put(LocalDate.class.getDeclaredMethod("getDayOfMonth"), applySqlFunction("DAY")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getDayOfWeek"), applySqlFunction("DAY")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getDayOfWeek"), applySqlFunction("DAY")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getDayOfWeek"), applySqlFunction("DAY")); + + this.javaMethods.put(LocalDate.class.getDeclaredMethod("getDayOfYear"), applySqlFunction("DAYOFYEAR")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getDayOfYear"), applySqlFunction("DAYOFYEAR")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getDayOfYear"), applySqlFunction("DAYOFYEAR")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getDayOfYear"), applySqlFunction("DAYOFYEAR")); + + this.javaMethods.put(LocalDate.class.getDeclaredMethod("getMonthValue"), applySqlFunction("MONTH")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getMonthValue"), applySqlFunction("MONTH")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getMonthValue"), applySqlFunction("MONTH")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getMonthValue"), applySqlFunction("MONTH")); + + this.javaMethods.put(LocalDate.class.getDeclaredMethod("getYear"), applySqlFunction("YEAR")); + this.javaMethods.put(LocalDateTime.class.getDeclaredMethod("getYear"), applySqlFunction("YEAR")); + this.javaMethods.put(OffsetDateTime.class.getDeclaredMethod("getYear"), applySqlFunction("YEAR")); + this.javaMethods.put(ZonedDateTime.class.getDeclaredMethod("getYear"), applySqlFunction("YEAR")); } catch (NoSuchMethodException e) { e.printStackTrace(); } @@ -184,7 +214,7 @@ public StringBuilder visit(ConstantExpression e) { return sb.append("'").append(escapeString(e.getValue().toString())).append("'"); } - return sb.append(e.getValue().toString()); + return sb.append(e.getValue()); } /** @@ -205,7 +235,7 @@ public StringBuilder visit(InvocationExpression e) { .map(ConstantExpression.class::cast) .collect(Collectors.toList()); if (!list.isEmpty()) { - arguments.push(list); + this.arguments.push(list); } } @@ -219,7 +249,7 @@ public StringBuilder visit(InvocationExpression e) { } if (e.getTarget().getExpressionType() == ExpressionType.MethodAccess && !e.getArguments().isEmpty()) { - javaMethodParameter = e.getArguments().get(0); + this.javaMethodParameter = e.getArguments().get(0); } return e.getTarget().accept(this); @@ -261,8 +291,9 @@ public StringBuilder visit(MemberExpression e) { return Expression.binary(operatorMethods.get(e.getMember()), e.getInstance(), this.javaMethodParameter).accept(this); } - if (this.complexMethods.containsKey(e.getMember())) { - return sb.append(this.complexMethods.get(e.getMember()).apply(e.getInstance(), this.javaMethodParameter, false)); + var javaMethodReplacer = this.javaMethods.get(e.getMember()); + if (javaMethodReplacer != null) { + return sb.append(javaMethodReplacer.apply(e.getInstance(), this.javaMethodParameter, false)); } var nameArray = e.getMember().getName().replaceAll("^(get)", "").toCharArray(); @@ -320,8 +351,8 @@ public StringBuilder visit(UnaryExpression e) { var memberExpression = (MemberExpression) invocationExpression.getTarget(); if (operatorMethods.containsKey(memberExpression.getMember())) { return Expression.logicalNot(Expression.binary(operatorMethods.get(memberExpression.getMember()), memberExpression.getInstance(), invocationExpression.getArguments().get(0))).accept(this); - } else if (complexMethods.containsKey(memberExpression.getMember())) { - return sb.append(complexMethods.get(memberExpression.getMember()).apply(memberExpression.getInstance(), invocationExpression.getArguments().get(0), true)); + } else if (this.javaMethods.containsKey(memberExpression.getMember())) { + return sb.append(this.javaMethods.get(memberExpression.getMember()).apply(memberExpression.getInstance(), invocationExpression.getArguments().get(0), true)); } else { sb.append("!"); } @@ -348,15 +379,16 @@ private StringBuilder stringContains(Expression string, Expression argument, boo } private StringBuilder listContains(Expression list, Expression argument, boolean isNegated) { - List l = (List) arguments.pop().get(((ParameterExpression) list).getIndex()).getValue(); + @SuppressWarnings("unchecked") + List l = (List) this.arguments.pop().get(((ParameterExpression) list).getIndex()).getValue(); var joiner = new StringJoiner(", ", "(", ")"); l.forEach(x -> joiner.add(x.toString())); - return argument.accept(new SqlVisitor(this.tableName, this.withBackticks, this.body, this.arguments)).append(isNegated ? " NOT" : "").append(" IN ").append(joiner.toString()); + return argument.accept(new SqlVisitor(this.tableName, this.withBackticks, this.body, this.arguments)).append(isNegated ? " NOT" : "").append(" IN ").append(joiner); } - private StringBuilder applySqlFunction(Expression date, String field) { - return new StringBuilder().append(field).append("(").append(date.accept(new SqlVisitor(this.tableName, this.withBackticks, this.body, this.arguments))).append(')'); + private TriFunction applySqlFunction(String field) { + return (value, argument, isNegated) -> new StringBuilder().append(field).append("(").append(value.accept(new SqlVisitor(this.tableName, this.withBackticks, this.body, this.arguments))).append(')'); } //endregion diff --git a/src/test/java/com/github/collinalpert/lambda2sql/test/Lambda2SqlTest.java b/src/test/java/com/github/collinalpert/lambda2sql/test/Lambda2SqlTest.java index 80dfca2..f0f1671 100644 --- a/src/test/java/com/github/collinalpert/lambda2sql/test/Lambda2SqlTest.java +++ b/src/test/java/com/github/collinalpert/lambda2sql/test/Lambda2SqlTest.java @@ -224,7 +224,11 @@ void testLastParameterNull() { void testMultipleSameParameter() { String s = "Steve"; SqlPredicate p = x -> x.getName() == s || x.getLastName() == s; + SqlPredicate p2 = x -> x.getName() == s || x.getName() == s && x.isActive(); + SqlPredicate p3 = x -> x.getName() == s || x.getLastName() == s && x.isActive(); assertPredicateEqual("`person`.`name` = 'Steve' OR `person`.`lastName` = 'Steve'", p); + assertPredicateEqual("`person`.`name` = 'Steve' OR `person`.name` = 'Steve' AND `person`.`isActive`", p2); + assertPredicateEqual("`person`.`name` = 'Steve' OR `person`.`lastName` = 'Steve' AND `person`.`isActive`", p3); } @Test