diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index 8e53e856d..211da6664 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -951,9 +951,12 @@ public interface ExpressionList { ExpressionList gtOrNull(String propertyName, Object value); /** - * Greater Than or Equal to OR Null - ({@code >= or null }). + * Is GREATER THAN if value is non-null and otherwise no expression is added to the query. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use gtIfPresent() rather than having a separate if block. */ - ExpressionList geOrNull(String propertyName, Object value); + ExpressionList gtIfPresent(String propertyName, @Nullable Object value); /** * Greater Than or Equal to the result of a sub-query. @@ -966,6 +969,20 @@ public interface ExpressionList { */ ExpressionList ge(String propertyName, Object value); + /** + * Greater Than or Equal to OR Null - ({@code >= or null }). + */ + ExpressionList geOrNull(String propertyName, Object value); + + + /** + * Is GREATER THAN OR EQUAL TO if value is non-null and otherwise no expression is added to the query. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use geIfPresent() rather than having a separate if block. + */ + ExpressionList geIfPresent(String propertyName, @Nullable Object value); + /** * Less Than the result of a sub-query. */ @@ -982,9 +999,12 @@ public interface ExpressionList { ExpressionList ltOrNull(String propertyName, Object value); /** - * Less Than or Equal to OR Null - ({@code <= or null }). + * Is LESS THAN if value is non-null and otherwise no expression is added to the query. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use ltIfPresent() rather than having a separate if block. */ - ExpressionList leOrNull(String propertyName, Object value); + ExpressionList ltIfPresent(String propertyName, @Nullable Object value); /** * Less Than or Equal to the result of a sub-query. @@ -996,6 +1016,19 @@ public interface ExpressionList { */ ExpressionList le(String propertyName, Object value); + /** + * Less Than or Equal to OR Null - ({@code <= or null }). + */ + ExpressionList leOrNull(String propertyName, Object value); + + /** + * Is LESS THAN OR EQUAL TO if value is non-null and otherwise no expression is added to the query. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use leIfPresent() rather than having a separate if block. + */ + ExpressionList leIfPresent(String propertyName, @Nullable Object value); + /** * Is Null - property is null. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index d47988865..3e25abe1b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -903,6 +903,16 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.geOrNull(propertyName, value)); } + @Override + public ExpressionList gtIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : add(expr.gt(propertyName, value)); + } + + @Override + public ExpressionList geIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : add(expr.ge(propertyName, value)); + } + @Override public ExpressionList icontains(String propertyName, String value) { return add(expr.icontains(propertyName, value)); @@ -1061,6 +1071,16 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.leOrNull(propertyName, value)); } + @Override + public ExpressionList ltIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : add(expr.lt(propertyName, value)); + } + + @Override + public ExpressionList leIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : add(expr.le(propertyName, value)); + } + @Override public ExpressionList not(Expression exp) { return add(expr.not(exp)); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index 2f8dea86f..01ee2147f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -677,6 +677,16 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.geOrNull(propertyName, value); } + @Override + public ExpressionList gtIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : exprList.gt(propertyName, value); + } + + @Override + public ExpressionList geIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : exprList.ge(propertyName, value); + } + @Override public ExpressionList having() { throw new IllegalStateException("having() not allowed on Junction expression list"); @@ -837,6 +847,16 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.leOrNull(propertyName, value); } + @Override + public ExpressionList ltIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : exprList.lt(propertyName, value); + } + + @Override + public ExpressionList leIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : exprList.le(propertyName, value); + } + @Override public ExpressionList ne(String propertyName, Query subQuery) { return exprList.ne(propertyName, subQuery); diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java index 5a31eded7..dfdac7cad 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java @@ -1,6 +1,7 @@ package io.ebean.typequery; +import io.avaje.lang.Nullable; import io.ebean.Query; /** @@ -65,6 +66,23 @@ public abstract class PBaseComparable extends PBaseValueEqual { return _root; } + /** + * Is greater than if value is non-null and otherwise no expression is added to the query. + *

+ * That is, only add the GREATER THAN predicate if the value is not null. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use gtIfPresent() rather than having a separate if block. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R gtIfPresent(@Nullable T value) { + expr().gtIfPresent(_name, value); + return _root; + } + + /** * Greater than or Equal to. * @@ -98,6 +116,22 @@ public abstract class PBaseComparable extends PBaseValueEqual { return _root; } + /** + * Is greater than or equal to if value is non-null and otherwise no expression is added to the query. + *

+ * That is, only add the GREATER THAN OR EQUAL TO predicate if the value is not null. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use geIfPresent() rather than having a separate if block. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R geIfPresent(@Nullable T value) { + expr().geIfPresent(_name, value); + return _root; + } + /** * Less than. * @@ -131,6 +165,22 @@ public abstract class PBaseComparable extends PBaseValueEqual { return _root; } + /** + * Is less than if value is non-null and otherwise no expression is added to the query. + *

+ * That is, only add the LESS THAN predicate if the value is not null. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use ltIfPresent() rather than having a separate if block. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R ltIfPresent(@Nullable T value) { + expr().ltIfPresent(_name, value); + return _root; + } + /** * Less than or Equal to. * @@ -164,6 +214,22 @@ public abstract class PBaseComparable extends PBaseValueEqual { return _root; } + /** + * Is less than or equal to if value is non-null and otherwise no expression is added to the query. + *

+ * That is, only add the LESS THAN OR EQUAL TO predicate if the value is not null. + *

+ * This is effectively a helper method that allows a query to be built in fluid style where some predicates are + * effectively optional. We can use leIfPresent() rather than having a separate if block. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R leIfPresent(@Nullable T value) { + expr().leIfPresent(_name, value); + return _root; + } + /** * Greater or equal to lower value and strictly less than upper value. *

diff --git a/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java index ff5edbfe7..e196cf61b 100644 --- a/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java +++ b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java @@ -7,7 +7,6 @@ import org.example.domain.query.QMyInner; import org.junit.jupiter.api.Test; import java.util.List; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -45,9 +44,33 @@ class MyInnerTest { assertThat(found4).isNotNull(); + MyInner found5 = new QMyInner() + .id.lt(99) + .one.gtIfPresent(null) + .one.geIfPresent(null) + .one.ltIfPresent(null) + .one.leIfPresent(null) + .description.eqIfPresent("foo") + .findOne(); + + assertThat(found5).isNotNull(); + + MyInner found6 = new QMyInner() + .id.lt(99) + .one.gtIfPresent("o") + .one.geIfPresent("o") + .one.ltIfPresent("oz") + .one.leIfPresent("oz") + .id.gt(1) + .findOne(); + + assertThat(found6).isNotNull(); + List sql = LoggedSql.stop(); - assertThat(sql).hasSize(2); + assertThat(sql).hasSize(4); assertThat(sql.get(0)).contains("select t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.one = ?;"); assertThat(sql.get(1)).contains("select t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.description = ?;"); + assertThat(sql.get(2)).contains("select t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.id < ? and t0.description = ?;"); + assertThat(sql.get(3)).contains("select t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.id < ? and t0.one > ? and t0.one >= ? and t0.one < ? and t0.one <= ? and t0.id > ?;"); } } diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java b/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java index 99f417300..cb7a617d4 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java @@ -69,6 +69,46 @@ public class TestQueryIsNull extends BaseTestCase { assertThat(query4.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.order_date is not null and t0.status = ? and t0.ship_date is null"); } + @Test + void ifPresent() { + ResetBasicData.reset(); + + Query query = DB.find(Order.class).select("id, status") + .where().eqIfPresent("status", null) + .gtIfPresent("id", null) + .geIfPresent("id", null) + .ltIfPresent("id", null) + .leIfPresent("id", null) + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0"); + + Query query1 = DB.find(Order.class).select("id, status") + .where().eqIfPresent("status", null) + .gtIfPresent("id", 90) + .geIfPresent("id", 91) + .ltIfPresent("id", 92) + .leIfPresent("id", 93) + .isNull("shipDate").query(); + query1.findList(); + + assertThat(query1.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.id > ? and t0.id >= ? and t0.id < ? and t0.id <= ? and t0.ship_date is null"); + + Query query2 = DB.find(Order.class).select("id, status") + .where() + .leIfPresent("id", 93) + .isNull("orderDate") + .ltIfPresent("id", 92) + .geIfPresent("id", 91) + .isNull("shipDate") + .gtIfPresent("id", 90) + .query(); + query2.findList(); + + assertThat(query2.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.id <= ? and t0.order_date is null and t0.id < ? and t0.id >= ? and t0.ship_date is null and t0.id > ?"); + } + @Test public void isNotNull_when_OneToMany_expect_existsSubquery() { ResetBasicData.reset();