diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index 5809642233..105259052a 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -1073,6 +1073,14 @@ default Query setUseQueryCache(boolean enabled) { */ ExpressionList like(String propertyName, String value); + /** + * Is LIKE 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 likeIfPresent() rather than having a separate if block. + */ + ExpressionList likeIfPresent(String propertyName, @Nullable String value); + /** * Case insensitive Like - property like value where the value contains the * SQL wild card characters % (percentage) and _ (underscore). Typically uses @@ -1080,17 +1088,41 @@ default Query setUseQueryCache(boolean enabled) { */ ExpressionList ilike(String propertyName, String value); + /** + * Is case insensitive LIKE 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 ilikeIfPresent() rather than having a separate if block. + */ + ExpressionList ilikeIfPresent(String propertyName, @Nullable String value); + /** * Starts With - property like value%. */ ExpressionList startsWith(String propertyName, String value); + /** + * Is STARTS WITH 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 startsWithIfPresent() rather than having a separate if block. + */ + ExpressionList startsWithIfPresent(String propertyName, @Nullable String value); + /** * Case insensitive Starts With - property like value%. Typically uses a * lower() function to make the expression case insensitive. */ ExpressionList istartsWith(String propertyName, String value); + /** + * Is case insensitive STARTS WITH 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 istartsWithIfPresent() rather than having a separate if block. + */ + ExpressionList istartsWithIfPresent(String propertyName, @Nullable String value); + /** * Ends With - property like %value. */ @@ -1107,12 +1139,28 @@ default Query setUseQueryCache(boolean enabled) { */ ExpressionList contains(String propertyName, String value); + /** + * Is CONTAINS 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 containsIfPresent() rather than having a separate if block. + */ + ExpressionList containsIfPresent(String propertyName, @Nullable String value); + /** * Case insensitive Contains - property like %value%. Typically uses a lower() * function to make the expression case insensitive. */ ExpressionList icontains(String propertyName, String value); + /** + * Is case insensitive CONTAINS 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 icontainsIfPresent() rather than having a separate if block. + */ + ExpressionList icontainsIfPresent(String propertyName, @Nullable String value); + /** * In expression using pairs of value objects. */ 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 5d50ca1dd4..6426092ad3 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 @@ -814,6 +814,11 @@ public ExpressionList contains(String propertyName, String value) { return add(expr.contains(propertyName, value)); } + @Override + public ExpressionList containsIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : add(expr.contains(propertyName, value)); + } + @Override public ExpressionList endsWith(String propertyName, String value) { return add(expr.endsWith(propertyName, value)); @@ -864,6 +869,11 @@ public ExpressionList icontains(String propertyName, String value) { return add(expr.icontains(propertyName, value)); } + @Override + public ExpressionList icontainsIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : add(expr.icontains(propertyName, value)); + } + @Override public ExpressionList idIn(Object... idValues) { return add(expr.idIn(idValues)); @@ -894,6 +904,11 @@ public ExpressionList ilike(String propertyName, String value) { return add(expr.ilike(propertyName, value)); } + @Override + public ExpressionList ilikeIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : add(expr.ilike(propertyName, value)); + } + @Override public ExpressionList inPairs(Pairs pairs) { return add(expr.inPairs(pairs)); @@ -1027,6 +1042,11 @@ public ExpressionList istartsWith(String propertyName, String value) { return add(expr.istartsWith(propertyName, value)); } + @Override + public ExpressionList istartsWithIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : add(expr.istartsWith(propertyName, value)); + } + @Override public ExpressionList le(String propertyName, Query subQuery) { return add(expr.le(propertyName, subQuery)); @@ -1052,6 +1072,11 @@ public ExpressionList like(String propertyName, String value) { return add(expr.like(propertyName, value)); } + @Override + public ExpressionList likeIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : add(expr.like(propertyName, value)); + } + @Override public ExpressionList lt(String propertyName, Query subQuery) { return add(expr.lt(propertyName, subQuery)); @@ -1144,6 +1169,11 @@ public ExpressionList startsWith(String propertyName, String value) { return add(expr.startsWith(propertyName, value)); } + @Override + public ExpressionList startsWithIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : add(expr.startsWith(propertyName, value)); + } + @Override public ExpressionList match(String propertyName, String search) { return match(propertyName, search, null); 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 2861237a27..0e893ad064 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 @@ -272,6 +272,11 @@ public ExpressionList contains(String propertyName, String value) { return exprList.contains(propertyName, value); } + @Override + public ExpressionList containsIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : exprList.contains(propertyName, value); + } + @Override public ExpressionList endsWith(String propertyName, String value) { return exprList.endsWith(propertyName, value); @@ -660,6 +665,11 @@ public ExpressionList icontains(String propertyName, String value) { return exprList.icontains(propertyName, value); } + @Override + public ExpressionList icontainsIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : exprList.icontains(propertyName, value); + } + @Override public ExpressionList idEq(Object value) { return exprList.idEq(value); @@ -700,6 +710,11 @@ public ExpressionList ilike(String propertyName, String value) { return exprList.ilike(propertyName, value); } + @Override + public ExpressionList ilikeIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : exprList.ilike(propertyName, value); + } + @Override public ExpressionList inPairs(Pairs pairs) { return exprList.inPairs(pairs); @@ -830,6 +845,11 @@ public ExpressionList istartsWith(String propertyName, String value) { return exprList.istartsWith(propertyName, value); } + @Override + public ExpressionList istartsWithIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : exprList.istartsWith(propertyName, value); + } + @Override public ExpressionList le(String propertyName, Query subQuery) { return exprList.le(propertyName, subQuery); @@ -845,6 +865,11 @@ public ExpressionList like(String propertyName, String value) { return exprList.like(propertyName, value); } + @Override + public ExpressionList likeIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : exprList.like(propertyName, value); + } + @Override public ExpressionList lt(String propertyName, Query subQuery) { return exprList.lt(propertyName, subQuery); @@ -1015,6 +1040,11 @@ public ExpressionList startsWith(String propertyName, String value) { return exprList.startsWith(propertyName, value); } + @Override + public ExpressionList startsWithIfPresent(String propertyName, @Nullable String value) { + return value == null ? this : exprList.startsWith(propertyName, value); + } + @Override public ExpressionList where() { return exprList.where(); diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java index 9a5c21f6c8..5e1151cddc 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseString.java @@ -1,5 +1,7 @@ package io.ebean.typequery; +import org.jspecify.annotations.Nullable; + /** * Base for property types that store as String Varchar types. * @@ -237,6 +239,17 @@ public final R startsWith(String value) { return _root; } + /** + * Is starts with if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R startsWithIfPresent(@Nullable String value) { + expr().startsWithIfPresent(_name, value); + return _root; + } + /** * Ends with - uses a like with '%' wildcard added to the beginning. * @@ -259,6 +272,17 @@ public final R contains(String value) { return _root; } + /** + * Is contains if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R containsIfPresent(@Nullable String value) { + expr().containsIfPresent(_name, value); + return _root; + } + /** * Case-insensitive like. * @@ -270,6 +294,17 @@ public final R ilike(String value) { return _root; } + /** + * Is case-insensitive like if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R ilikeIfPresent(@Nullable String value) { + expr().ilikeIfPresent(_name, value); + return _root; + } + /** * Case-insensitive starts with. * @@ -281,6 +316,17 @@ public final R istartsWith(String value) { return _root; } + /** + * Is case-insensitive starts with if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R istartsWithIfPresent(@Nullable String value) { + expr().istartsWithIfPresent(_name, value); + return _root; + } + /** * Case-insensitive ends with. * @@ -303,6 +349,28 @@ public final R icontains(String value) { return _root; } + /** + * Is case-insensitive contains if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R icontainsIfPresent(@Nullable String value) { + expr().icontainsIfPresent(_name, value); + return _root; + } + + /** + * Is like if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public final R likeIfPresent(@Nullable String value) { + expr().likeIfPresent(_name, value); + return _root; + } + /** * Add a full text "Match" expression. *

diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PString.java b/ebean-querybean/src/main/java/io/ebean/typequery/PString.java index 5903ae827d..204484c415 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PString.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PString.java @@ -1,5 +1,7 @@ package io.ebean.typequery; +import org.jspecify.annotations.Nullable; + /** * String property. * @@ -57,6 +59,17 @@ public R like(String value) { return _root; } + /** + * Is like if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public R likeIfPresent(@Nullable String value) { + expr().likeIfPresent(_name, value); + return _root; + } + /** * Starts with - uses a like with '%' wildcard added to the end. * @@ -68,6 +81,17 @@ public R startsWith(String value) { return _root; } + /** + * Is starts with if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public R startsWithIfPresent(@Nullable String value) { + expr().startsWithIfPresent(_name, value); + return _root; + } + /** * Ends with - uses a like with '%' wildcard added to the beginning. * @@ -90,6 +114,17 @@ public R contains(String value) { return _root; } + /** + * Is contains if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public R containsIfPresent(@Nullable String value) { + expr().containsIfPresent(_name, value); + return _root; + } + /** * Case insensitive like. * @@ -101,6 +136,17 @@ public R ilike(String value) { return _root; } + /** + * Is case-insensitive like if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public R ilikeIfPresent(@Nullable String value) { + expr().ilikeIfPresent(_name, value); + return _root; + } + /** * Case insensitive starts with. * @@ -112,6 +158,17 @@ public R istartsWith(String value) { return _root; } + /** + * Is case-insensitive starts with if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public R istartsWithIfPresent(@Nullable String value) { + expr().istartsWithIfPresent(_name, value); + return _root; + } + /** * Case insensitive ends with. * @@ -134,6 +191,17 @@ public R icontains(String value) { return _root; } + /** + * Is case-insensitive contains if value is non-null and otherwise no expression is added to the query. + * + * @param value the value which can be null + * @return the root query bean instance + */ + public R icontainsIfPresent(@Nullable String value) { + expr().icontainsIfPresent(_name, value); + return _root; + } + /** * Add a full text "Match" expression. *

diff --git a/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java index 89c40927cc..ec7788337c 100644 --- a/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java +++ b/ebean-querybean/src/test/java/org/querytest/MyInnerTest.java @@ -66,11 +66,101 @@ void insert_and_find() { assertThat(found6).isNotNull(); + MyInner found7 = new QMyInner() + .one.icontainsIfPresent("n") + .findOne(); + + assertThat(found7).isNotNull(); + + MyInner found8 = new QMyInner() + .one.icontainsIfPresent(null) + .description.eq("foo") + .findOne(); + + assertThat(found8).isNotNull(); + + MyInner found9 = new QMyInner() + .one.likeIfPresent("on%") + .findOne(); + + assertThat(found9).isNotNull(); + + MyInner found10 = new QMyInner() + .one.likeIfPresent(null) + .description.eq("foo") + .findOne(); + + assertThat(found10).isNotNull(); + + MyInner found11 = new QMyInner() + .one.ilikeIfPresent("ON%") + .findOne(); + + assertThat(found11).isNotNull(); + + MyInner found12 = new QMyInner() + .one.ilikeIfPresent(null) + .description.eq("foo") + .findOne(); + + assertThat(found12).isNotNull(); + + MyInner found13 = new QMyInner() + .one.istartsWithIfPresent("ON") + .findOne(); + + assertThat(found13).isNotNull(); + + MyInner found14 = new QMyInner() + .one.istartsWithIfPresent(null) + .description.eq("foo") + .findOne(); + + assertThat(found14).isNotNull(); + + MyInner found15 = new QMyInner() + .one.startsWithIfPresent("on") + .findOne(); + + assertThat(found15).isNotNull(); + + MyInner found16 = new QMyInner() + .one.startsWithIfPresent(null) + .description.eq("foo") + .findOne(); + + assertThat(found16).isNotNull(); + + MyInner found17 = new QMyInner() + .one.containsIfPresent("ne") + .findOne(); + + assertThat(found17).isNotNull(); + + MyInner found18 = new QMyInner() + .one.containsIfPresent(null) + .description.eq("foo") + .findOne(); + + assertThat(found18).isNotNull(); + List sql = LoggedSql.stop(); - assertThat(sql).hasSize(4); - assertThat(sql.get(0)).contains("select /* MyInnerTest.insert_and_find:36 */ t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.one = ?;"); - assertThat(sql.get(1)).contains("select /* MyInnerTest.insert_and_find:43 */ t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.description = ?;"); - assertThat(sql.get(2)).contains("select /* MyInnerTest.insert_and_find:54 */ 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 /* MyInnerTest.insert_and_find:65 */ 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 > ?;"); + assertThat(sql).hasSize(16); + assertThat(sql.get(0)).contains("where t0.one = ?"); + assertThat(sql.get(1)).contains("where t0.description = ?"); + assertThat(sql.get(2)).contains("where t0.id < ? and t0.description = ?"); + assertThat(sql.get(3)).contains("where t0.id < ? and t0.one > ? and t0.one >= ? and t0.one < ? and t0.one <= ? and t0.id > ?"); + assertThat(sql.get(4)).contains("where lower(t0.one) like ?"); + assertThat(sql.get(5)).contains("where t0.description = ?"); + assertThat(sql.get(6)).contains("where t0.one like ?"); + assertThat(sql.get(7)).contains("where t0.description = ?"); + assertThat(sql.get(8)).contains("where lower(t0.one) like ?"); + assertThat(sql.get(9)).contains("where t0.description = ?"); + assertThat(sql.get(10)).contains("where lower(t0.one) like ?"); + assertThat(sql.get(11)).contains("where t0.description = ?"); + assertThat(sql.get(12)).contains("where t0.one like ?"); + assertThat(sql.get(13)).contains("where t0.description = ?"); + assertThat(sql.get(14)).contains("where t0.one like ?"); + assertThat(sql.get(15)).contains("where t0.description = ?"); } }