From d3fd03ce5b2dd18d3a25d20949f0f72645b7d17a Mon Sep 17 00:00:00 2001 From: "robin.bygrave" Date: Sat, 6 Jun 2026 17:45:45 +1200 Subject: [PATCH 1/3] Add PString eqIfNotBlank() helper query expression Just to make this relatively common case pretty nice and clean --- .../main/java/io/ebean/typequery/PString.java | 29 +++++++ .../querytest/QStringEqIfNotBlankTest.java | 79 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 ebean-querybean/src/test/java/org/querytest/QStringEqIfNotBlankTest.java 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 204484c41..3eebe6bc1 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PString.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PString.java @@ -26,6 +26,35 @@ public final class PString extends PBaseComparable { super(name, root, prefix); } + /** + * Is equal to if the value is non-null and not blank, otherwise no expression is added to the query. + *

+ * This is like {@code eqIfPresent()} but additionally treats an empty or whitespace-only value as + * "not present". When present, the value is trimmed before being used in the equal to expression. + *

+ * This is a helper for building queries in fluid style where a String predicate is effectively + * optional, avoiding a separate {@code if} block and {@code trimToNull()} style helper. + * + *

{@code
+   *   List customers = new QCustomer()
+   *     .name.eqIfNotBlank(nameFilter)
+   *     .status.eqIfPresent(statusFilter)
+   *     .findList();
+   * }
+ * + * @param value the value which can be null or blank + * @return the root query bean instance + */ + public R eqIfNotBlank(@Nullable String value) { + if (value != null) { + String trimmed = value.trim(); + if (!trimmed.isEmpty()) { + expr().eq(_name, trimmed); + } + } + return _root; + } + /** * Case insensitive is equal to. * diff --git a/ebean-querybean/src/test/java/org/querytest/QStringEqIfNotBlankTest.java b/ebean-querybean/src/test/java/org/querytest/QStringEqIfNotBlankTest.java new file mode 100644 index 000000000..b26cc77e8 --- /dev/null +++ b/ebean-querybean/src/test/java/org/querytest/QStringEqIfNotBlankTest.java @@ -0,0 +1,79 @@ +package org.querytest; + +import io.ebean.DB; +import io.ebean.Database; +import org.example.domain.Customer; +import org.example.domain.query.QCustomer; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.example.domain.query.QCustomer.Alias.name; + +class QStringEqIfNotBlankTest { + + @Test + void present_addsEqPredicate() { + var q = new QCustomer() + .select(name) + .name.eqIfNotBlank("rob") + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).contains("where t0.name = ?"); + } + + @Test + void null_skipsPredicate() { + var q = new QCustomer() + .select(name) + .name.isNotNull() + .name.eqIfNotBlank(null) + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).doesNotContain("t0.name = ?"); + } + + @Test + void blankWhitespace_skipsPredicate() { + var q = new QCustomer() + .select(name) + .name.isNotNull() + .name.eqIfNotBlank(" ") + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).doesNotContain("t0.name = ?"); + } + + @Test + void empty_skipsPredicate() { + var q = new QCustomer() + .select(name) + .name.isNotNull() + .name.eqIfNotBlank("") + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).doesNotContain("t0.name = ?"); + } + + @Test + void present_trimsBoundValue() { + Database db = DB.getDefault(); + String unique = "eqIfNotBlank-" + System.nanoTime(); + + Customer customer = new Customer(); + customer.name = unique; + db.save(customer); + + Customer found = new QCustomer(db) + .name.eqIfNotBlank(" " + unique + " ") + .findOne(); + + assertThat(found).isNotNull(); + assertThat(found.name).isEqualTo(unique); + + db.delete(customer); + } +} From 26351325a80a7d1330ce5a1a66f74dbd5969b2b8 Mon Sep 17 00:00:00 2001 From: "robin.bygrave" Date: Sat, 6 Jun 2026 18:03:39 +1200 Subject: [PATCH 2/3] Fix test with overlapping cases --- .../src/test/java/org/querytest/QueryAlsoIfTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java b/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java index 3888f103a..8784c328b 100644 --- a/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java @@ -24,7 +24,7 @@ class QueryAlsoIfTest { .query(); q.findList(); - assertThat(q.getGeneratedSql()).isEqualTo("select /* QueryAlsoIfTest.also */ t0.id, t0.name from be_customer t0 where t0.name is not null and t0.email is not null"); + assertThat(q.getGeneratedSql()).isEqualTo("from be_customer t0 where t0.name is not null and t0.email is not null"); } @Test @@ -61,7 +61,7 @@ class QueryAlsoIfTest { .query(); q.findList(); - assertThat(q.getGeneratedSql()).isEqualTo("select /* QueryAlsoIfTest.applyIfPresent */ t0.id, t0.name from be_customer t0 where t0.name is not null and t0.status = ?"); + assertThat(q.getGeneratedSql()).isEqualTo("from be_customer t0 where t0.name is not null and t0.status = ?"); } @Test @@ -74,7 +74,7 @@ class QueryAlsoIfTest { .query(); q.findList(); - assertThat(q.getGeneratedSql()).isEqualTo("select /* QueryAlsoIfTest.notApplyIfPresent */ t0.id, t0.name from be_customer t0 where t0.name is not null"); + assertThat(q.getGeneratedSql()).contains("from be_customer t0 where t0.name is not null"); } @Test From 806c7cd7529359f5a5a2e2ad32235a0d0c4a4ed6 Mon Sep 17 00:00:00 2001 From: "robin.bygrave" Date: Sat, 6 Jun 2026 18:16:11 +1200 Subject: [PATCH 3/3] Fix test with overlapping cases --- .../src/test/java/org/querytest/QueryAlsoIfTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java b/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java index 8784c328b..a262cc248 100644 --- a/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QueryAlsoIfTest.java @@ -24,7 +24,7 @@ class QueryAlsoIfTest { .query(); q.findList(); - assertThat(q.getGeneratedSql()).isEqualTo("from be_customer t0 where t0.name is not null and t0.email is not null"); + assertThat(q.getGeneratedSql()).contains("from be_customer t0 where t0.name is not null and t0.email is not null"); } @Test @@ -61,7 +61,7 @@ class QueryAlsoIfTest { .query(); q.findList(); - assertThat(q.getGeneratedSql()).isEqualTo("from be_customer t0 where t0.name is not null and t0.status = ?"); + assertThat(q.getGeneratedSql()).contains("from be_customer t0 where t0.name is not null and t0.status = ?"); } @Test