diff --git a/src/main/java/io/ebean/Expr.java b/src/main/java/io/ebean/Expr.java index 08c828fc8..0bf0ddc4d 100644 --- a/src/main/java/io/ebean/Expr.java +++ b/src/main/java/io/ebean/Expr.java @@ -64,6 +64,17 @@ public class Expr { return Ebean.getExpressionFactory().ieq(propertyName, value); } + /** + * In Range - property >= value1 and property < value2. + *

+ * Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps. + *

+ */ + public static Expression inRange(String propertyName, Object value1, Object value2) { + + return Ebean.getExpressionFactory().inRange(propertyName, value1, value2); + } + /** * Between - property between the two given values. */ diff --git a/src/main/java/io/ebean/ExpressionFactory.java b/src/main/java/io/ebean/ExpressionFactory.java index bd5f0a590..e3455cc3f 100644 --- a/src/main/java/io/ebean/ExpressionFactory.java +++ b/src/main/java/io/ebean/ExpressionFactory.java @@ -149,6 +149,14 @@ public interface ExpressionFactory { */ Expression ineObject(String propertyName, Object value); + /** + * In Range - property >= value1 and property < value2. + *

+ * Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps. + *

+ */ + Expression inRange(String propertyName, Object value1, Object value2); + /** * Between - property between the two given values. */ diff --git a/src/main/java/io/ebean/ExpressionList.java b/src/main/java/io/ebean/ExpressionList.java index 86dfdfe49..4e8bf5e62 100644 --- a/src/main/java/io/ebean/ExpressionList.java +++ b/src/main/java/io/ebean/ExpressionList.java @@ -803,8 +803,16 @@ public interface ExpressionList { ExpressionList ine(String propertyName, String value); /** - * Between - property between the two given values. + * In Range - property >= value1 and property < value2. + *

+ * Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps. + *

*/ + ExpressionList inRange(String propertyName, Object value1, Object value2); + + /** + * Between - property between the two given values. + */ ExpressionList between(String propertyName, Object value1, Object value2); /** diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java index 43d0fae18..ba845529d 100644 --- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -217,8 +217,15 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { * Between - property between the two given values. */ @Override - public Expression between(String propertyName, Object value1, Object value2) { + public Expression inRange(String propertyName, Object value1, Object value2) { + return new InRangeExpression(propertyName, value1, value2); + } + /** + * Between - property between the two given values. + */ + @Override + public Expression between(String propertyName, Object value1, Object value2) { return new BetweenExpression(propertyName, value1, value2); } @@ -227,7 +234,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression betweenProperties(String lowProperty, String highProperty, Object value) { - return new BetweenPropertyExpression(lowProperty, highProperty, value); } @@ -236,7 +242,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression gt(String propertyName, Object value) { - return new SimpleExpression(propertyName, Op.GT, value); } @@ -246,7 +251,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression ge(String propertyName, Object value) { - return new SimpleExpression(propertyName, Op.GT_EQ, value); } @@ -255,7 +259,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression lt(String propertyName, Object value) { - return new SimpleExpression(propertyName, Op.LT, value); } @@ -264,7 +267,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression le(String propertyName, Object value) { - return new SimpleExpression(propertyName, Op.LT_EQ, value); } @@ -273,7 +275,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression isNull(String propertyName) { - return new NullExpression(propertyName, false); } @@ -282,7 +283,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression isNotNull(String propertyName) { - return new NullExpression(propertyName, true); } diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index 4d4d6c432..9f472b255 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 inRange(String propertyName, Object value1, Object value2) { + add(expr.inRange(propertyName, value1, value2)); + return this; + } + @Override public ExpressionList between(String propertyName, Object value1, Object value2) { add(expr.between(propertyName, value1, value2)); diff --git a/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java b/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java new file mode 100644 index 000000000..837186871 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/expression/InRangeExpression.java @@ -0,0 +1,61 @@ +package io.ebeaninternal.server.expression; + +import io.ebeaninternal.api.SpiExpression; +import io.ebeaninternal.api.SpiExpressionRequest; + +import java.io.IOException; + +class InRangeExpression extends AbstractExpression { + + private final Object valueHigh; + + private final Object valueLow; + + InRangeExpression(String propertyName, Object valueLow, Object valueHigh) { + super(propertyName); + this.valueLow = valueLow; + this.valueHigh = valueHigh; + } + + private Object low() { + return NamedParamHelp.value(valueLow); + } + + private Object high() { + return NamedParamHelp.value(valueHigh); + } + + @Override + public void writeDocQuery(DocQueryContext context) throws IOException { + context.writeRange(propName, Op.GT_EQ, low(), Op.LT, high()); + } + + @Override + public void addBindValues(SpiExpressionRequest request) { + request.addBindValue(low()); + request.addBindValue(high()); + } + + @Override + public void addSql(SpiExpressionRequest request) { + request.append("(").append(propName).append(" >= ? and ").append(propName).append(" < ?) "); + } + + @Override + public void queryPlanHash(StringBuilder builder) { + builder.append("InRange[").append(propName).append("]"); + } + + @Override + public int queryBindHash() { + int hc = low().hashCode(); + hc = hc * 92821 + high().hashCode(); + return hc; + } + + @Override + public boolean isSameByBind(SpiExpression other) { + InRangeExpression that = (InRangeExpression) other; + return low().equals(that.low()) && high().equals(that.high()); + } +} diff --git a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index cd74c635d..8d0f8f924 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 inRange(String propertyName, Object value1, Object value2) { + return exprList.inRange(propertyName, value1, value2); + } + @Override public ExpressionList between(String propertyName, Object value1, Object value2) { return exprList.between(propertyName, value1, value2); diff --git a/src/main/java/io/ebeaninternal/server/grammer/EqlAdapterHelper.java b/src/main/java/io/ebeaninternal/server/grammer/EqlAdapterHelper.java index fabbcfe8e..70b9355ab 100644 --- a/src/main/java/io/ebeaninternal/server/grammer/EqlAdapterHelper.java +++ b/src/main/java/io/ebeaninternal/server/grammer/EqlAdapterHelper.java @@ -49,6 +49,10 @@ class EqlAdapterHelper { peekExprList().between(path, bind(value1), bind(value2)); } + protected void addInRange(String path, String value1, String value2) { + peekExprList().inRange(path, bind(value1), bind(value2)); + } + protected void addIn(String path, List inValues) { peekExprList().in(path, inValues); } diff --git a/src/test/java/io/ebeaninternal/server/expression/InRangeExpressionTest.java b/src/test/java/io/ebeaninternal/server/expression/InRangeExpressionTest.java new file mode 100644 index 000000000..44ee53eb4 --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/expression/InRangeExpressionTest.java @@ -0,0 +1,69 @@ +package io.ebeaninternal.server.expression; + +import io.ebeaninternal.api.SpiExpression; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class InRangeExpressionTest extends BaseExpressionTest { + + @Test + public void addSql() { + + DefaultExpressionRequest expReq = newExpressionRequest(); + + InRangeExpression exp = new InRangeExpression("startDate", 1, 2); + exp.addSql(expReq); + + assertThat(expReq.getSql()).isEqualTo("(startDate >= ? and startDate < ?) "); + } + + @Test + public void copyForPlanKey_isSameInstance() { + + InRangeExpression exp = new InRangeExpression("startDate", 1, 2); + SpiExpression other = exp.copyForPlanKey(); + + assertThat(exp).isSameAs(other); + } + + @Test + public void isSameByPlan_when_properties_match() { + + InRangeExpression exp0 = new InRangeExpression("startDate", 1, 2); + InRangeExpression exp1 = new InRangeExpression("startDate", 3, 4); + + same(exp0, exp1); + same(exp1, exp0); + } + + @Test + public void isSameByPlan_when_properties_do_not_match() { + + InRangeExpression exp0 = new InRangeExpression("startDate", 1, 2); + InRangeExpression exp1 = new InRangeExpression("endDate", 1, 2); + + different(exp0, exp1); + different(exp1, exp0); + } + + @Test + public void isSameByBind_when_values_do_not_match() { + + InRangeExpression exp0 = new InRangeExpression("startDate", 1, 2); + InRangeExpression exp1 = new InRangeExpression("startDate", 1, 3); + + assertThat(exp0.isSameByBind(exp1)).isFalse(); + assertThat(exp1.isSameByBind(exp0)).isFalse(); + } + + @Test + public void isSameByBind_when_values_match() { + + InRangeExpression exp0 = new InRangeExpression("startDate", 1, 2); + InRangeExpression exp1 = new InRangeExpression("startDate", 1, 2); + + assertThat(exp0.isSameByBind(exp1)).isTrue(); + assertThat(exp1.isSameByBind(exp0)).isTrue(); + } +} diff --git a/src/test/java/org/tests/basic/TestQueryWhereInRange.java b/src/test/java/org/tests/basic/TestQueryWhereInRange.java new file mode 100644 index 000000000..86de95624 --- /dev/null +++ b/src/test/java/org/tests/basic/TestQueryWhereInRange.java @@ -0,0 +1,31 @@ +package org.tests.basic; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.Query; +import org.junit.Test; +import org.tests.model.basic.Order; +import org.tests.model.basic.ResetBasicData; + +import java.time.LocalDate; + +public class TestQueryWhereInRange extends BaseTestCase { + + @Test + public void inRange() { + + ResetBasicData.reset(); + + LocalDate today = LocalDate.now(); + + Query query= Ebean.find(Order.class) + .where().inRange("orderDate", today.minusDays(7), today) + .isNotNull("id") + .query(); + + query.findList(); + + sqlOf(query).contains("(order_date >= ? and order_date < ?)"); + + } +}