This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 7c1e2746b9 [CALCITE-5089] Allow GROUP BY ALL or DISTINCT set
quantifier on GROUPING SETS
7c1e2746b9 is described below
commit 7c1e2746b94a50f0bb08571287acb5327fdc1b16
Author: hannerwang <[email protected]>
AuthorDate: Wed Apr 13 15:01:39 2022 +0800
[CALCITE-5089] Allow GROUP BY ALL or DISTINCT set quantifier on GROUPING
SETS
Close apache/calcite#2771
---
core/src/main/codegen/templates/Parser.jj | 12 +++-
.../main/java/org/apache/calcite/sql/SqlKind.java | 3 +
.../org/apache/calcite/sql/SqlSelectOperator.java | 12 +++-
.../calcite/sql/fun/SqlInternalOperators.java | 4 ++
.../apache/calcite/sql/fun/SqlRollupOperator.java | 5 ++
.../sql/validate/AggregatingSelectScope.java | 17 +++++-
.../calcite/sql/validate/SqlValidatorImpl.java | 14 ++++-
.../apache/calcite/test/SqlToRelConverterTest.java | 24 ++++++++
.../apache/calcite/test/SqlToRelConverterTest.xml | 51 ++++++++++++++++
core/src/test/resources/sql/agg.iq | 69 ++++++++++++++++++++++
site/_docs/reference.md | 7 ++-
.../apache/calcite/sql/parser/SqlParserTest.java | 23 ++++++++
12 files changed, 235 insertions(+), 6 deletions(-)
diff --git a/core/src/main/codegen/templates/Parser.jj
b/core/src/main/codegen/templates/Parser.jj
index c18f195024..0652a25d48 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -126,6 +126,7 @@ import org.apache.calcite.util.SourceStringReader;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
+import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import java.io.Reader;
@@ -2499,11 +2500,20 @@ SqlNode WhereOpt() :
SqlNodeList GroupByOpt() :
{
List<SqlNode> list = new ArrayList<SqlNode>();
+ boolean distinct = false;
final Span s;
}
{
<GROUP> { s = span(); }
- <BY> list = GroupingElementList() {
+ <BY>
+ [ <DISTINCT> { distinct = true; } | <ALL> ]
+ list = GroupingElementList() {
+ if (distinct) {
+ SqlNode groupByDistinct =
+ SqlInternalOperators.GROUP_BY_DISTINCT.createCall(
+ s.add(getPos()).pos(), list);
+ list = ImmutableList.of(groupByDistinct);
+ }
return new SqlNodeList(list, s.addAll(list).pos());
}
|
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index e73214bce0..c6697489d2 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -157,6 +157,9 @@ public enum SqlKind {
/** A dynamic parameter. */
DYNAMIC_PARAM,
+ /** The DISTINCT keyword of the GROUP BY clause. */
+ GROUP_BY_DISTINCT,
+
/**
* ORDER BY clause.
*
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
b/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
index 59025647cb..70aa0b4cf9 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java
@@ -202,10 +202,18 @@ public class SqlSelectOperator extends SqlOperator {
}
}
if (select.groupBy != null) {
- writer.sep("GROUP BY");
- final SqlNodeList groupBy =
+ SqlNodeList groupBy =
select.groupBy.size() == 0 ? SqlNodeList.SINGLETON_EMPTY
: select.groupBy;
+ // if the DISTINCT keyword of GROUP BY is present it can be the only item
+ if (groupBy.size() == 1 && groupBy.get(0) != null
+ && groupBy.get(0).getKind() == SqlKind.GROUP_BY_DISTINCT) {
+ writer.sep("GROUP BY DISTINCT");
+ List<SqlNode> operandList = ((SqlCall)
groupBy.get(0)).getOperandList();
+ groupBy = new SqlNodeList(operandList, groupBy.getParserPosition());
+ } else {
+ writer.sep("GROUP BY");
+ }
writer.list(SqlWriter.FrameTypeEnum.GROUP_BY_LIST, SqlWriter.COMMA,
groupBy);
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
index ceea8fbb1f..c3f12806f9 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
@@ -115,4 +115,8 @@ public abstract class SqlInternalOperators {
new SqlInternalOperator("SEPARATOR", SqlKind.SEPARATOR, 20, false,
ReturnTypes.ARG0, InferTypes.RETURN_TYPE, OperandTypes.ANY);
+ /** {@code DISTINCT} operator, occurs within {@code GROUP BY} clause. */
+ public static final SqlInternalOperator GROUP_BY_DISTINCT =
+ new SqlRollupOperator("GROUP BY DISTINCT", SqlKind.GROUP_BY_DISTINCT);
+
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlRollupOperator.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlRollupOperator.java
index 0b3f8b9d31..ed0007bcd7 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlRollupOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlRollupOperator.java
@@ -53,6 +53,11 @@ class SqlRollupOperator extends SqlInternalOperator {
return;
}
break;
+ case GROUP_BY_DISTINCT:
+ writer.keyword(call.getOperator().getName());
+ SqlNodeList groupBy = new SqlNodeList(call.getOperandList(),
call.getParserPosition());
+ writer.list(SqlWriter.FrameTypeEnum.GROUP_BY_LIST, SqlWriter.COMMA,
groupBy);
+ return;
default:
break;
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
b/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
index 53cc201f28..27b90195e8 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
@@ -20,6 +20,7 @@ import org.apache.calcite.linq4j.Linq4j;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlSelect;
@@ -30,6 +31,7 @@ import org.apache.calcite.util.Pair;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMultiset;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -93,8 +95,15 @@ public class AggregatingSelectScope
try {
final ImmutableList.Builder<ImmutableList<ImmutableBitSet>> builder =
ImmutableList.builder();
+ boolean groupByDistinct = false;
if (select.getGroup() != null) {
- final SqlNodeList groupList = select.getGroup();
+ SqlNodeList groupList = select.getGroup();
+ // if the DISTINCT keyword of GROUP BY is present it can be the only
item
+ if (groupList.size() == 1 && groupList.get(0).getKind() ==
SqlKind.GROUP_BY_DISTINCT) {
+ groupList = new SqlNodeList(((SqlCall)
groupList.get(0)).getOperandList(),
+ groupList.getParserPosition());
+ groupByDistinct = true;
+ }
for (SqlNode groupExpr : groupList) {
SqlValidatorUtil.analyzeGroupItem(this, groupAnalyzer, builder,
groupExpr);
@@ -111,6 +120,12 @@ public class AggregatingSelectScope
flatGroupSets.add(ImmutableBitSet.of());
}
+ if (groupByDistinct) {
+ ImmutableSet<ImmutableBitSet> sets =
ImmutableSet.copyOf(flatGroupSets);
+ flatGroupSets.clear();
+ flatGroupSets.addAll(sets);
+ }
+
return new Resolved(groupAnalyzer.extraExprs, groupAnalyzer.groupExprs,
flatGroupSets, groupAnalyzer.groupExprProjection);
} finally {
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 2978f1a700..343a453bd0 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -4182,10 +4182,16 @@ public class SqlValidatorImpl implements
SqlValidatorWithHints {
private void validateGroupByExpr(SqlNode groupByItem,
SqlValidatorScope groupByScope) {
switch (groupByItem.getKind()) {
+ case GROUP_BY_DISTINCT:
+ SqlCall call = (SqlCall) groupByItem;
+ for (SqlNode operand : call.getOperandList()) {
+ validateGroupByExpr(operand, groupByScope);
+ }
+ break;
case GROUPING_SETS:
case ROLLUP:
case CUBE:
- final SqlCall call = (SqlCall) groupByItem;
+ call = (SqlCall) groupByItem;
for (SqlNode operand : call.getOperandList()) {
validateExpr(operand, groupByScope);
}
@@ -4260,6 +4266,7 @@ public class SqlValidatorImpl implements
SqlValidatorWithHints {
// expressions, because they do not have a type.
for (SqlNode node : groupList) {
switch (node.getKind()) {
+ case GROUP_BY_DISTINCT:
case GROUPING_SETS:
case ROLLUP:
case CUBE:
@@ -4296,6 +4303,11 @@ public class SqlValidatorImpl implements
SqlValidatorWithHints {
@Nullable AggregatingSelectScope aggregatingScope,
SqlNode groupItem) {
switch (groupItem.getKind()) {
+ case GROUP_BY_DISTINCT:
+ for (SqlNode sqlNode : ((SqlCall) groupItem).getOperandList()) {
+ validateGroupItem(groupScope, aggregatingScope, sqlNode);
+ }
+ break;
case GROUPING_SETS:
case ROLLUP:
case CUBE:
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 880846127c..52893c4b94 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -4493,4 +4493,28 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
.withTrim(false)
.ok();
}
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-5089">[CALCITE-5089]
+ * Allow GROUP BY ALL or DISTINCT set quantifier on GROUPING SETS</a>. */
+ @Test void testGroupByDistinct() {
+ final String sql = "SELECT deptno, job, count(*)\n"
+ + "FROM emp\n"
+ + "GROUP BY DISTINCT\n"
+ + "CUBE (deptno, job),\n"
+ + "ROLLUP (deptno, job)";
+ sql(sql).ok();
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-5089">[CALCITE-5089]
+ * Allow GROUP BY ALL or DISTINCT set quantifier on GROUPING SETS</a>. */
+ @Test void testGroupByAll() {
+ final String sql = "SELECT deptno, job, count(*)\n"
+ + "FROM emp\n"
+ + "GROUP BY ALL\n"
+ + "CUBE (deptno, job),\n"
+ + "ROLLUP (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 3cf56aa333..7d883fbaaa 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -1839,6 +1839,41 @@ LogicalProject(D=[$0], EXPR$1=[+($0, $1)])
from emp group by d,mgr]]>
</Resource>
</TestCase>
+ <TestCase name="testGroupByAll">
+ <Resource name="sql">
+ <![CDATA[SELECT deptno, job, count(*)
+FROM emp
+GROUP BY ALL
+CUBE (deptno, job),
+ROLLUP (deptno, job)]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalUnion(all=[true])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]],
EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}]], EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}]], EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalAggregate(group=[{0, 1}], EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testGroupByCaseIn">
<Resource name="sql">
<![CDATA[select
@@ -1918,6 +1953,22 @@ group by
cube (coord_ne.sub.a, coord.x, coord."unit")]]>
</Resource>
</TestCase>
+ <TestCase name="testGroupByDistinct">
+ <Resource name="sql">
+ <![CDATA[SELECT deptno, job, count(*)
+FROM emp
+GROUP BY DISTINCT
+CUBE (deptno, job),
+ROLLUP (deptno, job)]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalAggregate(group=[{0, 1}], groups=[[{0, 1}, {0}, {1}, {}]],
EXPR$2=[COUNT()])
+ LogicalProject(DEPTNO=[$7], JOB=[$2])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testGroupByExpression">
<Resource name="sql">
<![CDATA[select count(*)
diff --git a/core/src/test/resources/sql/agg.iq
b/core/src/test/resources/sql/agg.iq
index f7688e712e..f8b56a8325 100644
--- a/core/src/test/resources/sql/agg.iq
+++ b/core/src/test/resources/sql/agg.iq
@@ -581,6 +581,75 @@ select distinct count(*) from emp group by cube(deptno,
gender);
!ok
+# CUBE and ROLLUP cartesian product over same columns
+select deptno, gender, count(*) from emp where deptno = 20 group by
cube(deptno, gender), rollup(deptno, gender);
++--------+--------+--------+
+| DEPTNO | GENDER | EXPR$2 |
++--------+--------+--------+
+| 20 | M | 1 |
+| 20 | M | 1 |
+| 20 | M | 1 |
+| 20 | M | 1 |
+| 20 | M | 1 |
+| 20 | M | 1 |
+| 20 | M | 1 |
+| 20 | | 1 |
+| 20 | | 1 |
+| 20 | | 1 |
+| | M | 1 |
+| | | 1 |
++--------+--------+--------+
+(12 rows)
+
+!ok
+
+# GROUP BY DISTINCT CUBE and ROLLUP cartesian product over same columns
+select deptno, gender, count(*) from emp where deptno = 20 group by distinct
cube(deptno, gender), rollup(deptno, gender);
++--------+--------+--------+
+| DEPTNO | GENDER | EXPR$2 |
++--------+--------+--------+
+| 20 | M | 1 |
+| 20 | | 1 |
+| | M | 1 |
+| | | 1 |
++--------+--------+--------+
+(4 rows)
+
+!ok
+
+# GROUP BY over empty columns
+select count(*) from emp where deptno = 20 group by ();
++--------+
+| EXPR$0 |
++--------+
+| 1 |
++--------+
+(1 row)
+
+!ok
+
+# GROUP BY DISTINCT over empty columns
+select count(*) from emp where deptno = 20 group by distinct ();
++--------+
+| EXPR$0 |
++--------+
+| 1 |
++--------+
+(1 row)
+
+!ok
+
+# GROUP BY DISTINCT x + y
+select deptno + 1, count(*) from emp where deptno = 20 group by distinct
deptno + 1;
++--------+--------+
+| EXPR$0 | EXPR$1 |
++--------+--------+
+| 21 | 1 |
++--------+--------+
+(1 row)
+
+!ok
+
# CUBE and JOIN
select e.deptno, e.gender, min(e.ename) as min_name
from emp as e join dept as d using (deptno)
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 4f1de54526..2b6f52eda7 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -203,7 +203,7 @@ select:
{ * | projectItem [, projectItem ]* }
FROM tableExpression
[ WHERE booleanExpression ]
- [ GROUP BY { groupItem [, groupItem ]* } ]
+ [ GROUP BY [ ALL | DISTINCT ] { groupItem [, groupItem ]* } ]
[ HAVING booleanExpression ]
[ WINDOW windowName AS windowSpec [, windowName AS windowSpec ]* ]
@@ -370,6 +370,11 @@ function).
An IN, EXISTS, UNIQUE or scalar sub-query may be correlated; that is, it
may refer to tables in the FROM clause of an enclosing query.
+GROUP BY DISTINCT removes duplicate grouping sets (for example,
+"GROUP BY DISTINCT GROUPING SETS ((a), (a, b), (a))" is equivalent to
+"GROUP BY GROUPING SETS ((a), (a, b))");
+GROUP BY ALL is equivalent to GROUP BY.
+
*selectWithoutFrom* is equivalent to VALUES,
but is not standard SQL and is only allowed in certain
[conformance levels]({{ site.apiRoot
}}/org/apache/calcite/sql/validate/SqlConformance.html#isFromRequired--).
diff --git
a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 0dd79138ea..54841c39f5 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -2122,6 +2122,29 @@ public class SqlParserTest {
sql(sql).ok(expected);
}
+ @Test void testGroupByAllOrDistinct() {
+ final String sql = "select deptno from emp\n"
+ + "group by all cube (a, b), rollup (a, b)";
+ final String expected = "SELECT `DEPTNO`\n"
+ + "FROM `EMP`\n"
+ + "GROUP BY CUBE(`A`, `B`), ROLLUP(`A`, `B`)";
+ sql(sql).ok(expected);
+
+ final String sql1 = "select deptno from emp\n"
+ + "group by distinct cube (a, b), rollup (a, b)";
+ final String expected1 = "SELECT `DEPTNO`\n"
+ + "FROM `EMP`\n"
+ + "GROUP BY DISTINCT CUBE(`A`, `B`), ROLLUP(`A`, `B`)";
+ sql(sql1).ok(expected1);
+
+ final String sql2 = "select deptno from emp\n"
+ + "group by cube (a, b), rollup (a, b)";
+ final String expected2 = "SELECT `DEPTNO`\n"
+ + "FROM `EMP`\n"
+ + "GROUP BY CUBE(`A`, `B`), ROLLUP(`A`, `B`)";
+ sql(sql2).ok(expected2);
+ }
+
@Test void testGroupByCube2() {
final String sql = "select deptno from emp\n"
+ "group by cube ((a, b), (c, d)) order by a";