From fbcd4c3133cffcf98bfb41d58cee450811577d43 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 21 Aug 2023 11:15:39 +1200 Subject: [PATCH] Add filterManyRaw() and Deprecate filterMany( ) The filterMany( ) uses the language parser which ultimately will we probably remove in ebean 15.x Code can migrate to filterManyRaw() instead or better yet change to use the improved query beans filterMany( ) option that will be included in this same ebean release. --- .../main/java/io/ebean/ExpressionList.java | 23 +++++++++ .../expression/DefaultExpressionList.java | 5 ++ .../server/expression/JunctionExpression.java | 5 ++ .../java/io/ebean/typequery/TQAssocBean.java | 26 ++++++++++ .../java/org/querytest/QCustomerTest.java | 35 ++++++++++++++ .../org/tests/query/TestQueryFilterMany.java | 47 ++++++++++++++++++- 6 files changed, 140 insertions(+), 1 deletion(-) diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index c3b272da7..d5416a38c 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -508,6 +508,8 @@ public interface ExpressionList { ExpressionList filterMany(String manyProperty); /** + * Deprecated for removal - migrate to filterManyRaw() + *

* Add filter expressions to the many property. * *

{@code
@@ -524,8 +526,29 @@ public interface ExpressionList {
    * @param expressions  Filter expressions with and, or and ? or ?1 type bind parameters
    * @param params       Bind parameters used in the expressions
    */
+  @Deprecated(forRemoval = true)
   ExpressionList filterMany(String manyProperty, String expressions, Object... params);
 
+  /**
+   * Add filter expressions for the many path. The expressions can include SQL functions if
+   * desired and the property names are translated to column names.
+   * 

+ * The expressions can contain placeholders for bind values using ? or ?1 style. + * + *

{@code
+   *
+   *     new QCustomer()
+   *       .name.startsWith("Postgres")
+   *       .contacts.filterMany("status = ? and firstName like ?", Contact.Status.NEW, "Rob%")
+   *       .findList();
+   *
+   * }
+ * + * @param rawExpressions The raw expressions which can include ? and ?1 style bind parameter placeholders + * @param params The parameter values to bind + */ + ExpressionList filterManyRaw(String manyProperty, String rawExpressions, Object... params); + /** * Specify specific properties to fetch on the main/root bean (aka partial * object). 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 a9c64bcaa..e049d5d87 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 @@ -444,6 +444,11 @@ public class DefaultExpressionList implements SpiExpressionList { return query.filterMany(manyProperty).where(expressions, params); } + @Override + public ExpressionList filterManyRaw(String manyProperty, String rawExpression, Object... params) { + return query.filterMany(manyProperty).raw(rawExpression, params); + } + @Override public Query withLock(Query.LockType lockType) { return query.withLock(lockType); 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 d90062675..d5c718531 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 @@ -317,6 +317,11 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr throw new IllegalStateException("filterMany not allowed on Junction expression list"); } + @Override + public ExpressionList filterManyRaw(String manyProperty, String rawExpression, Object... params) { + throw new IllegalStateException("filterMany not allowed on Junction expression list"); + } + @Override public Query usingTransaction(Transaction transaction) { return exprList.usingTransaction(transaction); diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java index 9c56d5852..44189e4be 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java @@ -188,6 +188,8 @@ public abstract class TQAssocBean extends TQAssoc { } /** + * Deprecated for removal - migrate to filterManyRaw() + *

* Apply a filter when fetching these beans. *

* The expressions can use any valid Ebean expression and contain @@ -215,11 +217,35 @@ public abstract class TQAssocBean extends TQAssoc { * @param expressions The expressions including and, or, not etc with ? and ?1 bind params. * @param params The bind parameter values */ + @Deprecated(forRemoval = true) public final R filterMany(String expressions, Object... params) { expr().filterMany(_name, expressions, params); return _root; } + /** + * Add filter expressions for the many path. The expressions can include SQL functions if + * desired and the property names are translated to column names. + *

+ * The expressions can contain placeholders for bind values using ? or ?1 style. + * + *

{@code
+   *
+   *     new QCustomer()
+   *       .name.startsWith("Shrek")
+   *       .contacts.filterMany("status = ? and firstName like ?", Contact.Status.NEW, "Rob%")
+   *       .findList();
+   *
+   * }
+ * + * @param rawExpressions The raw expressions which can include ? and ?1 style bind parameter placeholders + * @param params The parameter values to bind + */ + public final R filterManyRaw(String rawExpressions, Object... params) { + expr().filterManyRaw(_name, rawExpressions, params); + return _root; + } + /** * Is empty for a collection property. *

diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java index bd1e7bbe3..5027e1a1a 100644 --- a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java @@ -280,6 +280,41 @@ public class QCustomerTest { assertThat(q.getGeneratedSql()).isEqualTo("select t0.id, t0.name, t1.id, t1.first_name, t1.last_name from be_customer t0 left join be_contact t1 on t1.customer_id = t0.id where t1.first_name like ? escape'|' order by t0.id"); } + @Test + void filterManySeparateQuery() { + Customer cust = new Customer(); + cust.setName("filterManySeparateQuery"); + cust.setStatus(Customer.Status.GOOD); + cust.save(); + + var q = new QCustomer() + .select(FGCustomerContacts) + .contacts.filterManyRaw("firstName like ?", "R%") + .contacts.filterMany(c -> c.firstName.startsWith("R")) // same as filterManyRaw() expression + .setMaxRows(10) // force the ToMany path to be in a separate secondary query + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).isEqualTo("select t0.id, t0.name from be_customer t0 limit 10"); + } + + @Test + void filterManySingleQuery() { + Customer cust = new Customer(); + cust.setName("filterManySingleQuery"); + cust.setStatus(Customer.Status.GOOD); + cust.save(); + + var q = new QCustomer() + .select(FGCustomerContacts) + .contacts.filterManyRaw("firstName like ?", "R%") + .contacts.filterMany(c -> c.firstName.startsWith("R")) // same as filterManyRaw() expression + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).isEqualTo("select t0.id, t0.name from be_customer t0 limit 10"); + } + @Test public void testIdIn() { diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java b/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java index dffe3a72e..cecbdd617 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java @@ -124,7 +124,7 @@ public class TestQueryFilterMany extends BaseTestCase { List sqlList = LoggedSql.stop(); assertEquals(2, sqlList.size()); assertThat(sqlList.get(0)).contains("lower(t0.name) = ?"); - assertThat(sqlList.get(1)).contains("status = ?"); + assertThat(sqlList.get(1)).contains("t0.status = ?"); if (isH2() || isPostgresCompatible()) { assertThat(sqlList.get(0)).doesNotContain("offset"); @@ -133,6 +133,51 @@ public class TestQueryFilterMany extends BaseTestCase { } } + @Test + public void filterManyRaw_firstMaxRows_expressionFluidStyle() { + ResetBasicData.reset(); + + LoggedSql.start(); + + final Query query = DB.find(Customer.class) + .where().ieq("name", "Rob") + // use expression + fluid style adding maxRows/firstRow to filterMany + .filterManyRaw("orders", "status = ?", Order.Status.NEW) + .setMaxRows(100).setFirstRow(3).order("orderDate desc, id") + .order().asc("id").setMaxRows(5); + + final List customers = query.findList(); + assertThat(customers).isNotEmpty(); + List sqlList = LoggedSql.stop(); + assertEquals(2, sqlList.size()); + assertThat(sqlList.get(0)).contains("lower(t0.name) = ?"); + assertThat(sqlList.get(1)).contains("t0.status = ?"); + + if (isH2() || isPostgresCompatible()) { + assertThat(sqlList.get(0)).doesNotContain("offset"); + assertThat(sqlList.get(0)).contains(" limit 5"); + assertThat(sqlList.get(1)).contains(" order by t0.order_date desc, t0.id limit 100 offset 3"); + } + } + + @Test + public void filterManyRaw_singleQuery() { + ResetBasicData.reset(); + + LoggedSql.start(); + + final Query query = DB.find(Customer.class) + .where().ieq("name", "Rob") + .filterManyRaw("orders", "status = ?", Order.Status.NEW) + .order().asc("id"); + + final List customers = query.findList(); + assertThat(customers).isNotEmpty(); + List sqlList = LoggedSql.stop(); + assertEquals(1, sqlList.size()); + assertThat(sqlList.get(0)).contains(" where lower(t0.name) = ? and t1.status = ? order by t0.id"); + } + @Test public void test_with_findOne_rawSeparateQuery() { ResetBasicData.reset();