From 2e03452e2eb2016da76da337a99d24ae2c54eeed Mon Sep 17 00:00:00 2001 From: Ryszard-Trojnacki Date: Thu, 19 Mar 2015 10:43:12 +0100 Subject: [PATCH] Added SQL command "EXISTS" and "NOT EXISTS" to Ebean (ExpressionFactory, ExpressionList). --- .../com/avaje/ebean/ExpressionFactory.java | 10 +++ .../java/com/avaje/ebean/ExpressionList.java | 10 +++ .../expression/DefaultExpressionFactory.java | 17 ++++ .../server/expression/ExistsExpression.java | 87 +++++++++++++++++++ .../server/expression/JunctionExpression.java | 10 +++ .../util/DefaultExpressionList.java | 13 +++ .../avaje/tests/query/TestQueryExists.java | 41 +++++++++ 7 files changed, 188 insertions(+) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java create mode 100644 src/test/java/com/avaje/tests/query/TestQueryExists.java diff --git a/src/main/java/com/avaje/ebean/ExpressionFactory.java b/src/main/java/com/avaje/ebean/ExpressionFactory.java index 9e7e7be60..0479e3ea3 100644 --- a/src/main/java/com/avaje/ebean/ExpressionFactory.java +++ b/src/main/java/com/avaje/ebean/ExpressionFactory.java @@ -169,6 +169,16 @@ public interface ExpressionFactory { * In - property has a value in the collection of values. */ public Expression in(String propertyName, Collection values); + + /** + * Exists expression + */ + public Expression exists(Query subQuery); + + /** + * Not exists expression + */ + public Expression notExists(Query subQuery); /** * Id Equal to - ID property is equal to the value. diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index 771adbb58..93bc1692b 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -473,6 +473,16 @@ public interface ExpressionList extends Serializable { * In - property has a value in the collection of values. */ public ExpressionList in(String propertyName, Collection values); + + /** + * Exists expression + */ + public ExpressionList exists(Query subQuery); + + /** + * Not exists expression + */ + public ExpressionList notExists(Query subQuery); /** * Id IN a list of id values. 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 a66d4a230..13f6feeee 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -241,6 +241,23 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { public Expression in(String propertyName, Collection values) { return new InExpression(propertyName, values); } + + /** + * Exists subquery + */ + @Override + public Expression exists(Query subQuery) { + return new ExistsExpression((SpiQuery) subQuery, false); + } + + /** + * Not exists subquery + */ + @Override + public Expression notExists(Query subQuery) { + return new ExistsExpression((SpiQuery) subQuery, true); + } + /** * Id Equal to - ID property is equal to the value. diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java new file mode 100644 index 000000000..64537e6eb --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsExpression.java @@ -0,0 +1,87 @@ +package com.avaje.ebeaninternal.server.expression; + +import java.util.List; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.api.SpiQuery; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.query.CQuery; + +public class ExistsExpression implements SpiExpression { + + private static final long serialVersionUID = 666990277309851644L; + + private final boolean not; + private final SpiQuery subQuery; + + private transient CQuery compiledSubQuery; + + public ExistsExpression(SpiQuery subQuery, boolean not) { + this.subQuery = subQuery; + this.not=not; + } + + public void queryAutoFetchHash(HashQueryPlanBuilder builder) { + builder.add(ExistsExpression.class).add(not); + + subQuery.queryAutofetchHash(builder); + } + + public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + + // queryPlanHash executes prior to addSql() or addBindValues() + // ... so compiledQuery will exist + compiledSubQuery = compileSubQuery(request); + + queryAutoFetchHash(builder); + } + + /** + * Compile/build the sub query. + */ + private CQuery compileSubQuery(BeanQueryRequest queryRequest) { + + SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer(); + return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction()); + } + + public int queryBindHash() { + return subQuery.queryBindHash(); + } + + public void addSql(SpiExpressionRequest request) { + + String subSelect = compiledSubQuery.getGeneratedSql(); + subSelect = subSelect.replace('\n', ' '); + + + if(not) request.append(" not"); + request.append(" exists ("); + request.append(subSelect); + request.append(") "); + } + + public void addBindValues(SpiExpressionRequest request) { + + List bindParams = compiledSubQuery.getPredicates().getWhereExprBindValues(); + + if (bindParams == null) { + return; + } + + for (int i = 0; i < bindParams.size(); i++) { + request.addBindValue(bindParams.get(i)); + } + } + + @Override + public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { + // TODO Auto-generated method stub + + } +} 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 447034efd..6cd7b6e21 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -361,6 +361,16 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr public ExpressionList in(String propertyName, com.avaje.ebean.Query subQuery) { return exprList.in(propertyName, subQuery); } + + @Override + public ExpressionList exists(Query subQuery) { + return exprList.exists(subQuery); + } + + @Override + public ExpressionList notExists(Query subQuery) { + return exprList.exists(subQuery); + } @Override public ExpressionList isNotNull(String propertyName) { diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index fe7efb65f..5faaac083 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -468,6 +468,19 @@ public class DefaultExpressionList implements SpiExpressionList { add(expr.in(propertyName, values)); return this; } + + @Override + public ExpressionList exists(Query subQuery) { + add(expr.exists(subQuery)); + return this; + } + + @Override + public ExpressionList notExists(Query subQuery) { + add(expr.notExists(subQuery)); + return this; + } + @Override public ExpressionList isNotNull(String propertyName) { diff --git a/src/test/java/com/avaje/tests/query/TestQueryExists.java b/src/test/java/com/avaje/tests/query/TestQueryExists.java new file mode 100644 index 000000000..453fee06a --- /dev/null +++ b/src/test/java/com/avaje/tests/query/TestQueryExists.java @@ -0,0 +1,41 @@ +package com.avaje.tests.query; + +import org.junit.Assert; +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.ResetBasicData; + +public class TestQueryExists extends BaseTestCase { + @Test + public void testExists() { + ResetBasicData.reset(); + + Query subQuery=Ebean.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id") + .query(); + Query query=Ebean.find(Customer.class).alias("qt").where().exists(subQuery).query(); + + query.findList(); + String sql=query.getGeneratedSql(); + + Assert.assertTrue(sql.indexOf("exists (")>0); + } + + @Test + public void testNotExists() { + ResetBasicData.reset(); + + Query subQuery=Ebean.find(Order.class).alias("sq").select("id").where().raw("sq.kcustomer_id = qt.id") + .query(); + Query query=Ebean.find(Customer.class).alias("qt").where().notExists(subQuery).query(); + + query.findList(); + String sql=query.getGeneratedSql(); + + Assert.assertTrue(sql.indexOf("not exists (")>0); + } +}