This is an automated email from the ASF dual-hosted git repository.
JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 8fad78466f6 Implement GROUP BY ALL functionality (#17937)
8fad78466f6 is described below
commit 8fad78466f6e72930c140aed43a856674f10d2cb
Author: Zhao Xinqi <[email protected]>
AuthorDate: Tue Jun 16 14:12:22 2026 +0800
Implement GROUP BY ALL functionality (#17937)
---
.../it/query/recent/IoTDBGroupByAllTableIT.java | 238 +++++++++++++++++++++
.../relational/analyzer/AggregationAnalyzer.java | 84 +++++++-
.../relational/analyzer/StatementAnalyzer.java | 28 ++-
.../plan/relational/sql/parser/AstBuilder.java | 16 +-
.../plan/relational/sql/ast/GroupBy.java | 26 ++-
.../sql/util/CommonQuerySqlFormatter.java | 9 +-
.../db/relational/grammar/sql/RelationalSql.g4 | 3 +-
7 files changed, 385 insertions(+), 19 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBGroupByAllTableIT.java
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBGroupByAllTableIT.java
new file mode 100644
index 00000000000..da543952a34
--- /dev/null
+++
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBGroupByAllTableIT.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.relational.it.query.recent;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBGroupByAllTableIT {
+ protected static final String DATABASE_NAME = "test";
+
+ protected static final String[] createSqls =
+ new String[] {
+ "CREATE DATABASE " + DATABASE_NAME,
+ "USE " + DATABASE_NAME,
+ "CREATE TABLE table1(device_id STRING TAG, s1 DOUBLE FIELD, x INT32
FIELD, y INT32 FIELD)",
+ "INSERT INTO table1(time, device_id, s1, x, y) VALUES (1, 'd1', 20.0,
1, 10)",
+ "INSERT INTO table1(time, device_id, s1, x, y) VALUES (2, 'd1', 30.0,
2, 20)",
+ "INSERT INTO table1(time, device_id, s1, x, y) VALUES (3, 'd2', 20.0,
1, 30)",
+ "INSERT INTO table1(time, device_id, s1, x, y) VALUES (4, 'd2', 40.0,
3, 40)",
+ "FLUSH"
+ };
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ EnvFactory.getEnv().initClusterEnvironment();
+ prepareTableData(createSqls);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ EnvFactory.getEnv().cleanClusterEnvironment();
+ }
+
+ @Test
+ public void testBasicInference() {
+ String[] expectedHeader = new String[] {"device_id", "_col1"};
+ String[] retArray = new String[] {"d1,25.0,", "d2,30.0,"};
+
+ tableResultSetEqualTest(
+ "SELECT device_id, avg(s1) FROM table1 GROUP BY ALL ORDER BY
device_id",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+
+ expectedHeader = new String[] {"d", "avg_s1"};
+ retArray = new String[] {"d1,25.0,", "d2,30.0,"};
+ tableResultSetEqualTest(
+ "SELECT device_id AS d, avg(s1) AS avg_s1 FROM table1 GROUP BY ALL
ORDER BY d",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+ }
+
+ @Test
+ public void testExpressionAndConstantInference() {
+ String[] expectedHeader = new String[] {"_col0", "_col1"};
+ String[] retArray = new String[] {"false,2,", "true,2,"};
+
+ tableResultSetEqualTest(
+ "SELECT s1 > 25, count(*) FROM table1 GROUP BY ALL ORDER BY 1",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+
+ retArray = new String[] {"factory_01,4,"};
+ tableResultSetEqualTest(
+ "SELECT 'factory_01', count(*) FROM table1 GROUP BY ALL",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+
+ retArray = new String[] {"100,4,"};
+ tableResultSetEqualTest(
+ "SELECT 100, count(*) FROM table1 GROUP BY ALL", expectedHeader,
retArray, DATABASE_NAME);
+
+ expectedHeader = new String[] {"device_id", "_col1", "_col2"};
+ retArray = new String[] {"d1,1,25.0,", "d2,1,30.0,"};
+ tableResultSetEqualTest(
+ "SELECT device_id, 1, avg(s1) FROM table1 GROUP BY ALL ORDER BY
device_id",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+ }
+
+ @Test
+ public void testAggregateExpressionsAreSkipped() {
+ String[] expectedHeader = new String[] {"_col0", "_col1"};
+ String[] retArray = new String[] {"7.0,7.0,"};
+
+ tableResultSetEqualTest(
+ "SELECT sum(x), abs(sum(x)) FROM table1 GROUP BY ALL",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+
+ expectedHeader = new String[] {"_col0"};
+ retArray = new String[] {"27.5,"};
+ tableResultSetEqualTest(
+ "SELECT avg(s1) FROM table1 GROUP BY ALL", expectedHeader, retArray,
DATABASE_NAME);
+ }
+
+ @Test
+ public void testMixedAggregateExpressionValidation() {
+ tableAssertTestFail(
+ "SELECT s1 + avg(y) FROM table1 GROUP BY ALL",
+ "must be an aggregate expression or appear in GROUP BY clause",
+ DATABASE_NAME);
+
+ String[] expectedHeader = new String[] {"s1", "_col1"};
+ String[] retArray = new String[] {"20.0,40.0,", "30.0,50.0,",
"40.0,80.0,"};
+
+ tableResultSetEqualTest(
+ "SELECT s1, s1 + avg(y) FROM table1 GROUP BY ALL ORDER BY s1",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+ }
+
+ @Test
+ public void testSelectAllAfterExpansion() {
+ String[] expectedHeader = new String[] {"time", "device_id", "s1", "x",
"y", "_col5"};
+ String[] retArray =
+ new String[] {
+ "1970-01-01T00:00:00.001Z,d1,20.0,1,10,1,",
+ "1970-01-01T00:00:00.002Z,d1,30.0,2,20,1,",
+ "1970-01-01T00:00:00.003Z,d2,20.0,1,30,1,",
+ "1970-01-01T00:00:00.004Z,d2,40.0,3,40,1,"
+ };
+
+ tableResultSetEqualTest(
+ "SELECT *, count(*) FROM table1 GROUP BY ALL ORDER BY time",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+ }
+
+ @Test
+ public void testWindowFunctionValidation() {
+ String[] expectedHeader = new String[] {"device_id", "_col1", "_col2"};
+ String[] retArray = new String[] {"d1,1,25.0,", "d2,1,30.0,"};
+
+ tableResultSetEqualTest(
+ "SELECT device_id, count(*) OVER (PARTITION BY device_id), avg(s1)
FROM table1 GROUP BY ALL ORDER BY device_id",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+
+ expectedHeader = new String[] {"device_id", "s1"};
+ retArray = new String[] {"d1,20.0,", "d2,20.0,", "d1,30.0,", "d2,40.0,"};
+ tableResultSetEqualTest(
+ "SELECT device_id, s1 FROM table1 GROUP BY ALL ORDER BY rank() OVER
(ORDER BY s1), device_id",
+ expectedHeader,
+ retArray,
+ DATABASE_NAME);
+
+ tableAssertTestFail(
+ "SELECT device_id, rank() OVER (ORDER BY s1), avg(s1) FROM table1
GROUP BY ALL",
+ "ORDER BY expression 's1' must be an aggregate expression or appear in
GROUP BY clause",
+ DATABASE_NAME);
+
+ tableAssertTestFail(
+ "SELECT device_id, count(*) OVER (PARTITION BY s1), avg(s1) FROM
table1 GROUP BY ALL",
+ "PARTITION BY expression 's1' must be an aggregate expression or
appear in GROUP BY clause",
+ DATABASE_NAME);
+
+ tableAssertTestFail(
+ "SELECT device_id, avg(s1) FROM table1 GROUP BY ALL ORDER BY rank()
OVER (ORDER BY s1)",
+ "ORDER BY expression 's1' must be an aggregate expression or appear in
GROUP BY clause",
+ DATABASE_NAME);
+
+ tableAssertTestFail(
+ "SELECT sum(count(*) OVER (PARTITION BY device_id)) FROM table1 GROUP
BY ALL",
+ "Cannot nest window functions inside aggregation",
+ DATABASE_NAME);
+ }
+
+ @Test
+ public void testWindowFrameValidation() {
+ tableAssertTestFail(
+ "SELECT device_id, count(*) OVER (PARTITION BY device_id ORDER BY
device_id ROWS BETWEEN x PRECEDING AND CURRENT ROW), avg(s1) FROM table1 GROUP
BY ALL",
+ "Window frame start must be an aggregate expression or appear in GROUP
BY clause",
+ DATABASE_NAME);
+
+ tableAssertTestFail(
+ "SELECT device_id, count(*) OVER (PARTITION BY device_id ORDER BY
device_id ROWS BETWEEN CURRENT ROW AND x FOLLOWING), avg(s1) FROM table1 GROUP
BY ALL",
+ "Window frame end must be an aggregate expression or appear in GROUP
BY clause",
+ DATABASE_NAME);
+ }
+
+ @Test
+ public void testIllegalAutoGroupByCombination() {
+ tableAssertTestFail(
+ "SELECT device_id, count(*) FROM table1 GROUP BY ALL, device_id",
+ "GROUP BY ALL cannot be combined with explicit grouping elements",
+ DATABASE_NAME);
+
+ tableAssertTestFail(
+ "SELECT device_id, count(*) FROM table1 GROUP BY ALL,
ROLLUP(device_id)",
+ "GROUP BY ALL cannot be combined with explicit grouping elements",
+ DATABASE_NAME);
+
+ tableAssertTestFail(
+ "SELECT device_id, count(*) FROM table1 GROUP BY CUBE(device_id), ALL",
+ "GROUP BY ALL cannot be combined with explicit grouping elements",
+ DATABASE_NAME);
+ }
+}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
index d083c478fac..46787078230 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/AggregationAnalyzer.java
@@ -51,9 +51,13 @@ import
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.QuantifiedCo
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Row;
import
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SearchedCaseExpression;
import
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SimpleCaseExpression;
+import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SortItem;
import
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SubqueryExpression;
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Trim;
import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WhenClause;
+import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Window;
+import
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WindowFrame;
+import
org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.WindowSpecification;
import org.apache.iotdb.db.i18n.DataNodeQueryMessages;
import org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware;
import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.AstVisitor;
@@ -73,11 +77,13 @@ import static
com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static
org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractAggregateFunctions;
+import static
org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.extractWindowExpressions;
import static
org.apache.iotdb.db.queryengine.plan.relational.analyzer.ExpressionTreeUtils.isAggregationFunction;
import static
org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.getReferencesToScope;
import static
org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.hasReferencesToScope;
import static
org.apache.iotdb.db.queryengine.plan.relational.analyzer.ScopeReferenceExtractor.isFieldFromScope;
import static
org.apache.iotdb.db.queryengine.plan.relational.planner.ScopeAware.scopeAwareKey;
+import static
org.apache.iotdb.db.queryengine.plan.relational.utils.NodeUtils.getSortItemsFromOrderBy;
/** Checks whether an expression is constant with respect to the group */
class AggregationAnalyzer {
@@ -288,19 +294,85 @@ class AggregationAnalyzer {
@Override
public Boolean visitFunctionCall(FunctionCall node, Void context) {
if (isAggregationFunction(node.getName().toString())) {
- List<FunctionCall> aggregateFunctions =
extractAggregateFunctions(node.getArguments());
+ if (node.getWindow().isEmpty()) {
+ List<FunctionCall> aggregateFunctions =
extractAggregateFunctions(node.getArguments());
+ List<Expression> windowExpressions =
extractWindowExpressions(node.getArguments());
+
+ if (!aggregateFunctions.isEmpty()) {
+ throw new SemanticException(
+ String.format(
+ "Cannot nest aggregations inside aggregation '%s': %s",
+ node.getName(), aggregateFunctions));
+ }
+
+ if (!windowExpressions.isEmpty()) {
+ throw new SemanticException(
+ String.format(
+ "Cannot nest window functions inside aggregation '%s': %s",
+ node.getName(), windowExpressions));
+ }
+
+ return true;
+ }
+ } else {
+ // Reject FILTER for non-aggregation functions when FunctionCall
supports FILTER.
+ // Reject ORDER BY for non-aggregation functions when FunctionCall
supports function-level
+ // ORDER BY.
+ }
+
+ if (node.getWindow().isPresent()) {
+ Window window = node.getWindow().get();
+ if (window instanceof WindowSpecification windowSpecification
+ && !process(windowSpecification, context)) {
+ return false;
+ }
+ }
+
+ return node.getArguments().stream().allMatch(expression ->
process(expression, context));
+ }
- if (!aggregateFunctions.isEmpty()) {
+ @Override
+ public Boolean visitWindowSpecification(WindowSpecification node, Void
context) {
+ for (Expression expression : node.getPartitionBy()) {
+ if (!process(expression, context)) {
throw new SemanticException(
String.format(
- "Cannot nest aggregations inside aggregation '%s': %s",
- node.getName(), aggregateFunctions));
+ "PARTITION BY expression '%s' must be an aggregate
expression or appear in GROUP BY clause",
+ expression));
}
+ }
- return true;
+ for (SortItem sortItem : getSortItemsFromOrderBy(node.getOrderBy())) {
+ Expression expression = sortItem.getSortKey();
+ if (!process(expression, context)) {
+ throw new SemanticException(
+ String.format(
+ "ORDER BY expression '%s' must be an aggregate expression or
appear in GROUP BY clause",
+ expression));
+ }
}
- return node.getArguments().stream().allMatch(expression ->
process(expression, context));
+ if (node.getFrame().isPresent()) {
+ process(node.getFrame().get(), context);
+ }
+
+ return true;
+ }
+
+ @Override
+ public Boolean visitWindowFrame(WindowFrame node, Void context) {
+ if (node.getStart().getValue().isPresent()
+ && !process(node.getStart().getValue().get(), context)) {
+ throw new SemanticException(
+ "Window frame start must be an aggregate expression or appear in
GROUP BY clause");
+ }
+ if (node.getEnd().isPresent()
+ && node.getEnd().get().getValue().isPresent()
+ && !process(node.getEnd().get().getValue().get(), context)) {
+ throw new SemanticException(
+ "Window frame end must be an aggregate expression or appear in
GROUP BY clause");
+ }
+ return true;
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
index 35285f77bd0..3634debb6d8 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java
@@ -2773,12 +2773,23 @@ public class StatementAnalyzer {
FunctionCall gapFillColumn = null;
ImmutableList.Builder<Expression> gapFillGroupingExpressions =
ImmutableList.builder();
- checkGroupingSetsCount(node.getGroupBy().get());
- for (GroupingElement groupingElement :
node.getGroupBy().get().getGroupingElements()) {
+ GroupBy groupBy = node.getGroupBy().get();
+ List<Expression> allGroupByExpressions = ImmutableList.of();
+ List<GroupingElement> groupingElements;
+
+ if (groupBy.isAll()) {
+ allGroupByExpressions =
inferAllGroupByExpressions(outputExpressions);
+ groupingElements = ImmutableList.of(new
SimpleGroupBy(allGroupByExpressions));
+ } else {
+ checkGroupingSetsCount(groupBy);
+ groupingElements = groupBy.getGroupingElements();
+ }
+
+ for (GroupingElement groupingElement : groupingElements) {
if (groupingElement instanceof SimpleGroupBy) {
for (Expression column : groupingElement.getExpressions()) {
// simple GROUP BY expressions allow ordinals or arbitrary
expressions
- if (column instanceof LongLiteral) {
+ if (!groupBy.isAll() && column instanceof LongLiteral) {
long ordinal = ((LongLiteral) column).getParsedValue();
if (ordinal < 1 || ordinal > outputExpressions.size()) {
throw new SemanticException(
@@ -2919,6 +2930,17 @@ public class StatementAnalyzer {
.orElse(expression);
}
+ private List<Expression> inferAllGroupByExpressions(List<Expression>
outputExpressions) {
+ ImmutableList.Builder<Expression> groupingExpressions =
ImmutableList.builder();
+ for (Expression expression : outputExpressions) {
+ if (extractAggregateFunctions(ImmutableList.of(expression)).isEmpty()
+ && extractWindowFunctions(ImmutableList.of(expression)).isEmpty())
{
+ groupingExpressions.add(expression);
+ }
+ }
+ return groupingExpressions.build();
+ }
+
private boolean isDateBinGapFill(Expression column) {
return column instanceof FunctionCall
&& DATE_BIN
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
index ab2909e5e3f..b3c375129df 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/AstBuilder.java
@@ -2635,10 +2635,24 @@ public class AstBuilder extends
RelationalSqlBaseVisitor<Node> {
}
@Override
- public Node visitGroupBy(RelationalSqlParser.GroupByContext ctx) {
+ public Node visitAllGroupBy(RelationalSqlParser.AllGroupByContext ctx) {
+ return new GroupBy(getLocation(ctx), false, true, ImmutableList.of());
+ }
+
+ @Override
+ public Node visitExplicitGroupBy(RelationalSqlParser.ExplicitGroupByContext
ctx) {
+ if (ctx.setQuantifier() == null && ctx.groupingElement().size() > 1) {
+ for (RelationalSqlParser.GroupingElementContext element :
ctx.groupingElement()) {
+ if (element.getText().equalsIgnoreCase("ALL")) {
+ throw new SemanticException(
+ "GROUP BY ALL cannot be combined with explicit grouping
elements");
+ }
+ }
+ }
return new GroupBy(
getLocation(ctx),
isDistinct(ctx.setQuantifier()),
+ false,
visit(ctx.groupingElement(), GroupingElement.class));
}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/GroupBy.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/GroupBy.java
index 9a22e842434..2421684aa95 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/GroupBy.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/ast/GroupBy.java
@@ -33,18 +33,26 @@ public class GroupBy extends Node {
private static final long INSTANCE_SIZE =
RamUsageEstimator.shallowSizeOfInstance(GroupBy.class);
private final boolean isDistinct;
+ private final boolean isAll;
private final List<GroupingElement> groupingElements;
public GroupBy(boolean isDistinct, List<GroupingElement> groupingElements) {
- super(null);
- this.isDistinct = isDistinct;
- this.groupingElements =
ImmutableList.copyOf(requireNonNull(groupingElements));
+ this(null, isDistinct, false, groupingElements);
}
public GroupBy(
NodeLocation location, boolean isDistinct, List<GroupingElement>
groupingElements) {
- super(requireNonNull(location, "location is null"));
+ this(location, isDistinct, false, groupingElements);
+ }
+
+ public GroupBy(
+ NodeLocation location,
+ boolean isDistinct,
+ boolean isAll,
+ List<GroupingElement> groupingElements) {
+ super(location);
this.isDistinct = isDistinct;
+ this.isAll = isAll;
this.groupingElements =
ImmutableList.copyOf(requireNonNull(groupingElements));
}
@@ -52,6 +60,10 @@ public class GroupBy extends Node {
return isDistinct;
}
+ public boolean isAll() {
+ return isAll;
+ }
+
public List<GroupingElement> getGroupingElements() {
return groupingElements;
}
@@ -76,18 +88,20 @@ public class GroupBy extends Node {
}
GroupBy groupBy = (GroupBy) o;
return isDistinct == groupBy.isDistinct
+ && isAll == groupBy.isAll
&& Objects.equals(groupingElements, groupBy.groupingElements);
}
@Override
public int hashCode() {
- return Objects.hash(isDistinct, groupingElements);
+ return Objects.hash(isDistinct, isAll, groupingElements);
}
@Override
public String toString() {
return toStringHelper(this)
.add("isDistinct", isDistinct)
+ .add("isAll", isAll)
.add("groupingElements", groupingElements)
.toString();
}
@@ -98,7 +112,7 @@ public class GroupBy extends Node {
return false;
}
- return isDistinct == ((GroupBy) other).isDistinct;
+ return isDistinct == ((GroupBy) other).isDistinct && isAll == ((GroupBy)
other).isAll;
}
@Override
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/util/CommonQuerySqlFormatter.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/util/CommonQuerySqlFormatter.java
index 7bb325fbc63..0ce928a957e 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/util/CommonQuerySqlFormatter.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/sql/util/CommonQuerySqlFormatter.java
@@ -404,14 +404,19 @@ public class CommonQuerySqlFormatter implements
CommonQueryAstVisitor<Void, Inte
node.getGroupBy()
.ifPresent(
- groupBy ->
+ groupBy -> {
+ if (groupBy.isAll()) {
+ append(indent, "GROUP BY ALL").append('\n');
+ } else {
append(
indent,
"GROUP BY "
+ (groupBy.isDistinct() ? " DISTINCT " : "")
+
org.apache.iotdb.commons.queryengine.plan.relational.sql.util
.ExpressionFormatter.formatGroupBy(groupBy.getGroupingElements()))
- .append('\n'));
+ .append('\n');
+ }
+ });
node.getHaving()
.ifPresent(having -> append(indent, "HAVING " +
formatExpression(having)).append('\n'));
diff --git
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
index 301f9c0589c..4222754cdb9 100644
---
a/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
+++
b/iotdb-core/relational-grammar/src/main/antlr4/org/apache/iotdb/db/relational/grammar/sql/RelationalSql.g4
@@ -1055,7 +1055,8 @@ fromFirstQuerySpecification
;
groupBy
- : setQuantifier? groupingElement (',' groupingElement)*
+ : ALL
#allGroupBy
+ | setQuantifier? groupingElement (',' groupingElement)*
#explicitGroupBy
;
groupingElement