This is an automated email from the ASF dual-hosted git repository.

amashenkov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 8f6a1f99e71 IGNITE-25872 Sql. Support GROUPING aggregate function 
(#6479)
8f6a1f99e71 is described below

commit 8f6a1f99e71c4d743a73e627610831343fdf8f9c
Author: Andrew V. Mashenkov <amashen...@users.noreply.github.com>
AuthorDate: Wed Sep 3 10:42:11 2025 +0300

    IGNITE-25872 Sql. Support GROUPING aggregate function (#6479)
---
 .../internal/sql/engine/ItAggregatesTest.java      |  72 +++++++-
 .../group1/aggregate/group/test_grouping_sets.test | 149 ++++++++++++++++
 .../group/test_multicolumn_grouping_sets.test      | 106 ++++++++++++
 .../engine/exec/exp/agg/AccumulatorWrapper.java    |   3 +
 .../sql/engine/exec/exp/agg/Accumulators.java      |  65 +++++++
 .../engine/exec/exp/agg/AccumulatorsFactory.java   |  19 ++-
 .../sql/engine/exec/exp/agg/AggregateRow.java      |  21 ++-
 .../sql/engine/exec/rel/HashAggregateNode.java     |   4 +-
 .../sql/engine/exec/rel/SortAggregateNode.java     |   2 +-
 .../sql/engine/prepare/IgniteSqlValidator.java     |   8 +-
 .../sql/engine/rel/agg/MapReduceAggregates.java    |  27 ++-
 .../sql/engine/sql/fun/IgniteSqlOperatorTable.java |   1 +
 .../internal/sql/engine/util/IgniteResource.java   |   5 +
 .../ignite/internal/sql/docs/OperatorListTest.java |   1 +
 .../exec/exp/agg/GroupingAccumulatorTest.java      | 189 +++++++++++++++++++++
 .../planner/AbstractAggregatePlannerTest.java      |  35 +++-
 .../sql/engine/planner/AggregatePlannerTest.java   |  12 ++
 .../planner/ColocatedHashAggregatePlannerTest.java |  12 ++
 .../planner/ColocatedSortAggregatePlannerTest.java |  12 ++
 .../planner/MapReduceHashAggregatePlannerTest.java |  12 ++
 .../planner/MapReduceSortAggregatePlannerTest.java |  12 ++
 .../sql/engine/planner/TpcdsQueryPlannerTest.java  |   4 +-
 .../src/test/resources/docs/operator_list.txt      |   3 +
 .../src/test/resources/tpcds/plan/q27.plan         |  68 ++++++++
 .../test/resources/tpcds/plan/q27_colocated.plan   |  68 ++++++++
 25 files changed, 887 insertions(+), 23 deletions(-)

diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAggregatesTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAggregatesTest.java
index ea3973dec7a..c5f9d9f11f4 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAggregatesTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAggregatesTest.java
@@ -29,7 +29,10 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
+import org.apache.ignite.internal.lang.IgniteStringFormatter;
 import org.apache.ignite.internal.sql.BaseSqlIntegrationTest;
 import org.apache.ignite.internal.sql.engine.hint.IgniteHint;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeSystem;
@@ -37,6 +40,7 @@ import org.apache.ignite.internal.sql.engine.util.HintUtils;
 import org.apache.ignite.internal.sql.engine.util.QueryChecker;
 import org.apache.ignite.internal.testframework.WithSystemProperty;
 import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.sql.SqlException;
 import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -515,20 +519,82 @@ public class ItAggregatesTest extends 
BaseSqlIntegrationTest {
         assertQuery("SELECT str_col, SUM(int_col), COUNT(str_col) FROM 
test_str_int_real_dec GROUP BY GROUPING SETS "
                 + "( (str_col, int_col), (str_col), (int_col), () ) HAVING 
SUM(int_col) > 0")
                 .disableRules(rules)
+                // empty group
                 .returns(null, 80L, 4L)
+                // group (str_col, int_col)
                 .returns("s1", 10L, 1L)
-                .returns("s3", 40L, 1L)
                 .returns("s1", 20L, 1L)
                 .returns("s2", 10L, 1L)
-                .returns("s2", 10L, 1L)
                 .returns("s3", 40L, 1L)
+                // group (str_col)
                 .returns("s1", 30L, 2L)
+                .returns("s2", 10L, 1L)
+                .returns("s3", 40L, 1L)
+                // group (int_col)
                 .returns(null, 40L, 1L)
                 .returns(null, 20L, 2L)
                 .returns(null, 20L, 1L)
                 .check();
     }
 
+    @ParameterizedTest
+    @MethodSource("rulesForGroupingSets")
+    public void testGroupingFunction(String[] rules) {
+        sql("DELETE FROM test_str_int_real_dec");
+
+        sql("DELETE FROM test_str_int_real_dec");
+
+        sql("INSERT INTO test_str_int_real_dec(id, str_col, int_col) VALUES 
(1, 's1', 10)");
+        sql("INSERT INTO test_str_int_real_dec(id, str_col, int_col) VALUES 
(2, 's1', 20)");
+        sql("INSERT INTO test_str_int_real_dec(id, str_col, int_col) VALUES 
(3, 's2', 10)");
+        sql("INSERT INTO test_str_int_real_dec(id, str_col, int_col) VALUES 
(4, 's3', 40)");
+
+        assertQuery("SELECT GROUPING(str_col), str_col FROM 
test_str_int_real_dec GROUP BY GROUPING SETS ((str_col))")
+                .disableRules(rules)
+                .returns(1L, "s1")
+                .returns(1L, "s2")
+                .returns(1L, "s3")
+                .check();
+
+        assertQuery("SELECT GROUPING(str_col), str_col FROM 
test_str_int_real_dec GROUP BY GROUPING SETS ((str_col), (str_col))")
+                .disableRules(rules)
+                .returns(1L, "s1")
+                .returns(1L, "s1")
+                .returns(1L, "s2")
+                .returns(1L, "s2")
+                .returns(1L, "s3")
+                .returns(1L, "s3")
+                .check();
+
+        assertQuery("SELECT GROUPING(int_col, str_col), GROUPING(int_col),"
+                + "str_col, SUM(int_col), COUNT(str_col) FROM 
test_str_int_real_dec GROUP BY GROUPING SETS "
+                + "( (str_col, int_col), (str_col), (int_col), () ) HAVING 
SUM(int_col) > 0")
+                .disableRules(rules)
+                // group (str_col, int_col)
+                .returns(3L, 1L, "s1", 10L, 1L)
+                .returns(3L, 1L, "s1", 20L, 1L)
+                .returns(3L, 1L, "s2", 10L, 1L)
+                .returns(3L, 1L, "s3", 40L, 1L)
+                // group (str_col)
+                .returns(1L, 0L, "s1", 30L, 2L)
+                .returns(1L, 0L, "s2", 10L, 1L)
+                .returns(1L, 0L, "s3", 40L, 1L)
+                // group (int_col)
+                .returns(2L, 1L, null, 20L, 2L)
+                .returns(2L, 1L, null, 20L, 1L)
+                .returns(2L, 1L, null, 40L, 1L)
+                // empty group
+                .returns(0L, 0L, null, 80L, 4L)
+                .check();
+
+        String invalidQuery = IgniteStringFormatter.format(
+                "SELECT GROUPING({}), str_col FROM test_str_int_real_dec GROUP 
BY GROUPING SETS ((str_col))",
+                IntStream.rangeClosed(1, 64).mapToObj(i -> 
"str_col").collect(Collectors.joining(",")));
+        assertThrows(SqlException.class, () -> sql(invalidQuery),
+                "Invalid number of arguments to function ''GROUPING''. Was 
expecting number of agruments in range [1, 63]");
+
+    }
+
     @ParameterizedTest
     @MethodSource("rulesForGroupingSets")
     public void testDuplicateGroupingSets(String[] rules) {
@@ -760,7 +826,7 @@ public class ItAggregatesTest extends 
BaseSqlIntegrationTest {
                 new String[]{"ColocatedHashAggregateConverterRule", 
"ColocatedSortAggregateConverterRule"},
 
                 // Use colocated aggregates grouping sets
-                new String[]{"MapReduceHashAggregateConverterRule", 
"ColocatedSortAggregateConverterRule"}
+                new String[]{"MapReduceHashAggregateConverterRule", 
"MapReduceSortAggregateConverterRule"}
         );
 
         return rules.stream().map(Object.class::cast).map(Arguments::of);
diff --git 
a/modules/sql-engine/src/integrationTest/sql/group1/aggregate/group/test_grouping_sets.test
 
b/modules/sql-engine/src/integrationTest/sql/group1/aggregate/group/test_grouping_sets.test
new file mode 100644
index 00000000000..051533ad1fb
--- /dev/null
+++ 
b/modules/sql-engine/src/integrationTest/sql/group1/aggregate/group/test_grouping_sets.test
@@ -0,0 +1,149 @@
+# name: test/sql/aggregate/group/test_grouping_sets.test
+# description: Test group by with rollup and cube
+# feature: T431 (GROUPING operation), T433 (Multiargument GROUPING function)
+# group: [group]
+
+# Single group
+query I
+SELECT GROUPING(a)
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a))
+----
+1
+1
+
+query II rowsort
+SELECT GROUPING(a), a
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a))
+----
+1      1
+1      4
+
+query II rowsort
+SELECT GROUPING(a), SUM(c)
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a))
+----
+1      9
+1      6
+
+# Multiple groups
+query III rowsort
+SELECT GROUPING(a), GROUPING(b), GROUPING(c)
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a), (b), (c))
+----
+1      0       0
+1      0       0
+0      1       0
+0      1       0
+0      0       1
+0      0       1
+
+query IIII
+SELECT GROUPING(a), GROUPING(b), a, b
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a), (b))
+----
+1      0       1       NULL
+1      0       4       NULL
+0      1       NULL    2
+0      1       NULL    NULL
+
+query IIII rowsort
+SELECT GROUPING(a), SUM(a), GROUPING(b),  SUM(c)
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a), (b))
+----
+0      1       1       3
+0      5       1       12
+1      2       0       9
+1      4       0       6
+
+# Duplicate groups
+query I
+SELECT GROUPING(b) as g1
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((b), (b))
+----
+1
+1
+1
+1
+
+query II rowsort
+SELECT GROUPING(b), b
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((b), (b))
+----
+1      2
+1      2
+1      NULL
+1      NULL
+
+query II rowsort
+SELECT GROUPING(b) as g1, SUM(c) as g2
+FROM (VALUES (1, 2, 3), (4, NULL, 6), (1, NULL, 6)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((b), (b))
+----
+1      3
+1      3
+1      12
+1      12
+
+# Empty group
+query IIRR rowsort
+SELECT GROUPING(city_id), GROUPING(category_id), min(price), max(price)
+FROM (VALUES
+(2, 6, 21500.00),
+(2, 6, 22900.00),
+(2, 6, 23500.00),
+(2, 7, 17800.00),
+(4, 6, 20000.00),
+(4, 8, 37000.00),
+(4, 9, 75600.00)
+) AS t (city_id, category_id, price)
+GROUP BY GROUPING SETS ((city_id), (category_id), ())
+----
+1      0       17800.00        23500.00
+1      0       20000.00        75600.00
+0      1       17800.00        17800.00
+0      1       20000.00        23500.00
+0      1       37000.00        37000.00
+0      1       75600.00        75600.00
+0      0       17800.00        75600.00
+
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-26335 Conversion to relational 
algebra failed to preserve datatypes
+query IIT rowsort
+SELECT GROUPING(id), GROUPING(id), id
+FROM ( VALUES
+('c4a0327c-44be-416d-ae90-75c05079789f'::UUID, 1),
+('367fc6f1-40c3-4237-8545-3fd102d29134'::UUID, 2)
+) as t(val, id)
+GROUP BY ALL GROUPING SETS (id)
+----
+1      1       c4a0327c-44be-416d-ae90-75c05079789f
+1      1       c4a0327c-44be-416d-ae90-75c05079789f
+1      1       367fc6f1-40c3-4237-8545-3fd102d29134
+1      1       367fc6f1-40c3-4237-8545-3fd102d29134
+
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-26335 Conversion to relational 
algebra failed while rewriting GROUPING SETS to UNION
+# Duplicate groups
+query IIT rowsort
+SELECT GROUPING(val), GROUPING(val), val
+FROM ( VALUES
+(1, 1),
+(1, 2),
+(1, 3)
+) as t(id, val)
+GROUP BY ALL GROUPING SETS ((val), (val))
+----
+1      1       1
+1      1       1
+1      1       2
+1      1       2
+1      1       3
+1      1       3
diff --git 
a/modules/sql-engine/src/integrationTest/sql/group1/aggregate/group/test_multicolumn_grouping_sets.test
 
b/modules/sql-engine/src/integrationTest/sql/group1/aggregate/group/test_multicolumn_grouping_sets.test
new file mode 100644
index 00000000000..8b20715393d
--- /dev/null
+++ 
b/modules/sql-engine/src/integrationTest/sql/group1/aggregate/group/test_multicolumn_grouping_sets.test
@@ -0,0 +1,106 @@
+# name: test/sql/aggregate/group/test_grouping_sets.test
+# description: Test group by with rollup and cube
+# feature: T431 (GROUPING operation), T433 (Multiargument GROUPING function)
+# group: [group]
+
+query IIII rowsort
+SELECT GROUPING(a), GROUPING(b), GROUPING(c), GROUPING(a, b, c)
+FROM (VALUES (1, 2, 3), (1, NULL, 3)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a), (a, b), (a, b, c))
+----
+1      0       0       4
+1      1       0       6
+1      1       0       6
+1      1       1       7
+1      1       1       7
+
+query IIII rowsort
+SELECT GROUPING(a), GROUPING(b), GROUPING(c), GROUPING(a, b, c)
+FROM (VALUES (1, 2, 3), (1, NULL, 3)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a), (b, c), ())
+----
+1      0       0       4
+0      1       1       3
+0      1       1       3
+0      0       0       0
+
+query III rowsort
+SELECT GROUPING(a), GROUPING(a, b), GROUPING(a, b, c)
+FROM (VALUES (1, 2, 3), (1, NULL, 3)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a, b), (a, c), (b, c))
+----
+1      3       6
+1      3       6
+1      2       5
+0      1       3
+0      1       3
+
+query IIIII rowsort
+SELECT GROUPING(a, b), GROUPING(a, c), GROUPING(b, c), GROUPING(a), 
GROUPING(a, b, c)
+FROM (VALUES (1, 2, 3), (1, NULL, 3)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a, b), (a, c), (b, c))
+----
+3      2       2       1       6
+3      2       2       1       6
+2      3       1       1       5
+1      1       3       0       3
+1      1       3       0       3
+
+query IIIII rowsort
+SELECT GROUPING(a, b), GROUPING(a, b, c), a, b, c
+FROM (VALUES (1, 2, 3), (1, NULL, 3)) AS t (a, b, c)
+GROUP BY GROUPING SETS ((a), (a, b), (a, c), (a, b, c))
+----
+2      4       1       NULL    NULL
+3      6       1       2       NULL
+3      6       1       NULL    NULL
+2      5       1       NULL    3
+3      7       1       2       3
+3      7       1       NULL    3
+
+query IIRR rowsort
+SELECT GROUPING(category_id, city_id) as city, GROUPING(city_id, category_id), 
min(price) as price_min, max(price) as price_max
+FROM (VALUES
+(2, 6, 21500.00),
+(2, 6, 22900.00),
+(2, 6, 23500.00),
+(2, 7, 17800.00),
+(4, 6, 20000.00),
+(4, 8, 37000.00),
+(4, 9, 75600.00)
+) AS t (city_id, category_id, price)
+GROUP BY GROUPING SETS ((city_id, category_id))
+----
+3      3       17800.00        17800.00
+3      3       20000.00        20000.00
+3      3       21500.00        23500.00
+3      3       37000.00        37000.00
+3      3       75600.00        75600.00
+
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-26335 Conversion to relational 
algebra failed to preserve datatypes
+query IIIT rowsort
+SELECT GROUPING(val), GROUPING(id, val), id, val
+FROM ( VALUES
+('c4a0327c-44be-416d-ae90-75c05079789f'::UUID, 1),
+('367fc6f1-40c3-4237-8545-3fd102d29134'::UUID, 2)
+) as t(val, id)
+GROUP BY ALL GROUPING SETS ((val, id))
+----
+1      3       1       c4a0327c-44be-416d-ae90-75c05079789f
+1      3       2       367fc6f1-40c3-4237-8545-3fd102d29134
+
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-26335 Rewriting GROUPING SETS 
to UNION failed with AssertionError during conversion from AST
+query IIIT rowsort
+SELECT GROUPING(val), GROUPING(id, val), id, val
+FROM ( VALUES
+('c4a0327c-44be-416d-ae90-75c05079789f'::UUID, 1),
+('367fc6f1-40c3-4237-8545-3fd102d29134'::UUID, 2)
+) as t(val, id)
+GROUP BY ALL GROUPING SETS ((val, id), (id, val))
+----
+1      3       1       c4a0327c-44be-416d-ae90-75c05079789f
+1      3       2       367fc6f1-40c3-4237-8545-3fd102d29134
+1      3       1       c4a0327c-44be-416d-ae90-75c05079789f
+1      3       2       367fc6f1-40c3-4237-8545-3fd102d29134
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorWrapper.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorWrapper.java
index a5802ca9dd2..0502bb51be2 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorWrapper.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorWrapper.java
@@ -27,6 +27,9 @@ public interface AccumulatorWrapper<RowT> {
     /** Returns {@code true} if the accumulator function should be applied to 
distinct elements. */
     boolean isDistinct();
 
+    /** Returns {@code true} if the accumulator function should be applied to 
groupset keys. */
+    boolean isGrouping();
+
     /** Returns the accumulator function. */
     Accumulator accumulator();
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
index 6780c762878..2ff21e0e03a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/Accumulators.java
@@ -30,6 +30,7 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.function.IntFunction;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import org.apache.calcite.DataContext;
 import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.rel.core.AggregateCall;
@@ -38,6 +39,7 @@ import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.sql.fun.SqlLiteralAggFunction;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.catalog.commands.CatalogUtils;
 import org.apache.ignite.internal.sql.engine.exec.exp.IgniteSqlFunctions;
 import org.apache.ignite.internal.sql.engine.type.IgniteCustomType;
@@ -100,6 +102,8 @@ public class Accumulators {
                 assert lit instanceof RexLiteral : "Non-literal argument for 
LiteralAgg: " + call + ", argument: " + lit;
 
                 return LiteralVal.newAccumulator(context, (RexLiteral) lit);
+            case "GROUPING":
+                return groupingFactory(call);
             default:
                 throw new AssertionError(call.getAggregation().getName());
         }
@@ -217,6 +221,67 @@ public class Accumulators {
         return AnyVal.newAccumulator(type);
     }
 
+    private Supplier<Accumulator> groupingFactory(AggregateCall call) {
+        RelDataType type = call.getType();
+
+        if (type.getSqlTypeName() == ANY && !(type instanceof 
IgniteCustomType)) {
+            throw unsupportedAggregateFunction(call);
+        }
+
+        return GroupingAccumulator.newAccumulator(call.getArgList());
+    }
+
+    /**
+     * {@code GROUPING(column [, ...])} accumulator. Pseudo accumulator that 
accepts group key columns set.
+     */
+    public static class GroupingAccumulator implements Accumulator {
+        private final List<Integer> argList;
+
+        public static Supplier<Accumulator> newAccumulator(List<Integer> 
argList) {
+            return () -> new GroupingAccumulator(argList);
+        }
+
+        private GroupingAccumulator(List<Integer> argList) {
+            assert !argList.isEmpty() : "GROUPING function must have at least 
one argument.";
+            assert argList.size() < Long.SIZE : "GROUPING function with more 
than 63 arguments is not supported.";
+            this.argList = argList;
+        }
+
+        @Override
+        public void add(AccumulatorsState state, Object[] args) {
+            assert false;
+        }
+
+        @Override
+        public void end(AccumulatorsState state, AccumulatorsState result) {
+            ImmutableBitSet groupKey = (ImmutableBitSet) state.get();
+
+            assert groupKey != null;
+
+            long res = 0;
+            long bit = 1L << (argList.size() - 1);
+            for (Integer col : argList) {
+                res += groupKey.get(col) ? bit : 0L;
+                bit >>= 1;
+            }
+
+            result.set(res);
+            state.set(null);
+        }
+
+        @Override
+        public List<RelDataType> argumentTypes(IgniteTypeFactory typeFactory) {
+            return argList.stream()
+                    .map(i -> 
typeFactory.createTypeWithNullability(typeFactory.createSqlType(ANY), true))
+                    .collect(Collectors.toList());
+        }
+
+        @Override
+        public RelDataType returnType(IgniteTypeFactory typeFactory) {
+            return typeFactory.createSqlType(BIGINT);
+        }
+    }
+
     /**
      * {@code SINGLE_VALUE(SUBQUERY)} accumulator. Pseudo accumulator that 
returns a first value produced by a subquery and an error if a
      * subquery returns more than one row.
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorsFactory.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorsFactory.java
index 39386c6885a..853d8d2f1c2 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorsFactory.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AccumulatorsFactory.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.sql.engine.exec.exp.agg;
 
-import static org.apache.calcite.sql.fun.SqlInternalOperators.LITERAL_AGG;
 import static 
org.apache.ignite.internal.sql.engine.util.TypeUtils.createRowType;
 import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
 
@@ -45,6 +44,7 @@ import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexProgram;
 import org.apache.calcite.rex.RexProgramBuilder;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.util.Pair;
@@ -259,6 +259,8 @@ public class AccumulatorsFactory<RowT> {
     private static final class AccumulatorWrapperImpl<RowT> implements 
AccumulatorWrapper<RowT> {
         static final IntList SINGLE_ARG_LIST = IntList.of(0);
 
+        private static final Object[] NO_ARGUMENTS = {null};
+
         private final Accumulator accumulator;
 
         private final Function<Object[], Object[]> inAdapter;
@@ -277,6 +279,8 @@ public class AccumulatorsFactory<RowT> {
 
         private final boolean distinct;
 
+        private final boolean grouping;
+
         AccumulatorWrapperImpl(
                 ExecutionContext<RowT> ctx,
                 Accumulator accumulator,
@@ -290,7 +294,8 @@ public class AccumulatorsFactory<RowT> {
             this.outAdapter = outAdapter;
             this.distinct = call.isDistinct();
 
-            literalAgg = call.getAggregation() == LITERAL_AGG;
+            grouping = call.getAggregation().getKind() == SqlKind.GROUPING;
+            literalAgg = call.getAggregation().getKind() == 
SqlKind.LITERAL_AGG;
             ignoreNulls = call.ignoreNulls();
             filterArg = call.hasFilter() ? call.filterArg : -1;
 
@@ -302,6 +307,11 @@ public class AccumulatorsFactory<RowT> {
             return distinct;
         }
 
+        @Override
+        public boolean isGrouping() {
+            return grouping;
+        }
+
         @Override
         public Accumulator accumulator() {
             return accumulator;
@@ -313,9 +323,10 @@ public class AccumulatorsFactory<RowT> {
                 return null;
             }
 
-            if (literalAgg) {
+            if (literalAgg || grouping) {
                 // LiteralAgg has a constant as its argument.
-                return new Object[]{null};
+                // Grouping aggregate doesn't accepts rows.
+                return NO_ARGUMENTS;
             }
 
             if (IgniteUtils.assertionsEnabled() && argList == SINGLE_ARG_LIST) 
{
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AggregateRow.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AggregateRow.java
index bf7be3cb594..836d3118e43 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AggregateRow.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/AggregateRow.java
@@ -61,7 +61,7 @@ public class AggregateRow<RowT> {
     }
 
     /** Updates this row by using data of the given row. */
-    public void update(List<AccumulatorWrapper<RowT>> accs, ImmutableBitSet 
allFields, RowHandler<RowT> handler, RowT row) {
+    public void update(List<AccumulatorWrapper<RowT>> accs, ImmutableBitSet 
grpFields, RowHandler<RowT> handler, RowT row) {
         for (int i = 0; i < accs.size(); i++) {
             AccumulatorWrapper<RowT> acc = accs.get(i);
 
@@ -72,7 +72,9 @@ public class AggregateRow<RowT> {
 
             state.setIndex(i);
 
-            if (acc.isDistinct()) {
+            if (acc.isGrouping()) {
+                state.set(grpFields);
+            } else if (acc.isDistinct()) {
                 Set<Object> distinctSet = distinctSets.get(i);
                 distinctSet.add(args[0]);
             } else {
@@ -92,9 +94,14 @@ public class AggregateRow<RowT> {
     }
 
     /** Writes aggregate state of the given row to given array. */
-    public void writeTo(AggregateType type, List<AccumulatorWrapper<RowT>> 
accs, Object[] output, ImmutableBitSet allFields, byte groupId) {
-        int cardinality = allFields.cardinality();
-
+    public void writeTo(
+            AggregateType type,
+            List<AccumulatorWrapper<RowT>> accs,
+            Object[] output,
+            int offset,
+            ImmutableBitSet groupFields,
+            byte groupId
+    ) {
         AccumulatorsState result = new AccumulatorsState(accs.size());
 
         for (int i = 0; i < accs.size(); i++) {
@@ -104,6 +111,8 @@ public class AggregateRow<RowT> {
             result.setIndex(i);
 
             if (acc.isDistinct()) {
+                assert !acc.isGrouping();
+
                 Set<Object> distinctSet = distinctSets.get(i);
 
                 for (var arg : distinctSet) {
@@ -113,7 +122,7 @@ public class AggregateRow<RowT> {
 
             acc.accumulator().end(state, result);
 
-            output[i + cardinality] = acc.convertResult(result.get());
+            output[i + offset] = acc.convertResult(result.get());
 
             state.resetIndex();
             result.resetIndex();
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashAggregateNode.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashAggregateNode.java
index 5183d53f88c..7aecd02af47 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashAggregateNode.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashAggregateNode.java
@@ -257,7 +257,7 @@ public class HashAggregateNode<RowT> extends 
AbstractNode<RowT> implements Singl
             GroupKey grpKey = b.build();
 
             AggregateRow<RowT> aggRow = groups.computeIfAbsent(grpKey, k -> 
create());
-            aggRow.update(accs, allFields, handler, row);
+            aggRow.update(accs, grpFields, handler, row);
         }
 
         /**
@@ -287,7 +287,7 @@ public class HashAggregateNode<RowT> extends 
AbstractNode<RowT> implements Singl
                     fields[j++] = grpFields.get(field) ? grpKey.field(k++) : 
null;
                 }
 
-                aggRow.writeTo(type, accs, fields, allFields, grpId);
+                aggRow.writeTo(type, accs, fields, allFields.cardinality(), 
grpFields, grpId);
 
                 RowT row = rowFactory.create(fields);
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortAggregateNode.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortAggregateNode.java
index 20b69855f6b..9d6606de4ba 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortAggregateNode.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortAggregateNode.java
@@ -277,7 +277,7 @@ public class SortAggregateNode<RowT> extends 
AbstractNode<RowT> implements Singl
                 fields[i++] = grpKey;
             }
 
-            aggRow.writeTo(type, accs, fields, grpSet, 
AggregateRow.NO_GROUP_ID);
+            aggRow.writeTo(type, accs, fields, grpSet.cardinality(), grpSet, 
AggregateRow.NO_GROUP_ID);
 
             return rowFactory.create(fields);
         }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
index c777e66bdbc..bb5bb0d9f8f 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
@@ -1229,7 +1229,7 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     }
 
     private void validateAggregateFunction(SqlCall call, SqlAggFunction 
aggFunction) {
-        if (!SqlKind.AGGREGATE.contains(aggFunction.kind)) {
+        if (!aggFunction.isAggregator()) {
             throw newValidationError(call,
                     
IgniteResource.INSTANCE.unsupportedAggregationFunction(aggFunction.getName()));
         }
@@ -1247,6 +1247,12 @@ public class IgniteSqlValidator extends SqlValidatorImpl 
{
             case MAX:
             case ANY_VALUE:
 
+                return;
+            case GROUPING:
+                if (call.operandCount() > 63) {
+                    // Function result of BIGINT can fit only bitmask of 
length less than 64;
+                    throw newValidationError(call, 
IgniteResource.INSTANCE.invalidArgCount(aggFunction.getName(), 1, 63));
+                }
                 return;
             default:
                 throw newValidationError(call,
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/MapReduceAggregates.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/MapReduceAggregates.java
index b3d0914aebc..a2b430c0b2f 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/MapReduceAggregates.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/MapReduceAggregates.java
@@ -71,7 +71,8 @@ public class MapReduceAggregates {
             "ANY",
             "AVG",
             "SINGLE_VALUE",
-            "ANY_VALUE"
+            "ANY_VALUE",
+            "GROUPING"
     );
 
     /**
@@ -364,6 +365,8 @@ public class MapReduceAggregates {
                 return createCountAgg(call, reduceArgumentOffset);
             case "AVG":
                 return createAvgAgg(cluster, call, reduceArgumentOffset, 
input, canBeNull);
+            case "GROUPING":
+                return createGroupingAgg(call, reduceArgumentOffset);
             default:
                 return createSimpleAgg(call, reduceArgumentOffset);
         }
@@ -487,6 +490,28 @@ public class MapReduceAggregates {
         return new MapReduceAgg(argList, call, reduceCall, USE_INPUT_FIELD);
     }
 
+    private static MapReduceAgg createGroupingAgg(AggregateCall call, int 
reduceArgumentOffset) {
+        IntList argList = IntList.of(reduceArgumentOffset);
+
+        AggregateCall reduceCall = AggregateCall.create(
+                SqlStdOperatorTable.SINGLE_VALUE,
+                call.isDistinct(),
+                call.isApproximate(),
+                call.ignoreNulls(),
+                ImmutableList.of(),
+                argList,
+                // there is no filtering on REDUCE phase
+                -1,
+                null,
+                call.collation,
+                call.type,
+                "GROUPING" + reduceArgumentOffset);
+
+        // For aggregate that use the same aggregate function for both MAP and 
REDUCE phases
+        // use the result of an aggregate as is.
+        return new MapReduceAgg(argList, call, reduceCall, USE_INPUT_FIELD);
+    }
+
     /**
      * Produces intermediate expressions that modify results of MAP/REDUCE 
aggregate.
      * For example: after splitting a function into a MAP aggregate and REDUCE 
aggregate it is necessary to add casts to
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
index f88f433de2a..dd8edff3533 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/fun/IgniteSqlOperatorTable.java
@@ -544,6 +544,7 @@ public class IgniteSqlOperatorTable extends 
ReflectiveSqlOperatorTable {
         definedOperatorsBuilder.add(SqlStdOperatorTable.ANY_VALUE);
         definedOperatorsBuilder.add(SqlStdOperatorTable.SINGLE_VALUE);
         definedOperatorsBuilder.add(SqlStdOperatorTable.FILTER);
+        definedOperatorsBuilder.add(SqlStdOperatorTable.GROUPING);
 
         definedOperatorsBuilder.add(EVERY);
         definedOperatorsBuilder.add(SOME);
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
index 54029aa695d..e3840f40445 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
@@ -114,6 +114,11 @@ public interface IgniteResource {
     @BaseMessage("Timestamp literal ''{0}'' out of range.")
     ExInst<SqlValidatorException> timestampLiteralOutOfRange(String typeName);
 
+    @BaseMessage(
+            "Invalid number of arguments to function ''{0}''. Was expecting 
number of arguments in range [{1,number,#}, {2,number,#}]."
+    )
+    ExInst<SqlValidatorException> invalidArgCount(String functionName, int 
min, int max);
+
     /** Constructs a signature string to use in error messages. */
     static String makeSignature(SqlCallBinding binding, RelDataType... 
operandTypes) {
         return makeSignature(binding, Arrays.asList(operandTypes));
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/docs/OperatorListTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/docs/OperatorListTest.java
index 42e88c8e9bf..812563e8bb7 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/docs/OperatorListTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/docs/OperatorListTest.java
@@ -271,6 +271,7 @@ public class OperatorListTest extends 
BaseIgniteAbstractTest {
         ops.add(SqlStdOperatorTable.EVERY);
         ops.add(SqlStdOperatorTable.SOME);
 
+        ops.add(SqlStdOperatorTable.GROUPING);
         ops.internal(SqlInternalOperators.LITERAL_AGG);
 
         return ops;
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/GroupingAccumulatorTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/GroupingAccumulatorTest.java
new file mode 100644
index 00000000000..85476a6fda7
--- /dev/null
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/agg/GroupingAccumulatorTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.ignite.internal.sql.engine.exec.exp.agg;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.ImmutableBitSet;
+import 
org.apache.ignite.internal.sql.engine.exec.exp.agg.Accumulators.GroupingAccumulator;
+import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@code GROUPING} accumulator function.
+ */
+public class GroupingAccumulatorTest {
+
+    @Test
+    public void testFactory() {
+        assertThrows(AssertionError.class,
+                () -> GroupingAccumulator.newAccumulator(List.of()).get(),
+                "GROUPING function must have at least one argument"
+        );
+        assertThrows(AssertionError.class,
+                () -> GroupingAccumulator.newAccumulator(IntStream.range(0, 
64).boxed().collect(Collectors.toList())).get(),
+                "GROUPING function with more than 63 arguments is not 
supported"
+        );
+
+        assertNotNull(GroupingAccumulator.newAccumulator(IntStream.range(0, 
63).boxed().collect(Collectors.toList())).get());
+    }
+
+    @Test
+    public void emptyState() {
+        Accumulator acc = newCall(List.of(0));
+        AccumulatorsState result = newState();
+
+        assertThrows(AssertionError.class, () -> acc.end(newState(), result));
+
+        assertNull(result.get());
+    }
+
+    @Test
+    public void accumulatorIgnoresValues() {
+        Accumulator acc = newCall(List.of(0));
+
+        AccumulatorsState state = newState();
+
+        
assertEquals(IgniteTypeFactory.INSTANCE.createSqlType(SqlTypeName.BIGINT), 
acc.returnType(IgniteTypeFactory.INSTANCE));
+        assertThrows(AssertionError.class, () -> acc.add(state, new 
Object[]{1}));
+
+        assertFalse(state.hasValue());
+    }
+
+    @Test
+    public void callWithSingleArgument() {
+        Accumulator acc = newCall(List.of(1));
+
+        // Empty group
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of()), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(0L, result.get());
+        }
+
+        // Aggregate by single column
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(1)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(1L, result.get());
+        }
+
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(0)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(0L, result.get());
+        }
+
+        // Aggregate by multiple columns
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(0, 1)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(1L, result.get());
+        }
+    }
+
+    @Test
+    public void callWithMultipleArguments() {
+        Accumulator acc = newCall(List.of(3, 2));
+
+        // Empty group
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of()), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(0L, result.get());
+        }
+
+        // Aggregate by single column
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(1)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(0L, result.get());
+        }
+
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(2)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(1L, result.get());
+        }
+
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(3)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(2L, result.get());
+        }
+
+        // Aggregate by multiple columns
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(1, 2)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(1L, result.get());
+        }
+
+        {
+            AccumulatorsState result = newState();
+            acc.end(newState(ImmutableBitSet.of(2, 3)), result);
+
+            assertTrue(result.hasValue());
+            assertEquals(3L, result.get());
+        }
+    }
+
+    private Accumulator newCall(List<Integer> columns) {
+        return GroupingAccumulator.newAccumulator(columns).get();
+    }
+
+    private static AccumulatorsState newState() {
+        AccumulatorsState state = new AccumulatorsState(1);
+        state.setIndex(0);
+        return state;
+    }
+
+    private static AccumulatorsState newState(Object arg) {
+        AccumulatorsState state = new AccumulatorsState(1);
+        state.setIndex(0);
+        state.set(arg);
+        return state;
+    }
+}
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
index 5c425342f6d..c916cd48e68 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractAggregatePlannerTest.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.sql.engine.planner;
 
 import static 
org.apache.ignite.internal.sql.engine.trait.IgniteDistributions.single;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -948,6 +949,34 @@ public abstract class AbstractAggregatePlannerTest extends 
AbstractPlannerTest {
          */
         CASE_27C("MERGE INTO test as t0 USING test as t1 ON t0.id = t1.id "
                 + "WHEN MATCHED THEN UPDATE SET val1 = (SELECT val0 FROM 
test)", schema(single())),
+
+        /**
+         * Query: SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0)).
+         *
+         * <p>Distribution single()
+         */
+        CASE_28_1A("SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0))", schema(single())),
+
+        /**
+         * Query: SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0), (grp1)).
+         *
+         * <p>Distribution single()
+         */
+        CASE_28_1B("SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0), (grp1))", schema(single())),
+
+        /**
+         * Query: SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0)).
+         *
+         * <p>Distribution hash(1)
+         */
+        CASE_28_2A("SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0))", schema(hash(1))),
+
+        /**
+         * Query: SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0), (grp1)).
+         *
+         * <p>Distribution hash(1)
+         */
+        CASE_28_2B("SELECT GROUPING(grp0) from test GROUP BY GROUPING SETS 
((grp0), (grp1))", schema(hash(1))),
         ;
 
         final String query;
@@ -1000,8 +1029,10 @@ public abstract class AbstractAggregatePlannerTest 
extends AbstractPlannerTest {
         assertPlan(testCase.query, Collections.singleton(testCase.schema), 
predicate, List.of(), rulesToDisable);
     }
 
-    protected void assumeRun(String methodName, TestCase testCase) {
-        missedCases.remove(testCase);
+    protected void assumeRun(TestCase testCase) {
+        boolean removed = missedCases.remove(testCase);
+
+        assertTrue(removed, "Unapplicable/Ignored test case was unexpectedly 
run.");
     }
 
     @SafeVarargs
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java
index cfbf3d58469..52830a5db49 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AggregatePlannerTest.java
@@ -577,6 +577,18 @@ public class AggregatePlannerTest extends 
AbstractAggregatePlannerTest {
         
checkDerivedCollationWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_26A);
     }
 
+    /**
+     * Validates a plan for a query with GROUPING aggregate.
+     */
+    @Test
+    public void groupsWithGroupingAggregate() throws Exception {
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1A);
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2A);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2B);
+    }
+
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
         checkSimpleAggSingle(testCase, hasAggregate());
     }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java
index c70c505d273..29ae9c01366 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedHashAggregatePlannerTest.java
@@ -527,6 +527,18 @@ public class ColocatedHashAggregatePlannerTest extends 
AbstractAggregatePlannerT
         
checkDerivedCollationWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_26A);
     }
 
+    /**
+     * Validates a plan for a query with GROUPING aggregate.
+     */
+    @Test
+    public void groupsWithGroupingAggregate() throws Exception {
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1A);
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2A);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2B);
+    }
+
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
         checkSimpleAggSingle(testCase, hasAggregate());
     }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java
index 587cc13b35b..d5bade7f839 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/ColocatedSortAggregatePlannerTest.java
@@ -521,6 +521,18 @@ public class ColocatedSortAggregatePlannerTest extends 
AbstractAggregatePlannerT
         
checkDerivedCollationWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_26A);
     }
 
+    /**
+     * Validates a plan for a query with GROUPING aggregate.
+     */
+    @Test
+    public void groupsWithGroupingAggregate() throws Exception {
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1A);
+        assumeRun(TestCase.CASE_28_1B); // No collation can be utilized for 
the case if multiple group sets.
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2A);
+        assumeRun(TestCase.CASE_28_2B); // No collation can be utilized for 
the case if multiple group sets.
+    }
+
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
         checkSimpleAggSingle(testCase, hasAggregate());
     }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java
index 4a114fd5c41..6b4f846600a 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceHashAggregatePlannerTest.java
@@ -573,6 +573,18 @@ public class MapReduceHashAggregatePlannerTest extends 
AbstractAggregatePlannerT
         
checkDerivedCollationWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_26A);
     }
 
+    /**
+     * Validates a plan for a query with GROUPING aggregate.
+     */
+    @Test
+    public void groupsWithGroupingAggregate() throws Exception {
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1A);
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1B);
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2A);
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2B);
+    }
+
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
         checkSimpleAggSingle(testCase, hasAggregate());
     }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java
index f8754c671e7..464ccb27f48 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/MapReduceSortAggregatePlannerTest.java
@@ -581,6 +581,18 @@ public class MapReduceSortAggregatePlannerTest extends 
AbstractAggregatePlannerT
         
checkDerivedCollationWithOrderBySubsetOfGroupColumnsHash(TestCase.CASE_26A);
     }
 
+    /**
+     * Validates a plan for a query with GROUPING aggregate.
+     */
+    @Test
+    public void groupsWithGroupingAggregate() throws Exception {
+        checkSimpleAggWithGroupBySingle(TestCase.CASE_28_1A);
+        assumeRun(TestCase.CASE_28_1B); // No collation can be utilized for 
the case if multiple group sets.
+
+        checkSimpleAggWithGroupByHash(TestCase.CASE_28_2A);
+        assumeRun(TestCase.CASE_28_2B); // No collation can be utilized for 
the case if multiple group sets.
+    }
+
     private void checkSimpleAggSingle(TestCase testCase) throws Exception {
         checkSimpleAggSingle(testCase, hasAggregate());
     }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TpcdsQueryPlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TpcdsQueryPlannerTest.java
index d1f989a4207..4b3905a4fce 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TpcdsQueryPlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/TpcdsQueryPlannerTest.java
@@ -45,10 +45,8 @@ public class TpcdsQueryPlannerTest extends 
AbstractTpcQueryPlannerTest {
     private static final IntSet UNSUPPORTED_TESTS = IntSet.of(
             // TODO https://issues.apache.org/jira/browse/IGNITE-14642 Support 
STDDEV_SAMP function and unmute tests.
             17, 29, 35, 39,
-            // TODO https://issues.apache.org/jira/browse/IGNITE-25872 Support 
GROUPING function and unmute tests.
-            27, 36, 86,
             // TODO https://issues.apache.org/jira/browse/IGNITE-25873 Support 
aggregate window function RANK and unmute tests.
-            44, 47, 49, 57, 67, 70
+            44, 47, 49, 57, 67, 70, 36, 86
     );
 
     @ParameterizedTest
diff --git a/modules/sql-engine/src/test/resources/docs/operator_list.txt 
b/modules/sql-engine/src/test/resources/docs/operator_list.txt
index 51aac5d03d1..9411a7a2b84 100644
--- a/modules/sql-engine/src/test/resources/docs/operator_list.txt
+++ b/modules/sql-engine/src/test/resources/docs/operator_list.txt
@@ -378,6 +378,9 @@ EVERY(<boolean>)
 SOME(<boolean>)
 #014b53be23fc2009080420e94b78d52bf2b09dbd
 
+GROUPING(...)
+#c19a178a82716bcc5f313ca5259637dd60c8ba0e
+
 [internal]
 LITERAL_AGG()
 #6d021f1dd7329dc18c22767552769bc90f27b7ac
diff --git a/modules/sql-engine/src/test/resources/tpcds/plan/q27.plan 
b/modules/sql-engine/src/test/resources/tpcds/plan/q27.plan
new file mode 100644
index 00000000000..1ce5258a1f7
--- /dev/null
+++ b/modules/sql-engine/src/test/resources/tpcds/plan/q27.plan
@@ -0,0 +1,68 @@
+Sort
+    collation: [I_ITEM_ID ASC, S_STATE ASC]
+    fetch: 100
+    est: (rows=100)
+  ColocatedHashAggregate
+      fieldNames: [I_ITEM_ID, S_STATE, G_STATE, AGG1, AGG2, AGG3, AGG4]
+      group: [I_ITEM_ID, S_STATE]
+      groupSets: [[I_ITEM_ID, S_STATE], [I_ITEM_ID], []]
+      aggregation: [GROUPING(S_STATE), AVG(SS_QUANTITY), AVG(SS_LIST_PRICE), 
AVG(SS_COUPON_AMT), AVG(SS_SALES_PRICE)]
+      est: (rows=12751)
+    Project
+        fieldNames: [I_ITEM_ID, S_STATE, SS_QUANTITY, SS_LIST_PRICE, 
SS_COUPON_AMT, SS_SALES_PRICE]
+        projection: [I_ITEM_ID, S_STATE, SS_QUANTITY, SS_LIST_PRICE, 
SS_COUPON_AMT, SS_SALES_PRICE]
+        est: (rows=35419)
+      HashJoin
+          predicate: =(SS_STORE_SK, S_STORE_SK)
+          type: inner
+          est: (rows=35419)
+        HashJoin
+            predicate: =(SS_ITEM_SK, I_ITEM_SK)
+            type: inner
+            est: (rows=35419)
+          HashJoin
+              predicate: =(SS_SOLD_DATE_SK, D_DATE_SK)
+              type: inner
+              est: (rows=35419)
+            HashJoin
+                predicate: =(SS_CDEMO_SK, CD_DEMO_SK)
+                type: inner
+                est: (rows=106362)
+              Exchange
+                  distribution: single
+                  est: (rows=2880404)
+                TableScan
+                    table: PUBLIC.STORE_SALES
+                    fieldNames: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CDEMO_SK, 
SS_STORE_SK, SS_QUANTITY, SS_LIST_PRICE, SS_SALES_PRICE, SS_COUPON_AMT]
+                    est: (rows=2880404)
+              Exchange
+                  distribution: single
+                  est: (rows=70928)
+                TableScan
+                    table: PUBLIC.CUSTOMER_DEMOGRAPHICS
+                    predicate: AND(=(CD_GENDER, _UTF-8'F'), 
=(CD_MARITAL_STATUS, _UTF-8'U'), =(CD_EDUCATION_STATUS, _UTF-8'2 yr Degree'))
+                    fieldNames: [CD_DEMO_SK, CD_GENDER, CD_MARITAL_STATUS, 
CD_EDUCATION_STATUS]
+                    est: (rows=70928)
+            Exchange
+                distribution: single
+                est: (rows=24325)
+              TableScan
+                  table: PUBLIC.DATE_DIM
+                  predicate: =(D_YEAR, 2000)
+                  fieldNames: [D_DATE_SK, D_YEAR]
+                  est: (rows=24325)
+          Exchange
+              distribution: single
+              est: (rows=18000)
+            TableScan
+                table: PUBLIC.ITEM
+                fieldNames: [I_ITEM_SK, I_ITEM_ID]
+                est: (rows=18000)
+        Exchange
+            distribution: single
+            est: (rows=12)
+          TableScan
+              table: PUBLIC.STORE
+              predicate: SEARCH(S_STATE, Sarg[_UTF-8'AL':VARCHAR(2) CHARACTER 
SET "UTF-8", _UTF-8'FL':VARCHAR(2) CHARACTER SET "UTF-8", _UTF-8'IN':VARCHAR(2) 
CHARACTER SET "UTF-8", _UTF-8'NY':VARCHAR(2) CHARACTER SET "UTF-8", 
_UTF-8'OH':VARCHAR(2) CHARACTER SET "UTF-8", _UTF-8'SC':VARCHAR(2) CHARACTER 
SET "UTF-8"]:VARCHAR(2) CHARACTER SET "UTF-8")
+              fieldNames: [S_STORE_SK, S_STATE]
+              est: (rows=12)
diff --git 
a/modules/sql-engine/src/test/resources/tpcds/plan/q27_colocated.plan 
b/modules/sql-engine/src/test/resources/tpcds/plan/q27_colocated.plan
new file mode 100644
index 00000000000..1ce5258a1f7
--- /dev/null
+++ b/modules/sql-engine/src/test/resources/tpcds/plan/q27_colocated.plan
@@ -0,0 +1,68 @@
+Sort
+    collation: [I_ITEM_ID ASC, S_STATE ASC]
+    fetch: 100
+    est: (rows=100)
+  ColocatedHashAggregate
+      fieldNames: [I_ITEM_ID, S_STATE, G_STATE, AGG1, AGG2, AGG3, AGG4]
+      group: [I_ITEM_ID, S_STATE]
+      groupSets: [[I_ITEM_ID, S_STATE], [I_ITEM_ID], []]
+      aggregation: [GROUPING(S_STATE), AVG(SS_QUANTITY), AVG(SS_LIST_PRICE), 
AVG(SS_COUPON_AMT), AVG(SS_SALES_PRICE)]
+      est: (rows=12751)
+    Project
+        fieldNames: [I_ITEM_ID, S_STATE, SS_QUANTITY, SS_LIST_PRICE, 
SS_COUPON_AMT, SS_SALES_PRICE]
+        projection: [I_ITEM_ID, S_STATE, SS_QUANTITY, SS_LIST_PRICE, 
SS_COUPON_AMT, SS_SALES_PRICE]
+        est: (rows=35419)
+      HashJoin
+          predicate: =(SS_STORE_SK, S_STORE_SK)
+          type: inner
+          est: (rows=35419)
+        HashJoin
+            predicate: =(SS_ITEM_SK, I_ITEM_SK)
+            type: inner
+            est: (rows=35419)
+          HashJoin
+              predicate: =(SS_SOLD_DATE_SK, D_DATE_SK)
+              type: inner
+              est: (rows=35419)
+            HashJoin
+                predicate: =(SS_CDEMO_SK, CD_DEMO_SK)
+                type: inner
+                est: (rows=106362)
+              Exchange
+                  distribution: single
+                  est: (rows=2880404)
+                TableScan
+                    table: PUBLIC.STORE_SALES
+                    fieldNames: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CDEMO_SK, 
SS_STORE_SK, SS_QUANTITY, SS_LIST_PRICE, SS_SALES_PRICE, SS_COUPON_AMT]
+                    est: (rows=2880404)
+              Exchange
+                  distribution: single
+                  est: (rows=70928)
+                TableScan
+                    table: PUBLIC.CUSTOMER_DEMOGRAPHICS
+                    predicate: AND(=(CD_GENDER, _UTF-8'F'), 
=(CD_MARITAL_STATUS, _UTF-8'U'), =(CD_EDUCATION_STATUS, _UTF-8'2 yr Degree'))
+                    fieldNames: [CD_DEMO_SK, CD_GENDER, CD_MARITAL_STATUS, 
CD_EDUCATION_STATUS]
+                    est: (rows=70928)
+            Exchange
+                distribution: single
+                est: (rows=24325)
+              TableScan
+                  table: PUBLIC.DATE_DIM
+                  predicate: =(D_YEAR, 2000)
+                  fieldNames: [D_DATE_SK, D_YEAR]
+                  est: (rows=24325)
+          Exchange
+              distribution: single
+              est: (rows=18000)
+            TableScan
+                table: PUBLIC.ITEM
+                fieldNames: [I_ITEM_SK, I_ITEM_ID]
+                est: (rows=18000)
+        Exchange
+            distribution: single
+            est: (rows=12)
+          TableScan
+              table: PUBLIC.STORE
+              predicate: SEARCH(S_STATE, Sarg[_UTF-8'AL':VARCHAR(2) CHARACTER 
SET "UTF-8", _UTF-8'FL':VARCHAR(2) CHARACTER SET "UTF-8", _UTF-8'IN':VARCHAR(2) 
CHARACTER SET "UTF-8", _UTF-8'NY':VARCHAR(2) CHARACTER SET "UTF-8", 
_UTF-8'OH':VARCHAR(2) CHARACTER SET "UTF-8", _UTF-8'SC':VARCHAR(2) CHARACTER 
SET "UTF-8"]:VARCHAR(2) CHARACTER SET "UTF-8")
+              fieldNames: [S_STORE_SK, S_STATE]
+              est: (rows=12)

Reply via email to