diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index ded6c976f..3c6db789e 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -532,12 +532,24 @@ public interface SpiQuery extends Query { * Return the index of the first row to return in the query. */ public int getFirstRow(); + + /** + * Internally set by Ebean when this query must use the DISTINCT keyword. + *

+ * This does not exclude/remove the use of the id property. + */ + public Query setSqlDistinct(boolean sqlDistinct); /** - * return true if this query uses DISTINCT. + * Return true if this query has been specified by a user or internally by Ebean to use DISTINCT. + */ + public boolean isDistinctQuery(); + + /** + * Return true if this query has been specified by a user to use DISTINCT. */ public boolean isDistinct(); - + /** * Set default select clauses where none have been explicitly defined. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index 49b86f295..fac7b0c7f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1112,7 +1112,7 @@ public final class DefaultServer implements SpiEbeanServer { // predicates on *ToMany properties if (query.initManyWhereJoins()) { // we need a sql distinct now - query.setDistinct(true); + query.setSqlDistinct(true); } boolean allowOneManyFetch = true; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index 07f269ae0..4ff0371e7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -144,7 +144,7 @@ public class CQueryBuilder implements Constants { String sqlSelect = "select count(*)"; if (hasMany) { // need to count distinct id's ... - query.setDistinct(true); + query.setSqlDistinct(true); sqlSelect = null; } @@ -320,13 +320,13 @@ public class CQueryBuilder implements Constants { if (!useSqlLimiter) { sb.append("select "); - if (query.isDistinct()) { + if (query.isDistinctQuery()) { sb.append("distinct "); } } sb.append(select.getSelectSql()); - if (query.isDistinct() && dbOrderBy != null) { + if (query.isDistinctQuery() && dbOrderBy != null) { // add the orderby columns to the select clause (due to distinct) sb.append(", ").append(convertDbOrderByForSelect(dbOrderBy)); } 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 d1ae6cfc1..95bc00e59 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -258,7 +258,8 @@ public class SqlTreeBuilder { // Optional many property for lazy loading query BeanPropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty(); - return new SqlTreeNodeRoot(desc, props, myList, !subQuery, includeJoin, lazyLoadMany); + boolean withId = !subQuery && (query == null || !query.isDistinct()); + return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany); } else if (prop instanceof BeanPropertyAssocMany) { return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany) prop, props, myList); @@ -312,7 +313,7 @@ public class SqlTreeBuilder { // as we are now going to join to the many then we need // to add the distinct to the sql query to stop duplicate // rows... - query.setDistinct(true); + query.setSqlDistinct(true); } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java index 080cf2dc7..75c9a8f54 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -194,7 +194,7 @@ public class SqlTreeNodeBean implements SqlTreeNode { Mode queryMode = ctx.getQueryMode(); - PersistenceContext persistenceContext = ctx.getPersistenceContext(); + PersistenceContext persistenceContext = !readId ? null : ctx.getPersistenceContext(); Object id = null; if (!readId) { @@ -274,8 +274,9 @@ public class SqlTreeNodeBean implements SqlTreeNode { } else if (localBean != null) { ctx.setCurrentPrefix(prefix, pathMap); - createListProxies(localDesc, ctx, localBean); - + if (readId) { + createListProxies(localDesc, ctx, localBean); + } localDesc.postLoad(localBean, null); if (localBean instanceof EntityBean) { @@ -290,7 +291,10 @@ public class SqlTreeNodeBean implements SqlTreeNode { } if (partialObject) { - ctx.register(null, ebi); + if (readId) { + // register for lazy loading + ctx.register(null, ebi); + } } else { ebi.setFullyLoadedBean(true); } 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 e914676df..41b497550 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -128,11 +128,16 @@ public class DefaultOrmQuery implements SpiQuery { private String lazyLoadProperty; private String lazyLoadManyPath; - + + /** + * Set to true by a user wanting a DISTINCT query (id property must be excluded). + */ + private boolean distinct; + /** - * Set to true if you want a DISTINCT query. + * Set to true internally by Ebean when it needs the DISTINCT keyword added to the query (id property still expected). */ - private boolean distinct; + private boolean sqlDistinct; /** * Set to true if this is a future fetch using background threads. @@ -431,7 +436,8 @@ public class DefaultOrmQuery implements SpiQuery { copy.query = query; copy.additionalWhere = additionalWhere; copy.additionalHaving = additionalHaving; - copy.distinct = distinct; + copy.distinct = distinct; + copy.sqlDistinct = sqlDistinct; copy.timeout = timeout; copy.mapKey = mapKey; copy.id = id; @@ -658,7 +664,7 @@ public class DefaultOrmQuery implements SpiQuery { } builder.add((type == null ? 0 : type.ordinal()+1)); - builder.add(autoFetchTuned).add(distinct).add(query); + builder.add(autoFetchTuned).add(distinct).add(sqlDistinct).add(query); builder.add(firstRow).add(maxRows).add(orderBy).add(forUpdate); builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving); builder.add(mapKey); @@ -1022,19 +1028,33 @@ public class DefaultOrmQuery implements SpiQuery { return this; } + /** + * return true if user specified to use SQL DISTINCT (effectively excludes id property). + */ + public boolean isDistinct() { + return distinct; + } + + /** + * Internally set to use SQL DISTINCT on the query but still have id property included. + */ + public DefaultOrmQuery setDistinct(boolean distinct) { + this.distinct = distinct; + return this; + } /** - * return true if this query uses DISTINCT. + * Return true if this query uses SQL DISTINCT either explicitly by the user or internally defined by ebean. */ - public boolean isDistinct() { - return distinct; + public boolean isDistinctQuery() { + return distinct || sqlDistinct; } /** - * Set whether this query uses DISTINCT. + * Internally set to use SQL DISTINCT on the query but still have id property included. */ - public DefaultOrmQuery setDistinct(boolean isDistinct) { - this.distinct = isDistinct; + public DefaultOrmQuery setSqlDistinct(boolean sqlDistinct) { + this.sqlDistinct = sqlDistinct; return this; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java index 70b478666..e48817b3c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryLimitRequest.java @@ -38,7 +38,7 @@ public class OrmQueryLimitRequest implements SqlLimitRequest { } public boolean isDistinct() { - return ormQuery.isDistinct(); + return ormQuery.isDistinctQuery(); } public SpiQuery getOrmQuery() { diff --git a/src/test/java/com/avaje/tests/query/other/TestQueryDistinct.java b/src/test/java/com/avaje/tests/query/other/TestQueryDistinct.java new file mode 100644 index 000000000..72cc30fc2 --- /dev/null +++ b/src/test/java/com/avaje/tests/query/other/TestQueryDistinct.java @@ -0,0 +1,72 @@ +package com.avaje.tests.query.other; + +import java.util.List; + +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.ebean.bean.EntityBean; +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.ResetBasicData; + +public class TestQueryDistinct extends BaseTestCase { + + @Test + public void test() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .setUseCache(false) + .setDistinct(true) + .select("name"); + + List customers = query.findList(); + + String generatedSql = query.getGeneratedSql(); + Assert.assertTrue(generatedSql.contains("select distinct t0.name c0 from o_customer t0")); + + for (Customer customer : customers) { + + EntityBeanIntercept ebi = ((EntityBean)customer)._ebean_getIntercept(); + Assert.assertTrue(ebi.isDisableLazyLoad()); + Assert.assertNull(ebi.getPersistenceContext()); + + // lazy loading disabled + Assert.assertNull(customer.getId()); + Assert.assertNull(customer.getAnniversary()); + } + } + + + @Test + public void testDistinctStatus() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .setUseCache(false) + .setDistinct(true) + .select("status") + .where().isNotNull("status").query(); + + List customers = query.findList(); + + String generatedSql = query.getGeneratedSql(); + Assert.assertTrue(generatedSql.contains("select distinct t0.status c0 from o_customer t0")); + + for (Customer customer : customers) { + + Assert.assertNotNull(customer.getStatus()); + + // lazy loading disabled + Assert.assertNull(customer.getId()); + Assert.assertNull(customer.getAnniversary()); + } + } + +}