From 829dfcb013ef1bb767b2ecd4120d339bd10883be Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Sat, 9 Feb 2019 00:30:55 +1300 Subject: [PATCH] #1634 - ENH: Add convenience expressions "greater than or null" gtOrNull(), ltOrNull() and inRangeWith() --- src/main/java/io/ebean/Expr.java | 3 - src/main/java/io/ebean/ExpressionFactory.java | 35 ++++++++++++ src/main/java/io/ebean/ExpressionList.java | 29 ++++++++++ .../expression/DefaultExpressionFactory.java | 21 +++++++ .../expression/DefaultExpressionList.java | 18 ++++++ .../server/expression/JunctionExpression.java | 15 +++++ .../tests/basic/TestQueryWhereInRange.java | 55 ++++++++++++++++++- 7 files changed, 170 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/ebean/Expr.java b/src/main/java/io/ebean/Expr.java index 0bf0ddc4d..3b6ec35b4 100644 --- a/src/main/java/io/ebean/Expr.java +++ b/src/main/java/io/ebean/Expr.java @@ -71,7 +71,6 @@ public class Expr { *

*/ public static Expression inRange(String propertyName, Object value1, Object value2) { - return Ebean.getExpressionFactory().inRange(propertyName, value1, value2); } @@ -79,7 +78,6 @@ public class Expr { * Between - property between the two given values. */ public static Expression between(String propertyName, Object value1, Object value2) { - return Ebean.getExpressionFactory().between(propertyName, value1, value2); } @@ -87,7 +85,6 @@ public class Expr { * Between - value between two given properties. */ public static Expression between(String lowProperty, String highProperty, Object value) { - return Ebean.getExpressionFactory().betweenProperties(lowProperty, highProperty, value); } diff --git a/src/main/java/io/ebean/ExpressionFactory.java b/src/main/java/io/ebean/ExpressionFactory.java index e3455cc3f..873d82dd1 100644 --- a/src/main/java/io/ebean/ExpressionFactory.java +++ b/src/main/java/io/ebean/ExpressionFactory.java @@ -157,6 +157,25 @@ public interface ExpressionFactory { */ Expression inRange(String propertyName, Object value1, Object value2); + /** + * Value in Range between 2 properties. + * + *
{@code
+   *
+   *    .startDate.inRangeWith(endDate, now)
+   *
+   *    // which equates to
+   *    startDate <= now and (endDate > now or endDate is null)
+   *
+   * }
+ * + *

+ * This is a convenience expression combining a number of simple expressions. + * The most common use of this could be called "effective dating" where 2 date or + * timestamp columns represent the date range in which + */ + Expression inRangeWith(String lowProperty, String highProperty, Object value); + /** * Between - property between the two given values. */ @@ -167,6 +186,14 @@ public interface ExpressionFactory { */ Expression betweenProperties(String lowProperty, String highProperty, Object value); + /** + * Greater Than Or Null - property greater than the given value or null. + *

+ * A convenient expression combining GT and Is Null. Most often useful for range + * expressions where the top range value is nullable. + */ + Expression gtOrNull(String propertyName, Object value); + /** * Greater Than - property greater than the given value. */ @@ -178,6 +205,14 @@ public interface ExpressionFactory { */ Expression ge(String propertyName, Object value); + /** + * Less Than or Null - property less than the given value or null. + *

+ * A convenient expression combining LT and Is Null. Most often useful for range + * expressions where the bottom range value is nullable. + */ + Expression ltOrNull(String propertyName, Object value); + /** * Less Than - property less than the given value. */ diff --git a/src/main/java/io/ebean/ExpressionList.java b/src/main/java/io/ebean/ExpressionList.java index 4e8bf5e62..9759a9028 100644 --- a/src/main/java/io/ebean/ExpressionList.java +++ b/src/main/java/io/ebean/ExpressionList.java @@ -802,6 +802,25 @@ public interface ExpressionList { */ ExpressionList ine(String propertyName, String value); + /** + * Value in Range between 2 properties. + * + *

{@code
+   *
+   *    .startDate.inRangeWith(endDate, now)
+   *
+   *    // which equates to
+   *    startDate <= now and (endDate > now or endDate is null)
+   *
+   * }
+ * + *

+ * This is a convenience expression combining a number of simple expressions. + * The most common use of this could be called "effective dating" where 2 date or + * timestamp columns represent the date range in which + */ + ExpressionList inRangeWith(String lowProperty, String highProperty, Object value); + /** * In Range - property >= value1 and property < value2. *

@@ -825,6 +844,11 @@ public interface ExpressionList { */ ExpressionList gt(String propertyName, Object value); + /** + * Greater Than or Null - property greater than the given value or null. + */ + ExpressionList gtOrNull(String propertyName, Object value); + /** * Greater Than or Equal to - property greater than or equal to the given * value. @@ -836,6 +860,11 @@ public interface ExpressionList { */ ExpressionList lt(String propertyName, Object value); + /** + * Less Than or Null - property less than the given value or null. + */ + ExpressionList ltOrNull(String propertyName, Object value); + /** * Less Than or Equal to - property less than or equal to the given value. */ diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java index d7a1d9e36..e952b63d4 100644 --- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -221,6 +221,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return new InRangeExpression(propertyName, value1, value2); } + @Override + public Expression inRangeWith(String lowProperty, String highProperty, Object value) { + return and(le(lowProperty, value), gtOrNull(highProperty, value)); + } + /** * Between - property between the two given values. */ @@ -245,6 +250,14 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return new SimpleExpression(propertyName, Op.GT, value); } + /** + * Greater Than or null - property greater than the given value or null. + */ + @Override + public Expression gtOrNull(String propertyName, Object value) { + return or(gt(propertyName, value), isNull(propertyName)); + } + /** * Greater Than or Equal to - property greater than or equal to the given * value. @@ -254,6 +267,14 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return new SimpleExpression(propertyName, Op.GT_EQ, value); } + /** + * Less Than or null - property less than the given value or null. + */ + @Override + public Expression ltOrNull(String propertyName, Object value) { + return or(lt(propertyName, value), isNull(propertyName)); + } + /** * Less Than - property less than the given value. */ diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index 9f472b255..e0b6215d7 100644 --- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -794,6 +794,12 @@ public class DefaultExpressionList implements SpiExpressionList { return this; } + @Override + public ExpressionList inRangeWith(String lowProperty, String highProperty, Object value) { + add(expr.inRangeWith(lowProperty, highProperty, value)); + return this; + } + @Override public ExpressionList inRange(String propertyName, Object value1, Object value2) { add(expr.inRange(propertyName, value1, value2)); @@ -836,6 +842,12 @@ public class DefaultExpressionList implements SpiExpressionList { return this; } + @Override + public ExpressionList gtOrNull(String propertyName, Object value) { + add(expr.gtOrNull(propertyName, value)); + return this; + } + @Override public ExpressionList icontains(String propertyName, String value) { add(expr.icontains(propertyName, value)); @@ -990,6 +1002,12 @@ public class DefaultExpressionList implements SpiExpressionList { return this; } + @Override + public ExpressionList ltOrNull(String propertyName, Object value) { + add(expr.ltOrNull(propertyName, value)); + return this; + } + @Override public ExpressionList not(Expression exp) { add(expr.not(exp)); diff --git a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index c1ed7b611..dc251f07c 100644 --- a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -280,6 +280,11 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.and(expOne, expTwo); } + @Override + public ExpressionList inRangeWith(String lowProperty, String highProperty, Object value) { + return exprList.inRangeWith(lowProperty, highProperty, value); + } + @Override public ExpressionList inRange(String propertyName, Object value1, Object value2) { return exprList.inRange(propertyName, value1, value2); @@ -597,6 +602,11 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.gt(propertyName, value); } + @Override + public ExpressionList gtOrNull(String propertyName, Object value) { + return exprList.gtOrNull(propertyName, value); + } + @Override public ExpressionList having() { throw new IllegalStateException("having() not allowed on Junction expression list"); @@ -732,6 +742,11 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.lt(propertyName, value); } + @Override + public ExpressionList ltOrNull(String propertyName, Object value) { + return exprList.ltOrNull(propertyName, value); + } + @Override public ExpressionList ne(String propertyName, Object value) { return exprList.ne(propertyName, value); diff --git a/src/test/java/org/tests/basic/TestQueryWhereInRange.java b/src/test/java/org/tests/basic/TestQueryWhereInRange.java index 86de95624..e32831496 100644 --- a/src/test/java/org/tests/basic/TestQueryWhereInRange.java +++ b/src/test/java/org/tests/basic/TestQueryWhereInRange.java @@ -1,7 +1,7 @@ package org.tests.basic; import io.ebean.BaseTestCase; -import io.ebean.Ebean; +import io.ebean.DB; import io.ebean.Query; import org.junit.Test; import org.tests.model.basic.Order; @@ -9,6 +9,8 @@ import org.tests.model.basic.ResetBasicData; import java.time.LocalDate; +import static org.assertj.core.api.Assertions.assertThat; + public class TestQueryWhereInRange extends BaseTestCase { @Test @@ -18,14 +20,61 @@ public class TestQueryWhereInRange extends BaseTestCase { LocalDate today = LocalDate.now(); - Query query= Ebean.find(Order.class) + Query query = DB.find(Order.class) .where().inRange("orderDate", today.minusDays(7), today) .isNotNull("id") .query(); query.findList(); - sqlOf(query).contains("(order_date >= ? and order_date < ?)"); + assertThat(sqlOf(query)).contains("where (t0.order_date >= ? and t0.order_date < ?) and "); + } + @Test + public void gtOrNull() { + + ResetBasicData.reset(); + + LocalDate today = LocalDate.now(); + + Query query = DB.find(Order.class) + .where().gtOrNull("orderDate", today) + .query(); + + query.findList(); + + assertThat(query.getGeneratedSql()).contains("where (t0.order_date > ? or t0.order_date is null)"); + } + + @Test + public void ltOrNull() { + + ResetBasicData.reset(); + + LocalDate today = LocalDate.now(); + + Query query = DB.find(Order.class) + .where().ltOrNull("orderDate", today) + .query(); + + query.findList(); + + assertThat(query.getGeneratedSql()).contains(" where (t0.order_date < ? or t0.order_date is null)"); + } + + @Test + public void inRangeWith() { + + ResetBasicData.reset(); + + LocalDate today = LocalDate.now(); + + Query query = DB.find(Order.class) + .where().inRangeWith("orderDate", "shipDate", today) + .query(); + + query.findList(); + + assertThat(query.getGeneratedSql()).contains("(t0.order_date <= ? and (t0.ship_date > ? or t0.ship_date is null))"); } }