From 5de67e193966b6f03a8a1f3d2beee0636bbd9089 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Fri, 18 Mar 2016 14:11:51 +1300 Subject: [PATCH] #606 - Add not() as a junction expression (like and() and or()) to the query criteria API --- .../com/avaje/ebean/ExpressionFactory.java | 2 +- .../java/com/avaje/ebean/ExpressionList.java | 38 +++- src/main/java/com/avaje/ebean/Junction.java | 70 +++--- .../expression/DefaultExpressionFactory.java | 19 +- .../expression/DefaultExpressionList.java | 102 +++++---- .../server/expression/JunctionExpression.java | 205 ++++++++---------- .../expression/JunctionExpressionTest.java | 5 +- .../query/TestExprNestedDisjunction.java | 54 ++++- 8 files changed, 291 insertions(+), 204 deletions(-) diff --git a/src/main/java/com/avaje/ebean/ExpressionFactory.java b/src/main/java/com/avaje/ebean/ExpressionFactory.java index 0694886e4..a5647fc78 100644 --- a/src/main/java/com/avaje/ebean/ExpressionFactory.java +++ b/src/main/java/com/avaje/ebean/ExpressionFactory.java @@ -357,6 +357,6 @@ public interface ExpressionFactory { * This is doc store Elastic only. *

*/ - Junction textJunction(Query query, ExpressionList parent, Junction.Type type); + Junction junction(Junction.Type type, Query query, ExpressionList parent); } diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index 22491210e..278dfc2ab 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -900,6 +900,11 @@ public interface ExpressionList { */ Junction or(); + /** + * Return a list of expressions that will be wrapped by NOT. + */ + Junction not(); + /** * Return a list of expressions that will be joined by AND's. */ @@ -910,15 +915,6 @@ public interface ExpressionList { */ Junction disjunction(); - /** - * End a Conjunction or Disjunction returning the parent expression list. - *

- * Alternatively you can always use where() to return the top level expression - * list. - *

- */ - ExpressionList endJunction(); - /** * Start a list of expressions that will be joined by MUST. */ @@ -934,6 +930,30 @@ public interface ExpressionList { */ Junction mustNot(); + /** + * End a Conjunction or Disjunction returning the parent expression list. + *

+ * Alternatively you can always use where() to return the top level expression + * list. + *

+ */ + ExpressionList endJunction(); + + /** + * End the list of AND expressions. + */ + ExpressionList endAnd(); + + /** + * End the list of OR expressions. + */ + ExpressionList endOr(); + + /** + * End the list of NOT expressions. + */ + ExpressionList endNot(); + /** * End the list of MUST expressions. */ diff --git a/src/main/java/com/avaje/ebean/Junction.java b/src/main/java/com/avaje/ebean/Junction.java index 36a130ee7..06af1586b 100644 --- a/src/main/java/com/avaje/ebean/Junction.java +++ b/src/main/java/com/avaje/ebean/Junction.java @@ -10,68 +10,68 @@ package com.avaje.ebean; * Note: where() always takes you to the top level WHERE expression list. *

* - *
+ * 
{@code
  * Query q =
  *     Ebean.find(Person.class)
  *         .where().disjunction()
- *         .like("name", "Rob%")
- *         .eq("status", Status.NEW)
+ *         .like("name", "Rob%")
+ *         .eq("status", Status.NEW)
  *
  *         // where() returns us to the top level expression list
- *         .where().gt("id", 10);
+ *         .where().gt("id", 10);
  *
  * // read as...
  * // where ( ((name like Rob%) or (status = NEW)) AND (id > 10) )
- * 
+ * }
* *

* Note: endJunction() takes you to the parent expression list *

* - *
+ * 
{@code
  * Query q =
  *     Ebean.find(Person.class)
  *         .where().disjunction()
- *         .like("name", "Rob%")
- *         .eq("status", Status.NEW)
+ *         .like("name", "Rob%")
+ *         .eq("status", Status.NEW)
  *         .endJunction()
  *
  *         // endJunction().. takes us to the 'parent' expression list
  *         // which in this case is the top level (same as where())
  *
- *         .gt("id", 10);
+ *         .gt("id", 10);
  *
  * // read as...
  * // where ( ((name like Rob%) or (status = NEW)) AND (id > 10) )
- * 
+ * }
* *

* Example of a nested disjunction. *

* - *
- * Query<Customer> q =
+ * 
{@code
+ * Query q =
  *  Ebean.find(Customer.class)
  *      .where()
- *          .disjunction()
- *              .conjunction()
- *                  .startsWith("name", "r")
- *                  .eq("anniversary", onAfter)
+ *          .or()
+ *              .and()
+ *                  .startsWith("name", "r")
+ *                  .eq("anniversary", onAfter)
  *                  .endJunction()
- *              .conjunction()
- *                  .eq("status", Customer.Status.ACTIVE)
- *                  .gt("id", 0)
+ *              .and()
+ *                  .eq("status", Customer.Status.ACTIVE)
+ *                  .gt("id", 0)
  *                  .endJunction()
- *      .order().asc("name");
+ *      .order().asc("name");
  *
  * q.findList();
  * String s = q.getGeneratedSql();
  *
  *  // this produces an expression like:
  *
- *  ( name like ? and c.anniversary = ? ) or (c.status = ?  and c.id > ? )
+ *  ( name like ? and c.anniversary = ? ) or (c.status = ?  and c.id > ? )
  *
- * 
+ * }
*/ public interface Junction extends Expression, ExpressionList { @@ -83,32 +83,39 @@ public interface Junction extends Expression, ExpressionList { /** * AND group. */ - AND(" and "), + AND(" and ", ""), /** * OR group. */ - OR(" or "), + OR(" or ", ""), + + /** + * NOT group. + */ + NOT(" and ", "not "), /** * Text search AND group. */ - MUST("must"), + MUST("must", ""), /** * Text search NOT group. */ - MUST_NOT("must_not"), + MUST_NOT("must_not", ""), /** * Text search OR group. */ - SHOULD("should"); + SHOULD("should", ""); + String prefix; String literal; - Type(String literal) { + Type(String literal, String prefix) { this.literal = literal; + this.prefix = prefix; } /** @@ -117,6 +124,13 @@ public interface Junction extends Expression, ExpressionList { public String literal() { return literal; } + + /** + * Return the prefix value for this type. + */ + public String prefix() { + return prefix; + } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java index fa7323d49..c74f9b339 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -436,35 +436,42 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { * Return a list of expressions that will be joined by AND's. */ public Junction conjunction(Query query) { - return new JunctionExpression.Conjunction(query, query.where()); + return new JunctionExpression(Junction.Type.AND, query, query.where()); } /** * Return a list of expressions that will be joined by OR's. */ public Junction disjunction(Query query) { - return new JunctionExpression.Disjunction(query, query.where()); + return new JunctionExpression(Junction.Type.OR, query, query.where()); } /** * Return a list of expressions that will be joined by AND's. */ public Junction conjunction(Query query, ExpressionList parent) { - return new JunctionExpression.Conjunction(query, parent); + return new JunctionExpression(Junction.Type.AND, query, parent); } /** * Return a list of expressions that will be joined by OR's. */ public Junction disjunction(Query query, ExpressionList parent) { - return new JunctionExpression.Disjunction(query, parent); + return new JunctionExpression(Junction.Type.OR, query, parent); + } + + /** + * Return a list of expressions that are wrapped by NOT. + */ + public Junction junction(Junction.Type type, Query query) { + return new JunctionExpression(type, query, query.where()); } /** * Create and return a Full text junction (Must, Must Not or Should). */ @Override - public Junction textJunction(Query query, ExpressionList parent, Junction.Type type) { - return new JunctionExpression.TextJunction(query, parent, type); + public Junction junction(Junction.Type type, Query query, ExpressionList parent) { + return new JunctionExpression(type, query, parent); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java index d0dfb166d..d8d4332e1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java @@ -202,15 +202,6 @@ public class DefaultExpressionList implements SpiExpressionList { } } - @Override - public ExpressionList endJunction() { - return parentExprList == null ? this : parentExprList; - } - - protected ExpressionList endTextJunction() { - return parentExprList == null ? this : parentExprList; - } - @Override public Query query() { return query; @@ -646,42 +637,12 @@ public class DefaultExpressionList implements SpiExpressionList { return this; } - public Junction textJunction(Junction.Type type) { - Junction junction = expr.textJunction(query, this, type); - add(junction); - return junction; - } - - @Override - public Junction and() { - return conjunction(); - } - - @Override - public Junction or() { - return disjunction(); - } - - @Override - public Junction conjunction() { - Junction conjunction = expr.conjunction(query, this); - add(conjunction); - return conjunction; - } - @Override public ExpressionList contains(String propertyName, String value) { add(expr.contains(propertyName, value)); return this; } - @Override - public Junction disjunction() { - Junction disjunction = expr.disjunction(query, this); - add(disjunction); - return disjunction; - } - @Override public ExpressionList endsWith(String propertyName, String value) { add(expr.endsWith(propertyName, value)); @@ -911,33 +872,84 @@ public class DefaultExpressionList implements SpiExpressionList { return this; } + private Junction junction(Junction.Type type) { + Junction junction = expr.junction(type, query, this); + add(junction); + return junction; + } + + @Override + public ExpressionList endJunction() { + return parentExprList == null ? this : parentExprList; + } + + @Override + public Junction and() { + return conjunction(); + } + + @Override + public Junction or() { + return disjunction(); + } + + @Override + public Junction not() { + return junction(Junction.Type.NOT); + } + + @Override + public Junction conjunction() { + return junction(Junction.Type.AND); + } + + @Override + public Junction disjunction() { + return junction(Junction.Type.OR); + } + @Override public Junction must() { - return textJunction(Junction.Type.MUST); + return junction(Junction.Type.MUST); } @Override public Junction should() { - return textJunction(Junction.Type.SHOULD); + return junction(Junction.Type.SHOULD); } @Override public Junction mustNot() { - return textJunction(Junction.Type.MUST_NOT); + return junction(Junction.Type.MUST_NOT); + } + + @Override + public ExpressionList endAnd() { + return endJunction(); + } + + @Override + public ExpressionList endOr() { + return endJunction(); + } + + @Override + public ExpressionList endNot() { + return endJunction(); } @Override public ExpressionList endMust() { - return endTextJunction(); + return endJunction(); } @Override public ExpressionList endShould() { - return endTextJunction(); + return endJunction(); } @Override public ExpressionList endMustNot() { - return endTextJunction(); + return endJunction(); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java index 64708122c..beca6b9e1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -1,6 +1,19 @@ package com.avaje.ebeaninternal.server.expression; -import com.avaje.ebean.*; +import com.avaje.ebean.Expression; +import com.avaje.ebean.ExpressionList; +import com.avaje.ebean.FetchPath; +import com.avaje.ebean.FutureIds; +import com.avaje.ebean.FutureList; +import com.avaje.ebean.FutureRowCount; +import com.avaje.ebean.Junction; +import com.avaje.ebean.OrderBy; +import com.avaje.ebean.PagedList; +import com.avaje.ebean.Query; +import com.avaje.ebean.QueryEachConsumer; +import com.avaje.ebean.QueryEachWhileConsumer; +import com.avaje.ebean.QueryIterator; +import com.avaje.ebean.Version; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebean.search.Match; import com.avaje.ebean.search.MultiMatch; @@ -25,52 +38,7 @@ import java.util.Set; /** * Junction implementation. */ -abstract class JunctionExpression implements SpiJunction, SpiExpression, ExpressionList { - - static class TextJunction extends JunctionExpression { - - TextJunction(Query query, ExpressionList parent, TextJunction.Type type) { - super(type, query, parent); - } - TextJunction(TextJunction.Type type, DefaultExpressionList expressionList) { - super(type, expressionList); - } - @Override - public SpiExpression copyForPlanKey() { - return new TextJunction(type, exprList.copyForPlanKey()); - } - } - - static class Conjunction extends JunctionExpression { - - Conjunction(Query query, ExpressionList parent) { - super(Type.AND, query, parent); - } - - Conjunction(DefaultExpressionList expressionList) { - super(Type.AND, expressionList); - } - @Override - public SpiExpression copyForPlanKey() { - return new Conjunction(exprList.copyForPlanKey()); - } - } - - static class Disjunction extends JunctionExpression { - - Disjunction(Query query, ExpressionList parent) { - super(Type.OR, query, parent); - } - - Disjunction(DefaultExpressionList expressionList) { - super(Type.OR, expressionList); - } - - @Override - public SpiExpression copyForPlanKey() { - return new Disjunction(exprList.copyForPlanKey()); - } - } +class JunctionExpression implements SpiJunction, SpiExpression, ExpressionList { protected final DefaultExpressionList exprList; @@ -89,6 +57,10 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E this.exprList = exprList; } + public SpiExpression copyForPlanKey() { + return new JunctionExpression(type, exprList.copyForPlanKey()); + } + @Override public void writeDocQuery(DocQueryContext context) throws IOException { context.startBool(type == Type.AND); @@ -151,7 +123,6 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E public void addBindValues(SpiExpressionRequest request) { List list = exprList.internalList(); - for (int i = 0; i < list.size(); i++) { list.get(i).addBindValues(request); } @@ -163,8 +134,8 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E List list = exprList.internalList(); if (!list.isEmpty()) { + request.append(type.prefix()); request.append("("); - for (int i = 0; i < list.size(); i++) { SpiExpression item = list.get(i); if (i > 0) { @@ -172,7 +143,6 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E } item.addSql(request); } - request.append(") "); } } @@ -200,12 +170,10 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E @Override public int queryBindHash() { int hc = JunctionExpression.class.getName().hashCode(); - List list = exprList.internalList(); for (int i = 0; i < list.size(); i++) { hc = hc * 31 + list.get(i).queryBindHash(); } - return hc; } @@ -261,44 +229,6 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E return exprList.textCommonTerms(search, options); } - @Override - public Junction must() { - return exprList.must(); - } - - @Override - public Junction should() { - return exprList.should(); - } - - @Override - public Junction mustNot() { - return exprList.mustNot(); - } - - @Override - public ExpressionList endMust() { - return endTextJunction(); - } - - @Override - public ExpressionList endShould() { - return endTextJunction(); - } - - @Override - public ExpressionList endMustNot() { - return endTextJunction(); - } - - private ExpressionList endTextJunction() { - return exprList.endTextJunction(); - } - - @Override - public ExpressionList endJunction() { - return exprList.endJunction(); - } @Override public ExpressionList allEq(Map propertyMap) { @@ -320,31 +250,11 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E return exprList.betweenProperties(lowProperty, highProperty, value); } - @Override - public Junction and() { - return conjunction(); - } - - @Override - public Junction or() { - return disjunction(); - } - - @Override - public Junction conjunction() { - return exprList.conjunction(); - } - @Override public ExpressionList contains(String propertyName, String value) { return exprList.contains(propertyName, value); } - @Override - public Junction disjunction() { - return exprList.disjunction(); - } - @Override public ExpressionList endsWith(String propertyName, String value) { return exprList.endsWith(propertyName, value); @@ -787,4 +697,79 @@ abstract class JunctionExpression implements SpiJunction, SpiExpression, E return exprList.where(); } + @Override + public Junction and() { + return conjunction(); + } + + @Override + public Junction or() { + return disjunction(); + } + + @Override + public Junction not() { + return exprList.not(); + } + + @Override + public Junction conjunction() { + return exprList.conjunction(); + } + + @Override + public Junction disjunction() { + return exprList.disjunction(); + } + + @Override + public Junction must() { + return exprList.must(); + } + + @Override + public Junction should() { + return exprList.should(); + } + + @Override + public Junction mustNot() { + return exprList.mustNot(); + } + + @Override + public ExpressionList endJunction() { + return exprList.endJunction(); + } + + @Override + public ExpressionList endAnd() { + return endJunction(); + } + + @Override + public ExpressionList endOr() { + return endJunction(); + } + + @Override + public ExpressionList endNot() { + return endJunction(); + } + + @Override + public ExpressionList endMust() { + return endJunction(); + } + + @Override + public ExpressionList endShould() { + return endJunction(); + } + + @Override + public ExpressionList endMustNot() { + return endJunction(); + } + } diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java index a4bd1c166..26610a0c4 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/JunctionExpressionTest.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression; import com.avaje.ebean.Expr; import com.avaje.ebean.Expression; +import com.avaje.ebean.Junction; import org.junit.Test; import static org.assertj.core.api.StrictAssertions.assertThat; @@ -23,11 +24,11 @@ public class JunctionExpressionTest { } JunctionExpression and(DefaultExpressionList list) { - return new JunctionExpression.Conjunction(list); + return new JunctionExpression(Junction.Type.AND, list); } JunctionExpression or(DefaultExpressionList list) { - return new JunctionExpression.Disjunction(list); + return new JunctionExpression(Junction.Type.OR, list); } @Test diff --git a/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java b/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java index 3e2bbb697..3f178646d 100644 --- a/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java +++ b/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java @@ -9,6 +9,8 @@ import com.avaje.ebean.Query; import com.avaje.tests.model.basic.Customer; import com.avaje.tests.model.basic.ResetBasicData; +import static org.assertj.core.api.Assertions.assertThat; + public class TestExprNestedDisjunction extends BaseTestCase { @Test @@ -42,9 +44,11 @@ public class TestExprNestedDisjunction extends BaseTestCase { .where() .or() .and() - .startsWith("name", "r").eq("anniversary", onAfter).endJunction() - .and().eq("status", Customer.Status.ACTIVE).gt("id", 0).endJunction().orderBy() - .asc("name"); + .startsWith("name", "r").eq("anniversary", onAfter).endAnd() + .and() + .eq("status", Customer.Status.ACTIVE).gt("id", 0).endAnd() + .endOr() + .orderBy().asc("name"); q.findList(); String s = q.getGeneratedSql(); @@ -52,4 +56,48 @@ public class TestExprNestedDisjunction extends BaseTestCase { Assert.assertTrue(s.contains("(t0.name like ? ")); Assert.assertTrue(s.contains(" and t0.anniversary = ? ) or (t0.status = ? and t0.id > ? )")); } + + @Test + public void test_not() { + + ResetBasicData.reset(); + + java.sql.Date onAfter = java.sql.Date.valueOf("2009-08-31"); + + Query q = Ebean.find(Customer.class) + .where() + .not() + .gt("id", 1) + .eq("anniversary", onAfter) + .endNot() + .orderBy().asc("name"); + + q.findList(); + String s = q.getGeneratedSql(); + + assertThat(s).contains("where not (t0.id > ? and t0.anniversary = ? ) order by t0.name"); + } + + @Test + public void test_not_nested() { + + ResetBasicData.reset(); + + java.sql.Date onAfter = java.sql.Date.valueOf("2009-08-31"); + + Query q = Ebean.find(Customer.class) + .where() + .or() + .eq("status", Customer.Status.ACTIVE) + .not() + .gt("id", 1) + .eq("anniversary", onAfter) + //.endNot() + .orderBy().asc("name"); + + q.findList(); + String s = q.getGeneratedSql(); + + assertThat(s).contains("where (t0.status = ? or not (t0.id > ? and t0.anniversary = ? ) )"); + } }