diff --git a/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java b/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java index c98837a6d..1a1519b4c 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java +++ b/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java @@ -155,4 +155,11 @@ public class ManyWhereJoins implements Serializable { aggregation = true; } + /** + * Ensure we have the join required to support the aggregation properties. + */ + public void addAggregationJoin(String property) { + this.aggregation = true; + joins.put(property, new PropertyJoin(property, SqlJoinType.INNER)); + } } 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 3029a2690..ff3597337 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -236,11 +236,17 @@ public class SqlTreeBuilder { } } + OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false); + SqlTreeProperties props = getBaseSelect(desc, queryProps); + if (prefix == null && !rawSql) { + if (props.requireSqlDistinct(manyWhereJoins)) { + query.setSqlDistinct(true); + } addManyWhereJoins(myJoinList); } - SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList); + SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList, props); if (joinList != null) { joinList.add(selectNode); } @@ -264,11 +270,7 @@ public class SqlTreeBuilder { } } - private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, List myList) { - - OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false); - - SqlTreeProperties props = getBaseSelect(desc, queryProps); + private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, List myList, SqlTreeProperties props) { if (prefix == null) { buildExtraJoins(desc, myList); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java index d18936676..35b2b37bb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java @@ -10,7 +10,7 @@ import com.avaje.ebeaninternal.server.deploy.DbSqlContext; import java.sql.SQLException; import java.util.List; -public interface SqlTreeNode { +interface SqlTreeNode { String COMMA = ", "; 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 c244f13ab..de292e64a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -27,7 +27,7 @@ import java.util.Map; /** * Normal bean included in the query. */ -public class SqlTreeNodeBean implements SqlTreeNode { +class SqlTreeNodeBean implements SqlTreeNode { private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; @@ -83,7 +83,7 @@ public class SqlTreeNodeBean implements SqlTreeNode { */ private boolean intersectionAsOfTableAlias; - private boolean aggregation; + private final boolean aggregation; /** * Construct for leaf node. @@ -127,6 +127,7 @@ public class SqlTreeNodeBean implements SqlTreeNode { this.partialObject = props.isPartialObject(); this.properties = props.getProps(); + this.aggregation = props.isAggregation(); this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[myChildren.size()]); pathMap = createPathMap(prefix, desc); @@ -447,9 +448,6 @@ public class SqlTreeNodeBean implements SqlTreeNode { for (int i = 0; i < props.length; i++) { props[i].appendSelect(ctx, subQuery); - if (props[i].isAggregation()) { - aggregation = true; - } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index 45f30b8bc..cd31bc405 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -22,7 +22,7 @@ import java.util.List; * etc in this case we must add an extra join. *

*/ -public class SqlTreeNodeExtraJoin implements SqlTreeNode { +class SqlTreeNodeExtraJoin implements SqlTreeNode { private final BeanPropertyAssoc assocBeanProperty; @@ -34,7 +34,7 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode { private List children; - public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty, boolean pathContainsMany) { + SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty, boolean pathContainsMany) { this.prefix = prefix; this.assocBeanProperty = assocBeanProperty; this.pathContainsMany = pathContainsMany; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index defe4263d..b2691c3d9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -8,11 +8,11 @@ import com.avaje.ebeaninternal.server.deploy.DbSqlContext; import java.sql.SQLException; import java.util.List; -public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { +final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { private final BeanPropertyAssocMany manyProp; - public SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany prop, SqlTreeProperties props, List myList, boolean disableLazyLoad) { + SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany prop, SqlTreeProperties props, List myList, boolean disableLazyLoad) { super(prefix, prop, props, myList, disableLazyLoad); this.manyProp = prop; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java index b0d7b2944..a753d5a25 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -17,7 +17,7 @@ import java.util.List; /** * Join to Many (or child of a many) to support where clause predicates on many properties. */ -public class SqlTreeNodeManyWhereJoin implements SqlTreeNode { +class SqlTreeNodeManyWhereJoin implements SqlTreeNode { private final String parentPrefix; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java index f3f027bce..a0b386b81 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -11,7 +11,7 @@ import java.util.List; /** * Represents the root node of the Sql Tree. */ -public final class SqlTreeNodeRoot extends SqlTreeNodeBean { +final class SqlTreeNodeRoot extends SqlTreeNodeBean { private final TableJoin includeJoin; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java index 2fb593a46..8b519f851 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.deploy.TableJoin; @@ -33,6 +34,8 @@ public class SqlTreeProperties { private boolean allProperties; + private boolean aggregation; + public SqlTreeProperties() { } @@ -80,4 +83,41 @@ public class SqlTreeProperties { this.allProperties = true; } + /** + * Check for an aggregation property and set manyWhereJoin as needed. + *

+ * Return true if a Sql distinct is required. + *

+ */ + public boolean requireSqlDistinct(ManyWhereJoins manyWhereJoins) { + String joinProperty = aggregationJoin(); + if (joinProperty != null) { + aggregation = true; + manyWhereJoins.addAggregationJoin(joinProperty); + return false; + } else{ + return manyWhereJoins.requireSqlDistinct(); + } + } + + /** + * Return true if this contains an aggregation property. + */ + public boolean isAggregation() { + return aggregation; + } + + /** + * Return the property to join for aggregation. + */ + private String aggregationJoin() { + if (!allProperties) { + for (BeanProperty beanProperty : propsList) { + if (beanProperty.isAggregation()) { + return beanProperty.getElPrefix(); + } + } + } + return null; + } } \ No newline at end of file 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 d1237712d..8ffb97800 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -373,9 +373,6 @@ public class DefaultOrmQuery implements SpiQuery { if (havingExpressions != null) { havingExpressions.containsMany(beanDescriptor, manyWhereJoins); } - if (manyWhereJoins.requireSqlDistinct()) { - setSqlDistinct(true); - } } /** diff --git a/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java b/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java index f381bd20e..525dcb169 100644 --- a/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java +++ b/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestAggregationCount extends BaseTestCase { @Test - public void test() { + public void testFull() { TEventOne one = new TEventOne("first"); one.getLogs().add(new TEventMany("all", 1, 10)); @@ -28,8 +28,7 @@ public class TestAggregationCount extends BaseTestCase { two.getLogs().add(new TEventMany("alf", 30, 13)); Ebean.save(two); - - Query query = Ebean.find(TEventOne.class) + Query query2 = Ebean.find(TEventOne.class) .select("name, count, totalUnits, totalAmount") .where() .startsWith("logs.description", "a") @@ -37,14 +36,14 @@ public class TestAggregationCount extends BaseTestCase { .ge("count", 1) .orderBy().asc("name"); - List list = query.findList(); + List list = query2.findList(); for (TEventOne eventOne : list) { System.out.println(eventOne.getId() + " " + eventOne.getName() + " count:" + eventOne.getCount() + " units:" + eventOne.getTotalUnits() + " amount:" + eventOne.getTotalAmount()); } assertThat(list).isNotEmpty(); - String sql = query.getGeneratedSql(); + String sql = query2.getGeneratedSql(); assertThat(sql).contains("select t0.id c0, t0.name c1, count(u1.*) c2, sum(u1.units) c3, sum(u1.units * u1.amount) c4 from tevent_one t0"); assertThat(sql).contains("from tevent_one t0 join tevent_many u1 on u1.event_id = t0.id "); assertThat(sql).contains("where u1.description like ? "); @@ -52,4 +51,39 @@ public class TestAggregationCount extends BaseTestCase { } + @Test + public void testSelectOnly() { + + Query query0 = Ebean.find(TEventOne.class) + .select("name, count, totalUnits, totalAmount"); + + query0.findList(); + assertThat(query0.getGeneratedSql()).contains("select t0.id c0, t0.name c1, count(u1.*) c2, sum(u1.units) c3, sum(u1.units * u1.amount) c4 from tevent_one t0"); + assertThat(query0.getGeneratedSql()).contains("group by t0.id, t0.name"); + } + + @Test + public void testSelectWhere() { + + Query query0 = Ebean.find(TEventOne.class) + .select("name, count, totalUnits, totalAmount") + .where().gt("logs.description", "a").query(); + + query0.findList(); + assertThat(query0.getGeneratedSql()).contains("select t0.id c0, t0.name c1, count(u1.*) c2, sum(u1.units) c3, sum(u1.units * u1.amount) c4 from tevent_one t0"); + assertThat(query0.getGeneratedSql()).contains("group by t0.id, t0.name"); + } + + @Test + public void testSelectHavingOrderBy() { + + Query query1 = Ebean.find(TEventOne.class) + .select("name, count, totalUnits, totalAmount") + .having().ge("count", 1) + .orderBy().asc("name"); + + query1.findList(); + assertThat(query1.getGeneratedSql()).contains("having count(u1.*) >= ? order by t0.name"); + } + }