diff --git a/jinq-jpa/main/org/jinq/jpa/JPQL.java b/jinq-jpa/main/org/jinq/jpa/JPQL.java index 8fad6109..1c43ef34 100644 --- a/jinq-jpa/main/org/jinq/jpa/JPQL.java +++ b/jinq-jpa/main/org/jinq/jpa/JPQL.java @@ -1,5 +1,8 @@ package org.jinq.jpa; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; import java.util.Collection; import java.util.regex.Pattern; @@ -22,6 +25,18 @@ public static boolean like(String str, String pattern) return like(str, pattern, ""); } + public static Date currentDate() { + return new Date(new java.util.Date().getTime()); + } + + public static Timestamp currentTimestamp() { + return new Timestamp(new java.util.Date().getTime()); + } + + public static Time currentTime() { + return new Time(new java.util.Date().getTime()); + } + /** * In-memory implementation of JPQL like. * @param str string to search inside diff --git a/jinq-jpa/main/org/jinq/jpa/jpqlquery/ColumnExpressions.java b/jinq-jpa/main/org/jinq/jpa/jpqlquery/ColumnExpressions.java index 07e7096f..99a2e7ae 100644 --- a/jinq-jpa/main/org/jinq/jpa/jpqlquery/ColumnExpressions.java +++ b/jinq-jpa/main/org/jinq/jpa/jpqlquery/ColumnExpressions.java @@ -18,6 +18,13 @@ public ColumnExpressions(RowReader reader) this.reader = reader; } + public static ColumnExpressions noColumn(RowReader reader, Expression expr) { + + ColumnExpressions columnExpressions = new ColumnExpressions<>(reader); + columnExpressions.columns.add(expr); + return columnExpressions; + } + public static ColumnExpressions singleColumn(RowReader reader, Expression expr) { ColumnExpressions columnExpressions = new ColumnExpressions<>(reader); diff --git a/jinq-jpa/main/org/jinq/jpa/jpqlquery/FunctionExpression.java b/jinq-jpa/main/org/jinq/jpa/jpqlquery/FunctionExpression.java index f844dd16..7ad217bf 100644 --- a/jinq-jpa/main/org/jinq/jpa/jpqlquery/FunctionExpression.java +++ b/jinq-jpa/main/org/jinq/jpa/jpqlquery/FunctionExpression.java @@ -9,6 +9,12 @@ public class FunctionExpression extends Expression List arguments = new ArrayList<>(); String functionName; + public static FunctionExpression noParam(String name) { + FunctionExpression func = new FunctionExpression(); + func.functionName = name; + return func; + } + public static FunctionExpression singleParam(String name, Expression base) { FunctionExpression func = new FunctionExpression(); @@ -40,6 +46,7 @@ public static FunctionExpression threeParam(String name, Expression param1, Expr public void generateQuery(QueryGenerationState queryState, OperatorPrecedenceLevel operatorPrecedenceScope) { queryState.appendQuery(functionName); + if (arguments.isEmpty()) return; queryState.appendQuery("("); boolean isFirst = true; for (Expression arg: arguments) diff --git a/jinq-jpa/main/org/jinq/jpa/transform/MethodChecker.java b/jinq-jpa/main/org/jinq/jpa/transform/MethodChecker.java index 2b55ef67..d2c685f3 100644 --- a/jinq-jpa/main/org/jinq/jpa/transform/MethodChecker.java +++ b/jinq-jpa/main/org/jinq/jpa/transform/MethodChecker.java @@ -33,6 +33,9 @@ class MethodChecker implements PathAnalysisMethodChecker public final static MethodSignature jpqlIsIn; public final static MethodSignature jpqlIsInList; public final static MethodSignature jpqlListContains; + public final static MethodSignature jpqlCurrentDate; + public final static MethodSignature jpqlCurrentTimestamp; + public final static MethodSignature jpqlCurrentTime; public final static MethodSignature mathSqrt = new MethodSignature("java/lang/Math", "sqrt", "(D)D"); public final static MethodSignature mathAbsDouble = new MethodSignature("java/lang/Math", "abs", "(D)D"); public final static MethodSignature mathAbsInt = new MethodSignature("java/lang/Math", "abs", "(I)I"); @@ -57,6 +60,10 @@ class MethodChecker implements PathAnalysisMethodChecker jpqlIsInList = MethodSignature.fromMethod(JPQL.class.getMethod("isInList", Object.class, Collection.class)); jpqlListContains = MethodSignature.fromMethod(JPQL.class.getMethod("listContains", Collection.class, Object.class)); + jpqlCurrentTimestamp = MethodSignature.fromMethod(JPQL.class.getMethod("currentTimestamp")); + jpqlCurrentDate = MethodSignature.fromMethod(JPQL.class.getMethod("currentDate")); + jpqlCurrentTime = MethodSignature.fromMethod(JPQL.class.getMethod("currentTime")); + streamSelectAll = MethodSignature.fromMethod(JinqStream.class.getMethod("selectAll", JinqStream.Join.class)); streamSelectAllList = MethodSignature.fromMethod(JinqStream.class.getMethod("selectAllList", JinqStream.JoinToIterable.class)); streamJoinList = MethodSignature.fromMethod(JinqStream.class.getMethod("joinList", JinqStream.JoinToIterable.class)); @@ -74,6 +81,9 @@ class MethodChecker implements PathAnalysisMethodChecker jpqlFunctionStaticMethods.add(jpqlIsIn); jpqlFunctionStaticMethods.add(jpqlIsInList); jpqlFunctionStaticMethods.add(jpqlListContains); + jpqlFunctionStaticMethods.add(jpqlCurrentDate); + jpqlFunctionStaticMethods.add(jpqlCurrentTimestamp); + jpqlFunctionStaticMethods.add(jpqlCurrentTime); jpqlFunctionStaticMethods.add(mathSqrt); jpqlFunctionStaticMethods.add(mathAbsDouble); jpqlFunctionStaticMethods.add(mathAbsInt); diff --git a/jinq-jpa/main/org/jinq/jpa/transform/SymbExToColumns.java b/jinq-jpa/main/org/jinq/jpa/transform/SymbExToColumns.java index a7610f9c..db7789fd 100644 --- a/jinq-jpa/main/org/jinq/jpa/transform/SymbExToColumns.java +++ b/jinq-jpa/main/org/jinq/jpa/transform/SymbExToColumns.java @@ -260,9 +260,20 @@ private ColumnExpressions binaryOpWithNumericPromotion(String opString, T // Handle operations with NULL separately if (leftVal instanceof ConstantValue.NullConstant || rightVal instanceof ConstantValue.NullConstant) return binaryOpWithNull(opString, leftVal, rightVal, passdown); + // XXX: here we need to skip this assertion because our tipes are + // not the same (example a Date and a java.sql.Date), but are comparable. + boolean skipAssert = false; + if (leftVal.getType().toString().equals("Ljava/util/Date;")) { + String name = rightVal.getType().toString(); + skipAssert = name.equals("Ljava/sql/Timestamp;") || + name.equals("Ljava/sql/Time;") || + name.equals("Ljava/sql/Date;"); + } + // Check if we have a valid numeric promotion (i.e. one side has a widening cast // to match the type of the other side). - assert(leftVal.getType().equals(rightVal.getType()) + assert(skipAssert + || leftVal.getType().equals(rightVal.getType()) || (leftVal.getType().getInternalName().equals("java/lang/Object") && config.isObjectEqualsSafe)// in Scala, many comparisons are done on Objects || (rightVal.getType().getInternalName().equals("java/lang/Object") && config.isObjectEqualsSafe)); if (isWideningCast(leftVal)) @@ -709,6 +720,21 @@ else if (sig.equals(MethodChecker.mathSqrt)) return ColumnExpressions.singleColumn(new SimpleRowReader<>(), FunctionExpression.singleParam("SQRT", base.getOnlyColumn())); } + else if (sig.equals(MethodChecker.jpqlCurrentDate)) + { + return ColumnExpressions.noColumn(new SimpleRowReader<>(), + FunctionExpression.noParam("CURRENT_DATE")); + } + else if (sig.equals(MethodChecker.jpqlCurrentTimestamp)) + { + return ColumnExpressions.noColumn(new SimpleRowReader<>(), + FunctionExpression.noParam("CURRENT_TIMESTAMP")); + } + else if (sig.equals(MethodChecker.jpqlCurrentTime)) + { + return ColumnExpressions.noColumn(new SimpleRowReader<>(), + FunctionExpression.noParam("CURRENT_TIME")); + } throw new TypedValueVisitorException("Do not know how to translate the method " + sig + " into a JPQL function"); } else diff --git a/jinq-jpa/test/org/jinq/jpa/JPQLCurrentDateTest.java b/jinq-jpa/test/org/jinq/jpa/JPQLCurrentDateTest.java new file mode 100644 index 00000000..36e856fb --- /dev/null +++ b/jinq-jpa/test/org/jinq/jpa/JPQLCurrentDateTest.java @@ -0,0 +1,68 @@ +package org.jinq.jpa; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.jinq.jpa.test.entities.Sale; +import org.junit.Test; + +public class JPQLCurrentDateTest extends JinqJPATestBase +{ + + @Test + public void testCurrentTimeStamp() + { + List list = streams.streamAll(em, Sale.class) + .where(s -> s.getSqlTimestamp().before(JPQL.currentTimestamp())) + .sortedBy(s -> s.getCustomer().getName()) + .toList(); + + + assertEquals("SELECT A FROM Sale A WHERE A.sqlTimestamp < CURRENT_TIMESTAMP ORDER BY A.customer.name ASC", query); + assertEquals(6, list.size()); + assertEquals("Alice", list.get(0).getCustomer().getName()); + } + + @Test + public void testCurrentTimeStampWithUtilDate() + { + List list = streams.streamAll(em, Sale.class) + .where(s -> s.getDate().before(JPQL.currentTimestamp())) + .sortedBy(s -> s.getCustomer().getName()) + .toList(); + + + assertEquals("SELECT A FROM Sale A WHERE A.date < CURRENT_TIMESTAMP ORDER BY A.customer.name ASC", query); + assertEquals(6, list.size()); + assertEquals("Alice", list.get(0).getCustomer().getName()); + } + + @Test + public void testCurrentTime() + { + List list = streams.streamAll(em, Sale.class) + .where(s -> s.getSqlTime().before(JPQL.currentTime())) + .sortedBy(s -> s.getCustomer().getName()) + .toList(); + + + assertEquals("SELECT A FROM Sale A WHERE A.sqlTime < CURRENT_TIME ORDER BY A.customer.name ASC", query); + assertEquals(6, list.size()); + assertEquals("Alice", list.get(0).getCustomer().getName()); + } + + @Test + public void testCurrentDate() + { + List list = streams.streamAll(em, Sale.class) + .where(s -> s.getSqlDate().before(JPQL.currentDate())) + .sortedBy(s -> s.getCustomer().getName()) + .toList(); + + + assertEquals("SELECT A FROM Sale A WHERE A.sqlDate < CURRENT_DATE ORDER BY A.customer.name ASC", query); + assertEquals(6, list.size()); + assertEquals("Alice", list.get(0).getCustomer().getName()); + } +}