From f78f4ea64a1d2ae9a0c85536fc518059ba230f0c Mon Sep 17 00:00:00 2001 From: Ryszard-Trojnacki Date: Wed, 18 Mar 2015 10:28:54 +0100 Subject: [PATCH 1/2] Added ability to set alias for root table of sql query. --- src/main/java/com/avaje/ebean/Query.java | 5 +++ .../com/avaje/ebeaninternal/api/SpiQuery.java | 5 +++ .../server/query/SqlTreeBuilder.java | 2 +- .../server/querydefn/DefaultOrmQuery.java | 17 +++++++++ .../com/avaje/tests/query/TestQueryAlias.java | 35 +++++++++++++++++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/avaje/tests/query/TestQueryAlias.java diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java index 5ab23a0e9..9315cf790 100644 --- a/src/main/java/com/avaje/ebean/Query.java +++ b/src/main/java/com/avaje/ebean/Query.java @@ -1230,4 +1230,9 @@ public interface Query extends Serializable { * Return true if this query has forUpdate set. */ public boolean isForUpdate(); + + /** + * Set root table alias. + */ + public Query alias(String alias); } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index a998d4956..7ae485ee6 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -611,4 +611,9 @@ public interface SpiQuery extends Query { * Return true if this query has been cancelled. */ public boolean isCancelled(); + + /** + * Return root table alias set by {@link #alias(String)} command. + */ + public String getAlias(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java index c70070d49..a9d3cd6fb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -104,7 +104,7 @@ public class SqlTreeBuilder { this.queryDetail = query.getDetail(); this.predicates = predicates; - this.alias = new SqlTreeAlias(request.getBeanDescriptor().getBaseTableAlias()); + this.alias = new SqlTreeAlias(request.getQuery().getAlias()==null?request.getBeanDescriptor().getBaseTableAlias():request.getQuery().getAlias()); this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index e31691929..ed042b0fb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -190,6 +190,11 @@ public class DefaultOrmQuery implements SpiQuery { private boolean autoFetchTuned; private boolean logSecondaryQuery; + + /** + * Root table alias. For {@link Query#alias(String)} command. + */ + private String rootTableAlias=null; /** * The node of the bean or collection that fired lazy loading. Not null if profiling is on and @@ -680,6 +685,7 @@ public class DefaultOrmQuery implements SpiQuery { builder.add(id != null); builder.add(rawSql == null ? 0 : rawSql.queryHash()); builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); + builder.add(rootTableAlias); if (detail != null) { detail.queryPlanHash(request, builder); @@ -1284,6 +1290,17 @@ public class DefaultOrmQuery implements SpiQuery { this.cancelableQuery = cancelableQuery; } } + + @Override + public Query alias(String alias) { + this.rootTableAlias=alias; + return this; + } + + @Override + public String getAlias() { + return rootTableAlias; + } public void cancel() { synchronized (this) { diff --git a/src/test/java/com/avaje/tests/query/TestQueryAlias.java b/src/test/java/com/avaje/tests/query/TestQueryAlias.java new file mode 100644 index 000000000..04e21be76 --- /dev/null +++ b/src/test/java/com/avaje/tests/query/TestQueryAlias.java @@ -0,0 +1,35 @@ +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.CKeyParent; +import com.avaje.tests.model.basic.ResetBasicData; + +public class TestQueryAlias extends BaseTestCase { + @Test + public void testExists() { + 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().in("id.oneKey", sq).query(); + + pq.findList(); + + 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) + // 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.indexOf("ckey_parent myt0")>0); + Assert.assertTrue(sql.indexOf("in (select st0.one_key from ckey_parent st0)")>0); + } +} From 2e03452e2eb2016da76da337a99d24ae2c54eeed Mon Sep 17 00:00:00 2001 From: Ryszard-Trojnacki Date: Thu, 19 Mar 2015 10:43:12 +0100 Subject: [PATCH 2/2] 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); + } +}