From 87b485cfa4802625c30ff5a9c811ae000ecb74c9 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 27 Jul 2015 20:39:34 +1200 Subject: [PATCH] #355 - ENH: Add notIn(...) expressions. Just to make life easier in those cases --- .../com/avaje/ebean/ExpressionFactory.java | 17 +++- .../java/com/avaje/ebean/ExpressionList.java | 17 +++- .../expression/DefaultExpressionFactory.java | 29 +++++- .../server/expression/InExpression.java | 21 +++-- .../server/expression/InQueryExpression.java | 16 ++-- .../server/expression/JunctionExpression.java | 17 +++- .../util/DefaultExpressionList.java | 18 ++++ .../server/expression/InExpressionTest.java | 90 +++++++++++++++++++ .../com/avaje/tests/query/TestQueryAlias.java | 34 ++++++- 9 files changed, 237 insertions(+), 22 deletions(-) create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java diff --git a/src/main/java/com/avaje/ebean/ExpressionFactory.java b/src/main/java/com/avaje/ebean/ExpressionFactory.java index 398ef08a93..4d9a5e2fc8 100644 --- a/src/main/java/com/avaje/ebean/ExpressionFactory.java +++ b/src/main/java/com/avaje/ebean/ExpressionFactory.java @@ -169,7 +169,22 @@ public interface ExpressionFactory { * In - property has a value in the collection of values. */ Expression in(String propertyName, Collection values); - + + /** + * Not In - property has a value in the array of values. + */ + Expression notIn(String propertyName, Object[] values); + + /** + * Not In - property has a value in the collection of values. + */ + Expression notIn(String propertyName, Collection values); + + /** + * Not In - using a subQuery. + */ + Expression notIn(String propertyName, Query subQuery); + /** * Exists expression */ diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index ac523960c2..d4b95560b4 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -488,7 +488,22 @@ public interface ExpressionList extends Serializable { * In - property has a value in the collection of values. */ ExpressionList in(String propertyName, Collection values); - + + /** + * Not In - property has a value in the array of values. + */ + ExpressionList notIn(String propertyName, Object... values); + + /** + * Not In - property has a value in the collection of values. + */ + ExpressionList notIn(String propertyName, Collection values); + + /** + * Not In - using a subQuery. + */ + ExpressionList notIn(String propertyName, Query subQuery); + /** * Exists expression */ 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 13f6feeeef..6ee4c3676a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -225,23 +225,44 @@ public Expression icontains(String propertyName, String value) { * In - property has a value in the array of values. */ public Expression in(String propertyName, Object[] values) { - return new InExpression(propertyName, values); + return new InExpression(propertyName, values, false); } /** * In - using a subQuery. */ public Expression in(String propertyName, Query subQuery) { - return new InQueryExpression(propertyName, (SpiQuery) subQuery); + return new InQueryExpression(propertyName, (SpiQuery) subQuery, false); } /** * In - property has a value in the collection of values. */ public Expression in(String propertyName, Collection values) { - return new InExpression(propertyName, values); + return new InExpression(propertyName, values, false); } - + + /** + * In - property has a value in the array of values. + */ + public Expression notIn(String propertyName, Object[] values) { + return new InExpression(propertyName, values, true); + } + + /** + * Not In - property has a value in the collection of values. + */ + public Expression notIn(String propertyName, Collection values) { + return new InExpression(propertyName, values, true); + } + + /** + * In - using a subQuery. + */ + public Expression notIn(String propertyName, Query subQuery) { + return new InQueryExpression(propertyName, (SpiQuery) subQuery, true); + } + /** * Exists subquery */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java index bf4012472c..db4d0d7aa3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InExpression.java @@ -12,16 +12,20 @@ class InExpression extends AbstractExpression { private static final long serialVersionUID = 3150665801693551260L; + private final boolean not; + private final Object[] values; - InExpression(String propertyName, Collection coll) { + InExpression(String propertyName, Collection coll, boolean not) { super(propertyName); - values = coll.toArray(new Object[coll.size()]); + this.values = coll.toArray(new Object[coll.size()]); + this.not = not; } - InExpression(String propertyName, Object[] array) { + InExpression(String propertyName, Object[] array, boolean not) { super(propertyName); this.values = array; + this.not = not; } public void addBindValues(SpiExpressionRequest request) { @@ -50,8 +54,10 @@ public void addBindValues(SpiExpressionRequest request) { public void addSql(SpiExpressionRequest request) { if (values.length == 0) { - // 'no match' for in empty collection - request.append("1=0"); + if (!not) { + // 'no match' for in empty collection + request.append("1=0"); + } return; } @@ -69,6 +75,9 @@ public void addSql(SpiExpressionRequest request) { } else { request.append(propertyName); + if (not) { + request.append(" not"); + } request.append(" in (?"); for (int i = 1; i < values.length; i++) { request.append(", ").append("?"); @@ -82,7 +91,7 @@ public void addSql(SpiExpressionRequest request) { * Based on the number of values in the in clause. */ public void queryAutoFetchHash(HashQueryPlanBuilder builder) { - builder.add(InExpression.class).add(propName).add(values.length); + builder.add(InExpression.class).add(propName).add(values.length).add(not); builder.bind(values.length); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java index 59601047b1..dd8506d16f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/InQueryExpression.java @@ -18,18 +18,20 @@ class InQueryExpression extends AbstractExpression { private static final long serialVersionUID = 666990277309851644L; + private final boolean not; + private final SpiQuery subQuery; private transient CQuery compiledSubQuery; - public InQueryExpression(String propertyName, SpiQuery subQuery) { + public InQueryExpression(String propertyName, SpiQuery subQuery, boolean not) { super(propertyName); this.subQuery = subQuery; + this.not = not; } public void queryAutoFetchHash(HashQueryPlanBuilder builder) { - builder.add(InQueryExpression.class).add(propName); - + builder.add(InQueryExpression.class).add(propName).add(not); subQuery.queryAutofetchHash(builder); } @@ -61,9 +63,11 @@ public void addSql(SpiExpressionRequest request) { subSelect = subSelect.replace('\n', ' '); String propertyName = getPropertyName(); - request.append(" ("); - request.append(propertyName); - request.append(") in ("); + request.append(" (").append(propertyName).append(")"); + if (not) { + request.append(" not"); + } + request.append(" in ("); request.append(subSelect); request.append(") "); } 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 58b7965aeb..e4a6231260 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -367,7 +367,22 @@ public ExpressionList in(String propertyName, Object... values) { public ExpressionList in(String propertyName, com.avaje.ebean.Query subQuery) { return exprList.in(propertyName, subQuery); } - + + @Override + public ExpressionList notIn(String propertyName, Collection values) { + return exprList.notIn(propertyName, values); + } + + @Override + public ExpressionList notIn(String propertyName, Object... values) { + return exprList.notIn(propertyName, values); + } + + @Override + public ExpressionList notIn(String propertyName, com.avaje.ebean.Query subQuery) { + return exprList.notIn(propertyName, subQuery); + } + @Override public ExpressionList exists(Query subQuery) { return exprList.exists(subQuery); diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index 71d4d8c529..edafc330b4 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -475,6 +475,24 @@ public ExpressionList in(String propertyName, Object... values) { return this; } + @Override + public ExpressionList notIn(String propertyName, Object... values) { + add(expr.notIn(propertyName, values)); + return this; + } + + @Override + public ExpressionList notIn(String propertyName, Collection values) { + add(expr.notIn(propertyName, values)); + return this; + } + + @Override + public ExpressionList notIn(String propertyName, Query subQuery) { + add(expr.notIn(propertyName, subQuery)); + return this; + } + @Override public ExpressionList exists(Query subQuery) { add(expr.exists(subQuery)); diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java new file mode 100644 index 0000000000..7c1b6e8aee --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/InExpressionTest.java @@ -0,0 +1,90 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +public class InExpressionTest { + + + @Test + public void queryPlanHash_given_diffPropertyName_should_differentPlanHash() throws Exception { + + List values = values(42, 92); + + InExpression ex1 = new InExpression("foo", values, false); + InExpression ex2 = new InExpression("bar", values, false); + + HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); + ex1.queryPlanHash(null, b1); + + HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); + ex2.queryPlanHash(null, b2); + + assertNotEquals(b1.build(), b2.build()); + } + + @Test + public void queryPlanHash_given_diffBindCount_should_differentPlanHash() throws Exception { + + List values1 = values(42, 92); + List values2 = values(42, 92, 82); + + InExpression ex1 = new InExpression("foo", values1, false); + InExpression ex2 = new InExpression("foo", values2, false); + + HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); + ex1.queryPlanHash(null, b1); + + HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); + ex2.queryPlanHash(null, b2); + + assertNotEquals(b1.build(), b2.build()); + } + + @Test + public void queryPlanHash_given_diffNotFlag_should_differentPlanHash() throws Exception { + + List values = values(42, 92); + + InExpression ex1 = new InExpression("foo", values, true); + InExpression ex2 = new InExpression("foo", values, false); + + HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); + ex1.queryPlanHash(null, b1); + + HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); + ex2.queryPlanHash(null, b2); + + assertNotEquals(b1.build(), b2.build()); + } + + @Test + public void queryPlanHash_given_sameNotFlag_should_samePlanHash() throws Exception { + + List values = values(42, 92); + + InExpression ex1 = new InExpression("foo", values, true); + InExpression ex2 = new InExpression("foo", values, true); + + HashQueryPlanBuilder b1 = new HashQueryPlanBuilder(); + ex1.queryPlanHash(null, b1); + + HashQueryPlanBuilder b2 = new HashQueryPlanBuilder(); + ex2.queryPlanHash(null, b2); + + assertEquals(b1.build(), b2.build()); + } + + List values(int... vals) { + ArrayList list = new ArrayList(); + for (int i = 0; i < vals.length; i++) { + list.add(vals[i]); + } + return list; + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/query/TestQueryAlias.java b/src/test/java/com/avaje/tests/query/TestQueryAlias.java index 5b498ac34b..b764cd059f 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryAlias.java +++ b/src/test/java/com/avaje/tests/query/TestQueryAlias.java @@ -10,8 +10,10 @@ import com.avaje.tests.model.basic.ResetBasicData; public class TestQueryAlias extends BaseTestCase { + @Test public void testExists() { + ResetBasicData.reset(); Query sq = Ebean.createQuery(CKeyParent.class) @@ -24,7 +26,6 @@ public void testExists() { String sql = pq.getGeneratedSql(); - System.out.println(sql); // Without alias command is should be: // select t0.one_key c0, t0.two_key c1, t0.name c2, t0.version c3, t0.assoc_id c4 from ckey_parent t0 where (t0 // .one_key) in (select t0.one_key from ckey_parent t0) @@ -32,7 +33,34 @@ public void testExists() { // select myt0.one_key c0, myt0.two_key c1, myt0.name c2, myt0.version c3, myt0.assoc_id c4 from ckey_parent myt0 // where (myt0.one_key) in (select st0.one_key from ckey_parent st0) - Assert.assertTrue(sql.indexOf("ckey_parent myt0") > 0); - Assert.assertTrue(sql.indexOf("in (select st0.one_key from ckey_parent st0)") > 0); + Assert.assertTrue(sql.contains("ckey_parent myt0")); + Assert.assertTrue(sql.contains("(myt0.one_key) in (select st0.one_key from ckey_parent st0)")); } + + @Test + public void testNotExists() { + + ResetBasicData.reset(); + + Query sq = Ebean.createQuery(CKeyParent.class) + .select("id.oneKey").alias("st0") + .setAutofetch(false).where().query(); + + Query pq = Ebean.find(CKeyParent.class).alias("myt0").where().notIn("id.oneKey", sq).query(); + + pq.findList(); + + String sql = pq.getGeneratedSql(); + + // Without alias command is should be: + // select t0.one_key c0, t0.two_key c1, t0.name c2, t0.version c3, t0.assoc_id c4 from ckey_parent t0 where (t0 + // .one_key) in (select t0.one_key from ckey_parent t0) + // but with alias command SQL should look like this: + // select myt0.one_key c0, myt0.two_key c1, myt0.name c2, myt0.version c3, myt0.assoc_id c4 from ckey_parent myt0 + // where (myt0.one_key) in (select st0.one_key from ckey_parent st0) + + Assert.assertTrue(sql.contains("ckey_parent myt0")); + Assert.assertTrue(sql.contains("(myt0.one_key) not in (select st0.one_key from ckey_parent st0)")); + } + }