This is an automated email from the ASF dual-hosted git repository. jhyde pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 37b8cdbb381369e773229a81ae69ab5c1df34f3e Author: Julian Hyde <[email protected]> AuthorDate: Tue Aug 4 21:55:47 2020 -0700 [CALCITE-3957] AggregateMergeRule should merge SUM0 into COUNT even if GROUP BY is empty Close apache/calcite#2097 --- .../calcite/rel/rules/AggregateMergeRule.java | 4 ++- .../rel/rules/ProjectAggregateMergeRule.java | 4 ++- .../calcite/sql/SqlSplittableAggFunction.java | 3 ++- .../org/apache/calcite/test/RelOptRulesTest.java | 30 +++++++++++++++++----- .../org/apache/calcite/test/RelOptRulesTest.xml | 22 ++++++++++++++++ 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java index fa063fd..4b8b176 100644 --- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java +++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateMergeRule.java @@ -22,6 +22,7 @@ import org.apache.calcite.plan.RelRule; import org.apache.calcite.rel.core.Aggregate; import org.apache.calcite.rel.core.Aggregate.Group; import org.apache.calcite.rel.core.AggregateCall; +import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlSplittableAggFunction; import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.tools.RelBuilderFactory; @@ -99,7 +100,7 @@ public class AggregateMergeRule } boolean hasEmptyGroup = topAgg.getGroupSets() - .stream().anyMatch(n -> n.isEmpty()); + .stream().anyMatch(ImmutableBitSet::isEmpty); final List<AggregateCall> finalCalls = new ArrayList<>(); for (AggregateCall topCall : topAgg.getAggCallList()) { @@ -120,6 +121,7 @@ public class AggregateMergeRule // 0, which is wrong. if (!isAggregateSupported(bottomCall) || (bottomCall.getAggregation() == SqlStdOperatorTable.COUNT + && topCall.getAggregation().getKind() != SqlKind.SUM0 && hasEmptyGroup)) { return; } diff --git a/core/src/main/java/org/apache/calcite/rel/rules/ProjectAggregateMergeRule.java b/core/src/main/java/org/apache/calcite/rel/rules/ProjectAggregateMergeRule.java index 33207f6..67e0928 100644 --- a/core/src/main/java/org/apache/calcite/rel/rules/ProjectAggregateMergeRule.java +++ b/core/src/main/java/org/apache/calcite/rel/rules/ProjectAggregateMergeRule.java @@ -47,7 +47,9 @@ import java.util.concurrent.atomic.AtomicInteger; * Planner rule that matches a {@link Project} on a {@link Aggregate} * and projects away aggregate calls that are not used. * - * <p>Also converts {@code NVL(SUM(x), 0)} to {@code SUM0(x)}. + * <p>Also converts {@code COALESCE(SUM(x), 0)} to {@code SUM0(x)}. + * This transformation is useful because there are cases where + * {@link AggregateMergeRule} can merge {@code SUM0} but not {@code SUM}. * * @see CoreRules#PROJECT_AGGREGATE_MERGE */ diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java b/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java index 26e67e5..9e213bd 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlSplittableAggFunction.java @@ -198,7 +198,8 @@ public interface SqlSplittableAggFunction { public AggregateCall merge(AggregateCall top, AggregateCall bottom) { if (bottom.getAggregation().getKind() == SqlKind.COUNT - && top.getAggregation().getKind() == SqlKind.SUM) { + && (top.getAggregation().getKind() == SqlKind.SUM + || top.getAggregation().getKind() == SqlKind.SUM0)) { return AggregateCall.create(bottom.getAggregation(), bottom.isDistinct(), bottom.isApproximate(), false, bottom.getArgList(), bottom.filterArg, bottom.getCollation(), diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java index e35d0dc..8ddcc50 100644 --- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java +++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java @@ -1825,7 +1825,7 @@ class RelOptRulesTest extends RelOptTestBase { /** Test case for * <a href="https://issues.apache.org/jira/browse/CALCITE-2343">[CALCITE-2343] * Should not push over whose columns are all from left child past join since - * join will affect row count.</a>. */ + * join will affect row count</a>. */ @Test void testPushProjectWithOverPastJoin1() { final String sql = "select e.sal + b.comm,\n" + "count(e.empno) over (partition by e.deptno)\n" @@ -4731,11 +4731,11 @@ class RelOptRulesTest extends RelOptTestBase { /** Test case for * <a href="https://issues.apache.org/jira/browse/CALCITE-2249">[CALCITE-2249] - * AggregateJoinTransposeRule generates inequivalent nodes if Aggregate relNode contains - * distinct aggregate function.</a>. */ + * AggregateJoinTransposeRule generates non-equivalent nodes if Aggregate + * contains DISTINCT aggregate function</a>. */ @Test void testPushDistinctAggregateIntoJoin() { - final String sql = - "select count(distinct sal) from sales.emp join sales.dept on job = name"; + final String sql = "select count(distinct sal) from sales.emp\n" + + " join sales.dept on job = name"; sql(sql).withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED) .checkUnchanged(); } @@ -4893,6 +4893,24 @@ class RelOptRulesTest extends RelOptTestBase { .checkUnchanged(); } + /** Test case for + * <a href="https://issues.apache.org/jira/browse/CALCITE-3957">[CALCITE-3957] + * AggregateMergeRule should merge SUM0 into COUNT even if GROUP BY is + * empty</a>. (It is not valid to merge a SUM onto a SUM0 if the top GROUP BY + * is empty.) */ + @Test void testAggregateMergeSum0() { + final String sql = "select coalesce(sum(count_comm), 0)\n" + + "from (\n" + + " select deptno, count(comm) as count_comm\n" + + " from sales.emp\n" + + " group by deptno, mgr) t"; + sql(sql) + .withPreRule(CoreRules.PROJECT_AGGREGATE_MERGE, + CoreRules.AGGREGATE_PROJECT_MERGE) + .withRule(CoreRules.AGGREGATE_MERGE) + .check(); + } + /** * Test case for AggregateMergeRule, should not merge 2 aggregates * into a single aggregate, since top agg contains empty grouping set, @@ -5027,7 +5045,7 @@ class RelOptRulesTest extends RelOptTestBase { /** Test case for * <a href="https://issues.apache.org/jira/browse/CALCITE-2712">[CALCITE-2712] * Should remove the left join since the aggregate has no call and - * only uses column in the left input of the bottom join as group key.</a>. */ + * only uses column in the left input of the bottom join as group key</a>. */ @Test void testAggregateJoinRemove1() { final String sql = "select distinct e.deptno from sales.emp e\n" + "left outer join sales.dept d on e.deptno = d.deptno"; diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml index 071d0f8..86f6a44 100644 --- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml +++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml @@ -545,6 +545,28 @@ LogicalProject(EXPR$0=[1]) ]]> </Resource> </TestCase> + <TestCase name="testAggregateMergeSum0"> + <Resource name="sql"> + <![CDATA[select coalesce(sum(count_comm), 0) +from ( + select deptno, count(comm) as count_comm + from sales.emp + group by deptno, mgr) t]]> + </Resource> + <Resource name="planBefore"> + <![CDATA[ +LogicalAggregate(group=[{}], agg#0=[$SUM0($2)]) + LogicalAggregate(group=[{3, 7}], COUNT_COMM=[COUNT()]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + </Resource> + <Resource name="planAfter"> + <![CDATA[ +LogicalAggregate(group=[{}], agg#0=[COUNT()]) + LogicalTableScan(table=[[CATALOG, SALES, EMP]]) +]]> + </Resource> + </TestCase> <TestCase name="testAll"> <Resource name="sql"> <