From e195f20981e100211e7250563ea08d47c19aed73 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Tue, 5 Mar 2019 22:13:38 +1300 Subject: [PATCH] #1645 - ENH: Add expression rawOrEmpty() ... as a convenience for conditionally added a raw expression --- src/main/java/io/ebean/ExpressionList.java | 62 +++++++++++++++ .../expression/DefaultExpressionList.java | 16 +++- .../server/expression/JunctionExpression.java | 5 ++ .../java/org/tests/query/TestWhereIn.java | 7 +- .../org/tests/query/TestWhereRawClause.java | 77 +++++++++++++++++++ 5 files changed, 162 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/ebean/ExpressionList.java b/src/main/java/io/ebean/ExpressionList.java index 8f5cc16fb..d669e91b5 100644 --- a/src/main/java/io/ebean/ExpressionList.java +++ b/src/main/java/io/ebean/ExpressionList.java @@ -1282,6 +1282,68 @@ public interface ExpressionList { */ ExpressionList raw(String raw); + /** + * Only add the raw expression if the values is not null or empty. + *

+ * This is a pure convenience expression to make it nicer to deal with the pattern where we use + * raw() expression with a subquery and only want to add the subquery predicate when the collection + * of values is not empty. + *

+ *

Without inOrEmpty()

+ *
{@code
+   *
+   *   query.where() // add some predicates
+   *     .eq("status", Status.NEW);
+   *
+   *   // common pattern - we can use rawOrEmpty() instead
+   *   if (orderIds != null && !orderIds.isEmpty()) {
+   *     query.where().raw("t0.customer_id in (select o.customer_id from orders o where o.id in (?1))", orderIds);
+   *   }
+   *
+   *   query.findList();
+   *
+   * }
+ * + *

Using rawOrEmpty()

+ * Note that in the example below we use the ?1 bind parameter to get "parameter expansion" + * for each element in the collection. + * + *
{@code
+   *
+   *   query.where()
+   *     .eq("status", Status.NEW)
+   *     // only add the expression if orderIds is not empty
+   *     .rawOrEmpty("t0.customer_id in (select o.customer_id from orders o where o.id in (?1))", orderIds);
+   *     .findList();
+   *
+   * }
+ * + *

Postgres ANY

+ * With Postgres we would often use the SQL ANY expression and array parameter binding + * rather than IN. + * + *
{@code
+   *
+   *   query.where()
+   *     .eq("status", Status.NEW)
+   *     .rawOrEmpty("t0.customer_id in (select o.customer_id from orders o where o.id = any(?))", orderIds);
+   *     .findList();
+   *
+   * }
+ *

+ * Note that we need to cast the Postgres array for UUID types like: + *

+ *
{@code
+   *
+   *   " ... = any(?::uuid[])"
+   *
+   * }
+ * + * @param raw The raw expression that is typically a subquery + * @param values The values which is typically a list or set of id values. + */ + ExpressionList rawOrEmpty(String raw, Collection values); + /** * Add a match expression. * diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index 1372a06d2..71600f840 100644 --- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -914,7 +914,9 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public ExpressionList inOrEmpty(String propertyName, Collection values) { - add(expr.inOrEmpty(propertyName, values)); + if (notEmpty(values)) { + add(expr.in(propertyName, values)); + } return this; } @@ -1074,6 +1076,18 @@ public class DefaultExpressionList implements SpiExpressionList { return this; } + @Override + public ExpressionList rawOrEmpty(String raw, Collection values) { + if (notEmpty(values)) { + add(expr.raw(raw, values)); + } + return this; + } + + private boolean notEmpty(Collection values) { + return values != null && !values.isEmpty(); + } + @Override public ExpressionList startsWith(String propertyName, String value) { add(expr.startsWith(propertyName, value)); diff --git a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index ee920b048..010550a3b 100644 --- a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -807,6 +807,11 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.raw(raw, values); } + @Override + public ExpressionList rawOrEmpty(String raw, Collection values) { + return exprList.rawOrEmpty(raw, values); + } + @Override public ExpressionList raw(String raw) { return exprList.raw(raw); diff --git a/src/test/java/org/tests/query/TestWhereIn.java b/src/test/java/org/tests/query/TestWhereIn.java index c173bbc30..6fd900d68 100644 --- a/src/test/java/org/tests/query/TestWhereIn.java +++ b/src/test/java/org/tests/query/TestWhereIn.java @@ -53,7 +53,7 @@ public class TestWhereIn extends BaseTestCase { .where().inOrEmpty("customer.billingAddress.id", new ArrayList<>()).query(); query.findList(); - assertThat(sqlOf(query)).isEqualTo("select t0.id from o_order t0 where 1=1"); + assertThat(sqlOf(query)).isEqualTo("select t0.id from o_order t0"); } @Test @@ -79,10 +79,9 @@ public class TestWhereIn extends BaseTestCase { .where().inOrEmpty("code", null).query(); query.findList(); - assertThat(sqlOf(query)).contains("where 1=1"); + assertThat(sqlOf(query)).isEqualTo("select t0.code, t0.name from o_country t0"); } - @Test public void testInOrEmpty_when_empty() { @@ -92,7 +91,7 @@ public class TestWhereIn extends BaseTestCase { .where().inOrEmpty("code", new ArrayList<>()).query(); query.findList(); - assertThat(sqlOf(query)).contains("where 1=1"); + assertThat(sqlOf(query)).isEqualTo("select t0.code, t0.name from o_country t0"); } @Test diff --git a/src/test/java/org/tests/query/TestWhereRawClause.java b/src/test/java/org/tests/query/TestWhereRawClause.java index a84ffdfd6..58b7d9b59 100644 --- a/src/test/java/org/tests/query/TestWhereRawClause.java +++ b/src/test/java/org/tests/query/TestWhereRawClause.java @@ -6,6 +6,7 @@ import io.ebean.Expr; import io.ebean.Query; import io.ebean.annotation.ForPlatform; import io.ebean.annotation.Platform; +import org.ebeantest.LoggedSqlCollector; import org.junit.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; @@ -13,6 +14,7 @@ import org.tests.model.basic.OrderDetail; import org.tests.model.basic.ResetBasicData; import java.sql.Timestamp; +import java.util.Collections; import java.util.List; import static java.util.Arrays.asList; @@ -65,6 +67,54 @@ public class TestWhereRawClause extends BaseTestCase { assertThat(sqlOf(query)).contains(" t0.id in (select c.id from o_customer c where c.name in (?,?,?))"); } + @Test + public void testRawOrEmpty_when_notEmpty() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .select("name") + .where() + .rawOrEmpty("id in (select c.id from o_customer c where c.name in (?1))", asList("Rob", "Fiona", "Jack")) + .query(); + + List list = query.findList(); + assertThat(list).isNotEmpty(); + assertThat(sqlOf(query)).contains(" t0.id in (select c.id from o_customer c where c.name in (?,?,?))"); + } + + @Test + public void testRawOrEmpty_when_null() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .select("name") + .where() + .rawOrEmpty("id in (select c.id from o_customer c where c.name in (?1))", null) + .query(); + + List list = query.findList(); + assertThat(list).isNotEmpty(); + assertThat(sqlOf(query)).isEqualTo("select t0.id, t0.name from o_customer t0"); + } + + @Test + public void testRawOrEmpty_when_empty() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .select("name") + .where() + .rawOrEmpty("id in (select c.id from o_customer c where c.name in (?1))", Collections.emptySet()) + .query(); + + List list = query.findList(); + assertThat(list).isNotEmpty(); + assertThat(sqlOf(query)).isEqualTo("select t0.id, t0.name from o_customer t0"); + } + @Test public void testRaw_bindExpansion() { @@ -94,6 +144,33 @@ public class TestWhereRawClause extends BaseTestCase { assertThat(list).isNotEmpty(); } + @Test + @ForPlatform(Platform.POSTGRES) + public void testRawOrEmpty_PostgresArray() { + + ResetBasicData.reset(); + + LoggedSqlCollector.start(); + + Ebean.find(Customer.class) + .select("name").where().rawOrEmpty("name = any(?)", asList("Rob", "Fiona", "Jack")) + .findList(); + + Ebean.find(Customer.class) + .select("name").where().rawOrEmpty("name = any(?)", asList()) + .findList(); + + Ebean.find(Customer.class) + .select("name").where().rawOrEmpty("name = any(?)", null) + .findList(); + + List sql = LoggedSqlCollector.stop(); + + assertThat(sql.get(0)).isEqualTo("select t0.id, t0.name from o_customer t0 where t0.name = any(?); --bind(Array[3]={Rob,Fiona,Jack})"); + assertThat(sql.get(1)).isEqualTo("select t0.id, t0.name from o_customer t0; --bind()"); + assertThat(sql.get(2)).isEqualTo("select t0.id, t0.name from o_customer t0; --bind()"); + } + @Test public void testRawWithBindParams() {