diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index bd3016a60..00f61a798 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -385,7 +385,7 @@ public interface ExpressionList { /** * Executes the query returning a set of values for a single property. - * + *

* This can be used to cache sets. * * @return a HashSet of values for the selegted property @@ -835,6 +835,20 @@ public interface ExpressionList { */ ExpressionList eq(String propertyName, Object value); + /** + * Is EQUAL TO if value is non-null and otherwise no expression is added to the query. + *

+ * This is the EQUAL TO equivalent to {@link #inOrEmpty(String, Collection)} where the expression/predicate + * is only added when the value is non-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 eqIfPresent() rather than having a separate if block. + *

+ * Another option is to instead globally use {@link io.ebean.config.DatabaseConfig#setExpressionEqualsWithNullAsNoop(boolean)} + * but that is not always desirable. + */ + ExpressionList eqIfPresent(String propertyName, @Nullable Object value); + /** * Equal To or Null - property is equal to a given value or 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 c3abfef86..26f225f3a 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 @@ -798,6 +798,11 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.eq(propertyName, value)); } + @Override + public ExpressionList eqIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : add(expr.eq(propertyName, value)); + } + @Override public ExpressionList eqOrNull(String propertyName, Object value) { return add(expr.eqOrNull(propertyName, value)); 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 6984e5c9a..5bc19ca10 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 @@ -309,6 +309,11 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.eq(propertyName, value); } + @Override + public ExpressionList eqIfPresent(String propertyName, @Nullable Object value) { + return value == null ? this : exprList.eq(propertyName, value); + } + @Override public ExpressionList eqOrNull(String propertyName, Object value) { return exprList.eqOrNull(propertyName, value); diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java index e6b2590e8..8fd7cf2d7 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java @@ -1,5 +1,6 @@ package io.ebean.typequery; +import io.avaje.lang.Nullable; import io.ebean.Query; import java.util.Collection; @@ -82,6 +83,25 @@ public abstract class PBaseValueEqual extends TQPropertyBase { return _root; } + /** + * Is equal to if value is non-null and otherwise no expression is added to the query. + *

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

+ * This is the EQUAL TO equivalent to {@link #inOrEmpty(Collection)} where the expression/predicate + * is only added to the query when the value is non-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 eqIfPresent() rather than having a separate if block. + * + * @param value the equal to bind value + * @return the root query bean instance + */ + public final R eqIfPresent(@Nullable T value) { + expr().eqIfPresent(_name, value); + return _root; + } + /** * Is equal to or Null. * @@ -247,7 +267,7 @@ public abstract class PBaseValueEqual extends TQPropertyBase { public final R isIn(Query subQuery) { return in(subQuery); } - + /** * Is NOT in the result of a subquery. * diff --git a/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java index c08d56d94..ff5edbfe7 100644 --- a/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java +++ b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java @@ -1,17 +1,20 @@ package org.querytest; import io.ebean.DB; +import io.ebean.test.LoggedSql; import org.example.domain.MyInner; 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; class MyInnerTest { @Test void insert_and_find() { - MyInner myInner = new MyInner() .id(42).one("one").description("foo"); @@ -26,5 +29,25 @@ class MyInnerTest { .findOne(); assertThat(found2).isNotNull(); + + LoggedSql.start(); + + MyInner found3 = new QMyInner() + .one.eqIfPresent("one") + .findOne(); + + assertThat(found3).isNotNull(); + + MyInner found4 = new QMyInner() + .one.eqIfPresent(null) + .description.eqIfPresent("foo") + .findOne(); + + assertThat(found4).isNotNull(); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + 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 = ?;"); } } 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 faf97ab53..a47084131 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java @@ -23,6 +23,52 @@ public class TestQueryIsNull extends BaseTestCase { assertThat(query.getGeneratedSql()).contains("name is null"); } + @Test + void query_defaultNullToIsNull() { + ResetBasicData.reset(); + + Query query = DB.find(Order.class).select("id, status") + .where().eq("status", null).query(); + query.findList(); + + assertThat(query.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.status is null"); + } + + @Test + void query_eqIfPresent() { + ResetBasicData.reset(); + + Query query = DB.find(Order.class).select("id, status") + .where().eqIfPresent("status", 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).isNull("shipDate").query(); + query1.findList(); + + assertThat(query1.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.ship_date is null"); + + Query query2 = DB.find(Order.class).select("id, status") + .where().isNotNull("orderDate").eqIfPresent("status", null).query(); + query2.findList(); + + assertThat(query2.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.order_date is not null"); + + Query query3 = DB.find(Order.class).select("id, status") + .where().isNotNull("orderDate").eqIfPresent("status", null).isNull("shipDate").query(); + query3.findList(); + + assertThat(query3.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.order_date is not null and t0.ship_date is null"); + + Query query4 = DB.find(Order.class).select("id, status") + .where().isNotNull("orderDate").eqIfPresent("status", Order.Status.NEW).isNull("shipDate").query(); + query4.findList(); + + 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 public void isNotNull_when_OneToMany_expect_existsSubquery() { ResetBasicData.reset();