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
The following commit(s) were added to refs/heads/master by this push:
new c7d1818 [CALCITE-4748] If there are duplicate GROUPING SETS, Calcite
should return duplicate rows (NobiGo)
c7d1818 is described below
commit c7d1818763b7433f4ee2f47cc1536180ed5124fb
Author: NobiGo <[email protected]>
AuthorDate: Mon Jul 12 21:02:58 2021 +0800
[CALCITE-4748] If there are duplicate GROUPING SETS, Calcite should return
duplicate rows (NobiGo)
Close apache/calcite#2503
---
.../java/org/apache/calcite/tools/RelBuilder.java | 25 +++-
.../org/apache/calcite/test/RelBuilderTest.java | 12 +-
.../apache/calcite/test/SqlToRelConverterTest.java | 22 ++-
.../apache/calcite/test/SqlToRelConverterTest.xml | 91 +++++++++---
core/src/test/resources/sql/agg.iq | 162 +++++++++++++++++++++
5 files changed, 284 insertions(+), 28 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 6ab8e23..40268b4 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -1934,8 +1934,9 @@ public class RelBuilder {
final ImmutableSortedMultiset<ImmutableBitSet> groupSetMultiset =
ImmutableSortedMultiset.copyOf(ImmutableBitSet.COMPARATOR,
groupSetList);
- if (Iterables.any(aggCalls, RelBuilder::isGroupId)) {
- return rewriteAggregateWithGroupId(groupSet, groupSetMultiset,
+ if (Iterables.any(aggCalls, RelBuilder::isGroupId)
+ || !ImmutableBitSet.ORDERING.isStrictlyOrdered(groupSetMultiset)) {
+ return rewriteAggregateWithDuplicateGroupSets(groupSet,
groupSetMultiset,
ImmutableList.copyOf(aggCalls));
}
groupSets = ImmutableList.copyOf(groupSetMultiset.elementSet());
@@ -2093,14 +2094,26 @@ public class RelBuilder {
* flatten, sorting, redundancy removal), this information is lost in
RelNode.
* Therefore, it is impossible to implement the function in runtime.
*
- * <p>To fill this gap, an aggregation query that contains {@code GROUP_ID()}
- * function will generally be rewritten into UNION when converting to
RelNode.
+ * <p>To fill this gap, an aggregation query that contains duplicate group
+ * sets is rewritten into a Union of Aggregate operators whose group sets are
+ * distinct. The number of inputs to the Union is equal to the maximum number
+ * of duplicates. In the {@code N}th input to the Union, calls to the
+ * {@code GROUP_ID} aggregate function are replaced by the integer literal
+ * {@code N}.
+ *
+ * <p>This method also handles the case where group sets are distinct but
+ * there is a call to {@code GROUP_ID}. That call is replaced by the integer
+ * literal {@code 0}.
*
* <p>Also see the discussion in
* <a
href="https://issues.apache.org/jira/browse/CALCITE-1824">[CALCITE-1824]
- * GROUP_ID returns wrong result</a>.
+ * GROUP_ID returns wrong result</a> and
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-4748">[CALCITE-4748]
+ * If there are duplicate GROUPING SETS, Calcite should return duplicate
+ * rows</a>.
*/
- private RelBuilder rewriteAggregateWithGroupId(ImmutableBitSet groupSet,
+ private RelBuilder rewriteAggregateWithDuplicateGroupSets(
+ ImmutableBitSet groupSet,
ImmutableSortedMultiset<ImmutableBitSet> groupSets,
List<AggCall> aggregateCalls) {
final List<String> fieldNamesIfNoRewrite =
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index b8c1a52..9656adb 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -1675,7 +1675,10 @@ public class RelBuilderTest {
}
}
- @Test void testAggregateGroupingSetDuplicateIgnored() {
+ /** Tests that, if you try to create an Aggregate with duplicate grouping
+ * sets, RelBuilder creates a Union. Each branch of the Union has an
+ * Aggregate that has distinct grouping sets. */
+ @Test void testAggregateGroupingSetDuplicate() {
final RelBuilder builder = RelBuilder.create(config().build());
RelNode root =
builder.scan("EMP")
@@ -1687,8 +1690,11 @@ public class RelBuilderTest {
ImmutableBitSet.of(7))))
.build();
final String expected = ""
- + "LogicalAggregate(group=[{6, 7}], groups=[[{6}, {7}]])\n"
- + " LogicalTableScan(table=[[scott, EMP]])\n";
+ + "LogicalUnion(all=[true])\n"
+ + " LogicalAggregate(group=[{6, 7}], groups=[[{6}, {7}]])\n"
+ + " LogicalTableScan(table=[[scott, EMP]])\n"
+ + " LogicalAggregate(group=[{6, 7}], groups=[[{7}]])\n"
+ + " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(root, hasTree(expected));
}
diff --git
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index b488522..11bad99 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -557,7 +557,27 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
@Test void testGroupingSetsRepeated() {
final String sql = "select deptno, group_id()\n"
+ "from emp\n"
- + "group by grouping sets (deptno, (), deptno)";
+ + "group by grouping sets (deptno, (), job, (deptno, job), deptno,\n"
+ + " job, deptno)";
+ sql(sql).ok();
+ }
+
+ /** As {@link #testGroupingSetsRepeated()} but with no {@code GROUP_ID}
+ * function. (We still need the plan to contain a Union.) */
+ @Test void testGroupingSetsRepeatedNoGroupId() {
+ final String sql = "select deptno, job\n"
+ + "from emp\n"
+ + "group by grouping sets (deptno, (), job, (deptno, job), deptno,\n"
+ + " job, deptno)";
+ sql(sql).ok();
+ }
+
+ /** As {@link #testGroupingSetsRepeated()} but grouping sets are distinct.
+ * The {@code GROUP_ID} is replaced by 0.*/
+ @Test void testGroupingSetsWithGroupId() {
+ final String sql = "select deptno, group_id()\n"
+ + "from emp\n"
+ + "group by grouping sets (deptno, (), job)";
sql(sql).ok();
}
diff --git
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 1c5c977..8c12af1 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -682,7 +682,7 @@ LogicalProject(NAME=[$0])
</Resource>
<Resource name="sql">
<![CDATA[select e.deptno,
- (select * from lateral table(DEDUP(e.deptno, e.deptno)))
+ (select * from table(DEDUP(e.deptno, e.deptno)))
from emp e]]>
</Resource>
</TestCase>
@@ -1326,9 +1326,13 @@ group by sal,
<Resource name="plan">
<![CDATA[
LogicalProject(EXPR$0=[$3])
- LogicalAggregate(group=[{0, 1, 2}], groups=[[{0, 1, 2}, {0, 1}, {0, 2}]],
EXPR$0=[SUM($0)])
- LogicalProject(SAL=[$5], DEPTNO=[$7], ENAME=[$1])
- LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalUnion(all=[true])
+ LogicalAggregate(group=[{0, 1, 2}], groups=[[{0, 1, 2}, {0, 1}, {0, 2}]],
EXPR$0=[SUM($0)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], ENAME=[$1])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1, 2}], groups=[[{0, 2}]], EXPR$0=[SUM($0)])
+ LogicalProject(SAL=[$5], DEPTNO=[$7], ENAME=[$1])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
@@ -2020,19 +2024,47 @@ LogicalProject(EXPR$0=[1])
<Resource name="sql">
<![CDATA[select deptno, group_id()
from emp
-group by grouping sets (deptno, (), deptno)]]>
+group by grouping sets (deptno, (), job, (deptno, job), deptno,
+ job, deptno)]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(DEPTNO=[$0], EXPR$1=[$2])
+ LogicalUnion(all=[true])
+ LogicalProject(DEPTNO=[$0], JOB=[$1], EXPR$1=[0:BIGINT])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(DEPTNO=[$0], JOB=[$1], EXPR$1=[1:BIGINT])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0}, {1}]])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(DEPTNO=[$0], JOB=[$1], EXPR$1=[2:BIGINT])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0}]])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testGroupingSetsRepeatedNoGroupId">
+ <Resource name="sql">
+ <![CDATA[select deptno, job
+from emp
+group by grouping sets (deptno, (), job, (deptno, job), deptno,
+ job, deptno)]]>
</Resource>
<Resource name="plan">
<![CDATA[
LogicalUnion(all=[true])
- LogicalProject(DEPTNO=[$0], EXPR$1=[0:BIGINT])
- LogicalAggregate(group=[{0}], groups=[[{0}, {}]])
- LogicalProject(DEPTNO=[$7])
- LogicalTableScan(table=[[CATALOG, SALES, EMP]])
- LogicalProject(DEPTNO=[$0], EXPR$1=[1:BIGINT])
- LogicalAggregate(group=[{0}])
- LogicalProject(DEPTNO=[$7])
- LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0}, {1}]])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0}]])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
@@ -2059,8 +2091,27 @@ order by 2]]>
<Resource name="plan">
<![CDATA[
LogicalSort(sort0=[$1], dir0=[ASC])
- LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]],
EXPR$2=[SUM($2)])
- LogicalProject(DEPTNO=[$7], ENAME=[$1], SAL=[$5])
+ LogicalUnion(all=[true])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]],
EXPR$2=[SUM($2)])
+ LogicalProject(DEPTNO=[$7], ENAME=[$1], SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0}]], EXPR$2=[SUM($2)])
+ LogicalProject(DEPTNO=[$7], ENAME=[$1], SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testGroupingSetsWithGroupId">
+ <Resource name="sql">
+ <![CDATA[select deptno, group_id()
+from emp
+group by grouping sets (deptno, (), job)]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(DEPTNO=[$0], EXPR$1=[0:BIGINT])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0}, {1}, {}]])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
@@ -2113,9 +2164,13 @@ order by 2]]>
<Resource name="plan">
<![CDATA[
LogicalSort(sort0=[$1], dir0=[ASC])
- LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]],
EXPR$2=[SUM($2)])
- LogicalProject(DEPTNO=[$7], ENAME=[$1], SAL=[$5])
- LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalUnion(all=[true])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]],
EXPR$2=[SUM($2)])
+ LogicalProject(DEPTNO=[$7], ENAME=[$1], SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], groups=[[{}]], EXPR$2=[SUM($2)])
+ LogicalProject(DEPTNO=[$7], ENAME=[$1], SAL=[$5])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
diff --git a/core/src/test/resources/sql/agg.iq
b/core/src/test/resources/sql/agg.iq
index 0401e2a..c3eb0a4 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -1027,6 +1027,168 @@ select deptno
!ok
+# There are duplicate GROUPING SETS
+select deptno, sum(sal) as s
+from "scott".emp as t
+group by grouping sets (deptno, deptno);
++--------+----------+
+| DEPTNO | S |
++--------+----------+
+| 10 | 8750.00 |
+| 10 | 8750.00 |
+| 20 | 10875.00 |
+| 20 | 10875.00 |
+| 30 | 9400.00 |
+| 30 | 9400.00 |
++--------+----------+
+(6 rows)
+
+!ok
+
+# Similar, not duplicate GROUPING SETS
+select deptno, sum(sal) as s
+from "scott".emp as t
+group by grouping sets (deptno);
++--------+----------+
+| DEPTNO | S |
++--------+----------+
+| 10 | 8750.00 |
+| 20 | 10875.00 |
+| 30 | 9400.00 |
++--------+----------+
+(3 rows)
+
+!ok
+
+# Complex GROUPING SETS clause that contains duplicates
+select sum(sal) as s
+from "scott".emp as t
+group by job,
+ grouping sets ( deptno,
+ grouping sets ( (deptno, comm is null), comm is null),
+ (comm is null)),
+ ();
++---------+
+| S |
++---------+
+| 1300.00 |
+| 1300.00 |
+| 2450.00 |
+| 2450.00 |
+| 2850.00 |
+| 2850.00 |
+| 2975.00 |
+| 2975.00 |
+| 5000.00 |
+| 5000.00 |
+| 5000.00 |
+| 5000.00 |
+| 6000.00 |
+| 950.00 |
+| 950.00 |
+| 1900.00 |
+| 1900.00 |
+| 4150.00 |
+| 4150.00 |
+| 5600.00 |
+| 5600.00 |
+| 5600.00 |
+| 5600.00 |
+| 6000.00 |
+| 6000.00 |
+| 6000.00 |
+| 8275.00 |
+| 8275.00 |
++---------+
+(28 rows)
+
+!ok
+
+# Equivalent query using flat GROUPING SETS
+select sum(sal) as s
+from "scott".emp
+group by grouping sets ((job, deptno, comm is null),
+ (job, deptno), (job, comm is null), (job, comm is null));
++---------+
+| S |
++---------+
+| 1300.00 |
+| 1300.00 |
+| 2450.00 |
+| 2450.00 |
+| 2850.00 |
+| 2850.00 |
+| 2975.00 |
+| 2975.00 |
+| 5000.00 |
+| 5000.00 |
+| 5000.00 |
+| 5000.00 |
+| 6000.00 |
+| 950.00 |
+| 950.00 |
+| 1900.00 |
+| 1900.00 |
+| 4150.00 |
+| 4150.00 |
+| 5600.00 |
+| 5600.00 |
+| 5600.00 |
+| 5600.00 |
+| 6000.00 |
+| 6000.00 |
+| 6000.00 |
+| 8275.00 |
+| 8275.00 |
++---------+
+(28 rows)
+
+!ok
+
+# Equivalent query, but with GROUP_ID and GROUPING_ID
+select sum(sal) as s,
+ grouping_id(job, deptno, comm is null) as g,
+ group_id() as i
+from "scott".emp
+group by grouping sets ((job, deptno, comm is null),
+ (job, deptno), (job, comm is null), (job, comm is null))
+order by g, i, s desc;
++---------+---+---+
+| S | G | I |
++---------+---+---+
+| 6000.00 | 0 | 0 |
+| 5600.00 | 0 | 0 |
+| 5000.00 | 0 | 0 |
+| 2975.00 | 0 | 0 |
+| 2850.00 | 0 | 0 |
+| 2450.00 | 0 | 0 |
+| 1900.00 | 0 | 0 |
+| 1300.00 | 0 | 0 |
+| 950.00 | 0 | 0 |
+| 8275.00 | 0 | 1 |
+| 6000.00 | 0 | 1 |
+| 5600.00 | 0 | 1 |
+| 5000.00 | 0 | 1 |
+| 4150.00 | 0 | 1 |
+| 6000.00 | 1 | 0 |
+| 5600.00 | 1 | 0 |
+| 5000.00 | 1 | 0 |
+| 2975.00 | 1 | 0 |
+| 2850.00 | 1 | 0 |
+| 2450.00 | 1 | 0 |
+| 1900.00 | 1 | 0 |
+| 1300.00 | 1 | 0 |
+| 950.00 | 1 | 0 |
+| 8275.00 | 2 | 0 |
+| 6000.00 | 2 | 0 |
+| 5600.00 | 2 | 0 |
+| 5000.00 | 2 | 0 |
+| 4150.00 | 2 | 0 |
++---------+---+---+
+(28 rows)
+
+!ok
+
# [KYLIN-751] Max on negative double values is not working
# [CALCITE-735] Primitive.DOUBLE.min should be large and negative
select max(v) as x, min(v) as n