[NO ISSUE][COMP] Support GROUPING SETS, ROLLUP, CUBE

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Implement support for GROUPING SETS, ROLLUP, CUBE in
  GROUP BY clause, including GROUPING() operation
- Modify OptimizerTest to account for different variable id bases
  when comparing actual query plan with expected one
- Add RQG testsuite for grouping sets and regular testcases

Change-Id: I540ae172b9904e869f89f501e192dc83f3ea2550
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/5426
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
index 6550bb4..2706ac3 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
@@ -967,14 +967,26 @@
             topOp = new MutableObject<>(groupRecordVarAssignOp);
         }
 
+        boolean propagateHashHint = true;
+
         GroupByOperator gOp = new GroupByOperator();
-        for (GbyVariableExpressionPair ve : gc.getGbyPairList()) {
-            VariableExpr vexpr = ve.getVar();
-            LogicalVariable v = vexpr == null ? context.newVar() : context.newVarFromExpression(vexpr);
-            Pair<ILogicalExpression, Mutable<ILogicalOperator>> eo = langExprToAlgExpression(ve.getExpr(), topOp);
-            gOp.addGbyExpression(v, eo.first);
-            topOp = eo.second;
+        if (!gc.isGroupAll()) {
+            List<GbyVariableExpressionPair> groupingSet = getSingleGroupingSet(gc);
+            if (groupingSet.isEmpty()) {
+                gOp.addGbyExpression(context.newVar(), ConstantExpression.TRUE);
+                propagateHashHint = false;
+            } else {
+                for (GbyVariableExpressionPair ve : groupingSet) {
+                    VariableExpr vexpr = ve.getVar();
+                    LogicalVariable v = vexpr == null ? context.newVar() : context.newVarFromExpression(vexpr);
+                    Pair<ILogicalExpression, Mutable<ILogicalOperator>> eo =
+                            langExprToAlgExpression(ve.getExpr(), topOp);
+                    gOp.addGbyExpression(v, eo.first);
+                    topOp = eo.second;
+                }
+            }
         }
+
         if (gc.hasDecorList()) {
             for (GbyVariableExpressionPair ve : gc.getDecorPairList()) {
                 VariableExpr vexpr = ve.getVar();
@@ -1017,11 +1029,23 @@
         }
 
         gOp.setGroupAll(gc.isGroupAll());
-        gOp.getAnnotations().put(OperatorAnnotations.USE_HASH_GROUP_BY, gc.hasHashGroupByHint());
+        if (propagateHashHint) {
+            gOp.getAnnotations().put(OperatorAnnotations.USE_HASH_GROUP_BY, gc.hasHashGroupByHint());
+        }
         gOp.setSourceLocation(sourceLoc);
         return new Pair<>(gOp, null);
     }
 
+    protected List<GbyVariableExpressionPair> getSingleGroupingSet(GroupbyClause gby) throws CompilationException {
+        List<List<GbyVariableExpressionPair>> groupingSetList = gby.getGbyPairList();
+        if (groupingSetList.size() != 1) {
+            // should've been rewritten by SqlppGroupingSetsVisitor
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, gby.getSourceLocation(),
+                    String.valueOf(groupingSetList.size()));
+        }
+        return groupingSetList.get(0);
+    }
+
     protected AbstractFunctionCallExpression createRecordConstructor(List<Pair<Expression, Identifier>> fieldList,
             Mutable<ILogicalOperator> inputOp, SourceLocation sourceLoc) throws CompilationException {
         List<Mutable<ILogicalExpression>> args = new ArrayList<>();
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
index 82dc344..59642cb 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SqlppExpressionToPlanTranslator.java
@@ -849,8 +849,13 @@
     // Generates all field bindings according to the from clause.
     private void getGroupBindings(GroupbyClause groupbyClause, List<FieldBinding> outFieldBindings,
             Set<String> outFieldNames) throws CompilationException {
-        for (GbyVariableExpressionPair pair : groupbyClause.getGbyPairList()) {
-            outFieldBindings.add(getFieldBinding(pair.getVar(), outFieldNames));
+        Set<VariableExpr> gbyKeyVars = new HashSet<>();
+        List<GbyVariableExpressionPair> groupingSet = getSingleGroupingSet(groupbyClause);
+        for (GbyVariableExpressionPair pair : groupingSet) {
+            VariableExpr var = pair.getVar();
+            if (gbyKeyVars.add(var)) {
+                outFieldBindings.add(getFieldBinding(var, outFieldNames));
+            }
         }
         if (groupbyClause.hasGroupVar()) {
             outFieldBindings.add(getFieldBinding(groupbyClause.getGroupVar(), outFieldNames));
diff --git a/asterixdb/asterix-app/pom.xml b/asterixdb/asterix-app/pom.xml
index e39bb1f..392dbd8 100644
--- a/asterixdb/asterix-app/pom.xml
+++ b/asterixdb/asterix-app/pom.xml
@@ -158,6 +158,7 @@
           <ignoredUnusedDeclaredDependencies>
             <ignoredUnusedDeclaredDependency>org.apache.asterix:asterix-external-data:zip:*</ignoredUnusedDeclaredDependency>
             <ignoredUnusedDeclaredDependency>org.apache.asterix:asterix-external-data:test-jar:*</ignoredUnusedDeclaredDependency>
+            <ignoredUnusedDeclaredDependency>org.postgresql:postgresql:jar:*</ignoredUnusedDeclaredDependency>
           </ignoredUnusedDeclaredDependencies>
         </configuration>
       </plugin>
@@ -326,6 +327,14 @@
       </properties>
     </profile>
     <profile>
+      <id>asterix-gerrit-asterix-app-sql-rqg</id>
+      <properties>
+        <test.excludes>**/*.java</test.excludes>
+        <itest.includes>**/SqlppRQG*IT.java</itest.includes>
+        <failIfNoTests>false</failIfNoTests>
+      </properties>
+    </profile>
+    <profile>
       <id>asterix-gerrit-ssl-compression</id>
       <properties>
         <test.includes>**/*Compression*Test.java,**/*Ssl*Test.java</test.includes>
@@ -337,7 +346,7 @@
       <id>asterix-gerrit-verify-asterix-app</id>
       <properties>
         <test.includes>**/AqlExecutionTest.java</test.includes>
-        <itest.excludes>**/SqlppExecution*IT.java,**/RebalanceWithCancellationIT.java</itest.excludes>
+        <itest.excludes>**/SqlppExecution*IT.java,**/SqlppRQG*IT.java,**/RebalanceWithCancellationIT.java</itest.excludes>
         <failIfNoTests>false</failIfNoTests>
       </properties>
     </profile>
@@ -677,6 +686,16 @@
       <artifactId>hyracks-storage-am-lsm-invertedindex</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.testcontainers</groupId>
+      <artifactId>postgresql</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.postgresql</groupId>
+      <artifactId>postgresql</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>com.teradata.tpcds</groupId>
       <artifactId>tpcds</artifactId>
       <version>1.2</version>
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
index 8dab64f..eb91675 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/common/ResultExtractor.java
@@ -35,6 +35,7 @@
 import org.apache.logging.log4j.Logger;
 
 import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.PrettyPrinter;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -220,26 +221,25 @@
                         } else if (fieldValue.isArray()) {
                             JsonNode oneElement = fieldValue.get(0);
                             if (oneElement.isTextual()) {
-                                resultBuilder.append(
-                                        isJsonFormat ? PP_WRITER.writeValueAsString(oneElement) : oneElement.asText());
+                                resultBuilder.append(isJsonFormat ? prettyPrint(oneElement) : oneElement.asText());
                             } else {
-                                resultBuilder.append(PP_WRITER.writeValueAsString(oneElement));
+                                resultBuilder.append(prettyPrint(oneElement));
                             }
                         } else {
-                            resultBuilder.append(PP_WRITER.writeValueAsString(fieldValue));
+                            resultBuilder.append(prettyPrint(fieldValue));
                         }
                     } else {
                         JsonNode[] fields = Iterators.toArray(fieldValue.elements(), JsonNode.class);
                         if (isJsonFormat) {
                             for (JsonNode f : fields) {
-                                resultBuilder.append(PP_WRITER.writeValueAsString(f)).append('\n');
+                                resultBuilder.append(prettyPrint(f)).append('\n');
                             }
                         } else {
                             for (JsonNode f : fields) {
                                 if (f.isValueNode()) {
                                     resultBuilder.append(f.asText());
                                 } else {
-                                    resultBuilder.append(PP_WRITER.writeValueAsString(f)).append('\n');
+                                    resultBuilder.append(prettyPrint(f)).append('\n');
                                 }
                             }
                         }
@@ -275,6 +275,10 @@
         return extractedResult;
     }
 
+    public static String prettyPrint(JsonNode node) throws JsonProcessingException {
+        return PP_WRITER.writeValueAsString(node);
+    }
+
     private static void checkForErrors(ObjectNode result) throws Exception {
         final JsonNode errorsField = result.get(ResultField.ERRORS.getFieldName());
         if (errorsField != null) {
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/optimizer/OptimizerTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/optimizer/OptimizerTest.java
index 6e0413c..c3dc821 100644
--- a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/optimizer/OptimizerTest.java
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/optimizer/OptimizerTest.java
@@ -18,17 +18,19 @@
  */
 package org.apache.asterix.test.optimizer;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.apache.asterix.api.common.AsterixHyracksIntegrationUtil;
 import org.apache.asterix.api.java.AsterixJavaClient;
@@ -93,6 +95,9 @@
 
     protected static AsterixHyracksIntegrationUtil integrationUtil = new AsterixHyracksIntegrationUtil();
 
+    private static final String PATTERN_VAR_ID_PREFIX = "\\$\\$";
+    private static final Pattern PATTERN_VAR_ID = Pattern.compile(PATTERN_VAR_ID_PREFIX + "(\\d+)");
+
     @BeforeClass
     public static void setUp() throws Exception {
         final File outdir = new File(PATH_ACTUAL);
@@ -205,37 +210,36 @@
                 throw new Exception("Compile ERROR for " + queryFile + ": " + e.getMessage(), e);
             }
 
-            BufferedReader readerExpected =
-                    new BufferedReader(new InputStreamReader(new FileInputStream(expectedFile), "UTF-8"));
-            BufferedReader readerActual =
-                    new BufferedReader(new InputStreamReader(new FileInputStream(actualFile), "UTF-8"));
+            List<String> linesExpected = Files.readAllLines(expectedFile.toPath(), StandardCharsets.UTF_8);
+            List<String> linesActual = Files.readAllLines(actualFile.toPath(), StandardCharsets.UTF_8);
 
+            int varBaseExpected = findBaseVarId(linesExpected);
+            int varBaseActual = findBaseVarId(linesActual);
+
+            Iterator<String> readerExpected = linesExpected.iterator();
+            Iterator<String> readerActual = linesActual.iterator();
             String lineExpected, lineActual;
             int num = 1;
-            try {
-                while ((lineExpected = readerExpected.readLine()) != null) {
-                    lineActual = readerActual.readLine();
-                    if (lineActual == null) {
-                        throw new Exception("Result for " + queryFile + " changed at line " + num + ":\n< "
-                                + lineExpected + "\n> ");
-                    }
-                    if (!lineExpected.equals(lineActual)) {
-                        throw new Exception("Result for " + queryFile + " changed at line " + num + ":\n< "
-                                + lineExpected + "\n> " + lineActual);
-                    }
-                    ++num;
-                }
-                lineActual = readerActual.readLine();
-                if (lineActual != null) {
+            while (readerExpected.hasNext()) {
+                lineExpected = readerExpected.next();
+                if (!readerActual.hasNext()) {
                     throw new Exception(
-                            "Result for " + queryFile + " changed at line " + num + ":\n< \n> " + lineActual);
+                            "Result for " + queryFile + " changed at line " + num + ":\n< " + lineExpected + "\n> ");
                 }
-                LOGGER.info("Test \"" + queryFile.getPath() + "\" PASSED!");
-                actualFile.delete();
-            } finally {
-                readerExpected.close();
-                readerActual.close();
+                lineActual = readerActual.next();
+
+                if (!planLineEquals(lineExpected, varBaseExpected, lineActual, varBaseActual)) {
+                    throw new Exception("Result for " + queryFile + " changed at line " + num + ":\n< " + lineExpected
+                            + "\n> " + lineActual);
+                }
+                ++num;
             }
+            if (readerActual.hasNext()) {
+                throw new Exception(
+                        "Result for " + queryFile + " changed at line " + num + ":\n< \n> " + readerActual.next());
+            }
+            LOGGER.info("Test \"" + queryFile.getPath() + "\" PASSED!");
+            actualFile.delete();
         } catch (Exception e) {
             if (!(e instanceof AssumptionViolatedException)) {
                 LOGGER.error("Test \"" + queryFile.getPath() + "\" FAILED!");
@@ -245,4 +249,40 @@
             }
         }
     }
+
+    private boolean planLineEquals(String lineExpected, int varIdBaseExpected, String lineActual, int varIdBaseActual) {
+        String lineExpectedNorm = normalizePlanLine(lineExpected, varIdBaseExpected);
+        String lineActualNorm = normalizePlanLine(lineActual, varIdBaseActual);
+        return lineExpectedNorm.equals(lineActualNorm);
+    }
+
+    // rewrite variable ids in given plan line: $$varId -> $$(varId-varIdBase)
+    private String normalizePlanLine(String line, int varIdBase) {
+        if (varIdBase == Integer.MAX_VALUE) {
+            // plan did not contain any variables -> no rewriting necessary
+            return line;
+        }
+        Matcher m = PATTERN_VAR_ID.matcher(line);
+        StringBuffer sb = new StringBuffer(line.length());
+        while (m.find()) {
+            int varId = Integer.parseInt(m.group(1));
+            int newVarId = varId - varIdBase;
+            m.appendReplacement(sb, PATTERN_VAR_ID_PREFIX + newVarId);
+        }
+        m.appendTail(sb);
+        return sb.toString();
+    }
+
+    private int findBaseVarId(Collection<String> plan) {
+        int varIdBase = Integer.MAX_VALUE;
+        Matcher m = PATTERN_VAR_ID.matcher("");
+        for (String line : plan) {
+            m.reset(line);
+            while (m.find()) {
+                int varId = Integer.parseInt(m.group(1));
+                varIdBase = Math.min(varIdBase, varId);
+            }
+        }
+        return varIdBase;
+    }
 }
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java
new file mode 100644
index 0000000..838d980
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/runtime/SqlppRQGGroupingSetsIT.java
@@ -0,0 +1,613 @@
+/*
+ * 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.asterix.test.runtime;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.JDBCType;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.utils.Servlets;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.test.common.ExtractedResult;
+import org.apache.asterix.test.common.ResultExtractor;
+import org.apache.asterix.test.common.TestExecutor;
+import org.apache.asterix.test.common.TestHelper;
+import org.apache.asterix.testframework.context.TestCaseContext;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+// Prerequisite:
+// setenv TESTCONTAINERS_RYUK_DISABLED true
+
+@RunWith(Parameterized.class)
+public class SqlppRQGGroupingSetsIT {
+
+    private static final String CONF_PROPERTY_SEED = getConfigurationPropertyName("seed");
+
+    private static final long CONF_PROPERTY_SEED_DEFAULT = System.currentTimeMillis();
+
+    private static final String CONF_PROPERTY_LIMIT = getConfigurationPropertyName("limit");
+
+    private static final int CONF_PROPERTY_LIMIT_DEFAULT = 100;
+
+    private static final String TESTCONTAINERS_RYUK_DISABLED = "TESTCONTAINERS_RYUK_DISABLED";
+
+    private static final String TEST_CONFIG_FILE_NAME = "src/main/resources/cc.conf";
+
+    private static final String POSTGRES_IMAGE = "postgres:12.2";
+
+    private static final String TABLE_NAME = "tenk";
+
+    private static final Path TABLE_FILE = Paths.get("data", "tenk.tbl");
+
+    private static final char TABLE_FILE_COLUMN_SEPARATOR = '|';
+
+    private static final Path RESULT_OUTPUT_DIR = Paths.get("target", SqlppRQGGroupingSetsIT.class.getSimpleName());
+
+    private static final int ROLLUP_ELEMENT_LIMIT = 2;
+    private static final int CUBE_ELEMENT_LIMIT = 1;
+    private static final int GROUPING_SETS_ELEMENT_LIMIT = 2;
+    private static final int MULTI_ELEMENT_LIMIT = 2;
+
+    private static final String UNIQUE_1 = "unique1";
+    private static final String UNIQUE_2 = "unique2";
+    private static final String TWO = "two";
+    private static final String FOUR = "four";
+    private static final String TEN = "ten";
+    private static final String TWENTY = "twenty";
+    private static final String HUNDRED = "hundred";
+    private static final String THOUSAND = "thousand";
+    private static final String TWOTHOUSAND = "twothousand";
+    private static final String FIVETHOUS = "fivethous";
+    private static final String TENTHOUS = "tenthous";
+    private static final String ODD100 = "odd100";
+    private static final String EVEN100 = "even100";
+    private static final String STRINGU1 = "stringu1";
+    private static final String STRINGU2 = "stringu2";
+    private static final String STRING4 = "string4";
+
+    private static final List<String> GROUPBY_COLUMNS = Arrays.asList(TWO, FOUR, TEN, TWENTY, HUNDRED, ODD100, EVEN100);
+
+    private static final LinkedHashMap<String, JDBCType> TABLE_SCHEMA = createTableSchema();
+
+    private static final ObjectReader JSON_NODE_READER = new ObjectMapper().readerFor(JsonNode.class);
+
+    private static final Logger LOGGER = LogManager.getLogger(SqlppRQGGroupingSetsIT.class);
+
+    private static TestExecutor testExecutor;
+
+    private static PostgreSQLContainer<?> postgres;
+
+    private static Connection conn;
+
+    private static Statement stmt;
+
+    private final int testcaseId;
+
+    private final String sqlQuery;
+
+    private final String sqlppQuery;
+
+    private final String groupByClause;
+
+    @Parameters(name = "SqlppRQGGroupingSetsIT {index}: {3}")
+    public static Collection<Object[]> tests() {
+        List<Object[]> testCases = new ArrayList<>();
+
+        long seed = getLongConfigurationProperty(CONF_PROPERTY_SEED, CONF_PROPERTY_SEED_DEFAULT);
+        int limit = (int) getLongConfigurationProperty(CONF_PROPERTY_LIMIT, CONF_PROPERTY_LIMIT_DEFAULT);
+
+        LOGGER.info(String.format("Testsuite configuration: -D%s=%d -D%s=%d", CONF_PROPERTY_SEED, seed,
+                CONF_PROPERTY_LIMIT, limit));
+
+        Random random = new Random(seed);
+        for (int i = 0; i < limit; i++) {
+            TestQuery query = generateQuery(i, random);
+            testCases.add(new Object[] { i, query.sqlQuery, query.sqlppQuery, query.groupbyClause });
+        }
+
+        return testCases;
+    }
+
+    public SqlppRQGGroupingSetsIT(int testcaseId, String sqlQuery, String sqlppQuery, String groupByClause) {
+        this.testcaseId = testcaseId;
+        this.sqlQuery = sqlQuery;
+        this.sqlppQuery = sqlppQuery;
+        this.groupByClause = groupByClause;
+    }
+
+    @Test
+    public void test() throws Exception {
+        LOGGER.info(String.format("Starting testcase #%d: %s", testcaseId, groupByClause));
+
+        LOGGER.info("Running SQL");
+        LOGGER.info(sqlQuery);
+        stmt.execute(sqlQuery);
+        ArrayNode sqlResult;
+        try (ResultSet rs = stmt.getResultSet()) {
+            sqlResult = asJson(rs);
+        }
+
+        LOGGER.info("Running SQL++");
+        LOGGER.info(sqlppQuery);
+        ArrayNode sqlppResult;
+        try (InputStream resultStream = testExecutor.executeQueryService(sqlppQuery,
+                testExecutor.getEndpoint(Servlets.QUERY_SERVICE), TestCaseContext.OutputFormat.ADM)) {
+            sqlppResult = asJson(
+                    ResultExtractor.extract(resultStream, StandardCharsets.UTF_8, TestCaseContext.OutputFormat.ADM));
+        }
+
+        boolean eq = TestHelper.equalJson(sqlResult, sqlppResult, false);
+        if (!eq) {
+            File sqlResultFile = writeResult(sqlResult, "sql");
+            File sqlppResultFile = writeResult(sqlppResult, "sqlpp");
+
+            Assert.fail(String.format("Results do not match.\n%s\n%s", sqlResultFile.getCanonicalPath(),
+                    sqlppResultFile.getCanonicalPath()));
+        }
+    }
+
+    private static TestQuery generateQuery(int testcaseId, Random random) {
+        Set<String> allColumns = new LinkedHashSet<>();
+        List<String> groupingElements = new ArrayList<>();
+        int nElements = 1 + random.nextInt(3);
+
+        int rollupCount = 0, cubeCount = 0, groupingSetsCount = 0;
+        for (int i = 0; i < nElements; i++) {
+            String prefix;
+            int minItems, maxItems;
+            boolean allowSimpleSubgroup = false, allowComplexSubgroup = false;
+
+            switch (random.nextInt(6)) {
+                case 0:
+                case 1:
+                    prefix = "";
+                    minItems = 1;
+                    maxItems = 4;
+                    break;
+                case 2:
+                case 3:
+                    if (isSingleElementLimitReached(rollupCount, ROLLUP_ELEMENT_LIMIT)
+                            || isMultiElementLimitReached(rollupCount, cubeCount, groupingSetsCount)) {
+                        // skip this element
+                        nElements++;
+                        continue;
+                    }
+                    prefix = "ROLLUP";
+                    minItems = 1;
+                    maxItems = 4;
+                    allowSimpleSubgroup = true;
+                    rollupCount++;
+                    break;
+                case 4:
+                    if (isSingleElementLimitReached(cubeCount, CUBE_ELEMENT_LIMIT)
+                            || isMultiElementLimitReached(rollupCount, cubeCount, groupingSetsCount)) {
+                        // skip this element
+                        nElements++;
+                        continue;
+                    }
+                    prefix = "CUBE";
+                    minItems = 2;
+                    maxItems = 2;
+                    allowSimpleSubgroup = true; // allowed, not actually used, because we set maxItems to 2
+                    cubeCount++;
+                    break;
+                case 5:
+                    if (isSingleElementLimitReached(groupingSetsCount, GROUPING_SETS_ELEMENT_LIMIT)
+                            || isMultiElementLimitReached(rollupCount, cubeCount, groupingSetsCount)) {
+                        // skip this element
+                        nElements++;
+                        continue;
+                    }
+                    prefix = "GROUPING SETS";
+                    minItems = 0;
+                    maxItems = 3;
+                    allowSimpleSubgroup = allowComplexSubgroup = true;
+                    groupingSetsCount++;
+                    break;
+                default:
+                    throw new IllegalStateException();
+            }
+            int nItems = minItems + random.nextInt(maxItems - minItems + 1);
+            List<String> elementItems =
+                    nItems == 0 ? Collections.emptyList() : randomize(GROUPBY_COLUMNS, random).subList(0, nItems);
+            allColumns.addAll(elementItems);
+            if (allowSimpleSubgroup && nItems >= 3 && random.nextInt(2) == 0) {
+                makeSubgroup(elementItems, random, allowComplexSubgroup);
+            }
+            String elementItemsText = elementItems.isEmpty() ? "()" : String.join(",", elementItems);
+            String element = String.format("%s(%s)", prefix, elementItemsText);
+            groupingElements.add(element);
+        }
+
+        StringBuilder selectClause = new StringBuilder();
+        for (String col : allColumns) {
+            selectClause.append(col).append(',');
+        }
+        for (String col : allColumns) {
+            selectClause.append(String.format("GROUPING(%s) AS grp_%s", col, col)).append(',');
+        }
+        if (allColumns.size() > 1) {
+            selectClause.append(String.format("GROUPING(%s) AS grp", String.join(",", randomize(allColumns, random))))
+                    .append(',');
+        }
+        selectClause.append(String.format("SUM(%s) AS agg_sum", UNIQUE_1));
+
+        String groupingElementText = groupingElements.isEmpty() ? "()" : String.join(",", groupingElements);
+        String groupbyClause = String.format("GROUP BY %s", groupingElementText);
+
+        String orderbyClauseSql = generateOrderBy(allColumns, true);
+        String orderbyClauseSqlpp = generateOrderBy(allColumns, false);
+
+        String queryTemplate = "SELECT %s FROM %s %s %s";
+        String sqlQuery = String.format(queryTemplate, selectClause, TABLE_NAME, groupbyClause, orderbyClauseSql);
+        String sqlppQuery = String.format(queryTemplate, selectClause, TABLE_NAME, groupbyClause, orderbyClauseSqlpp);
+
+        LOGGER.info(String.format("Testcase #%d: %s", testcaseId, groupbyClause));
+
+        return new TestQuery(sqlQuery, sqlppQuery, groupbyClause);
+    }
+
+    private static boolean isSingleElementLimitReached(int elementCount, int limit) {
+        return elementCount >= limit;
+    }
+
+    private static boolean isMultiElementLimitReached(int elementCount1, int elementCount2, int elementCount3) {
+        return elementCount1 + elementCount2 + elementCount3 >= MULTI_ELEMENT_LIMIT;
+    }
+
+    private static String generateOrderBy(Set<String> allColumns, boolean insertNullsFirst) {
+        if (allColumns.isEmpty()) {
+            return "";
+        }
+        return "ORDER BY " + allColumns.stream().map(c -> c + (insertNullsFirst ? " NULLS FIRST" : ""))
+                .collect(Collectors.joining(", "));
+    }
+
+    private static void makeSubgroup(List<String> elementColumns, Random random, boolean allowComplexSubgroup) {
+        // rewrite (a, b, c, ... ) into (a,(b,c), ...) or (a,ROLLUP(b,c), ...)
+        String subgroupSpecifier = "";
+        if (allowComplexSubgroup && random.nextInt(2) == 0) {
+            subgroupSpecifier = "ROLLUP";
+        }
+        int start = random.nextInt(elementColumns.size() - 1);
+        List<String> sublist = elementColumns.subList(start, start + 2);
+        String s = String.format("%s(%s)", subgroupSpecifier, String.join(",", sublist));
+        sublist.clear();
+        sublist.add(s);
+    }
+
+    private ArrayNode asJson(ExtractedResult aresult) throws IOException {
+        ArrayNode result = (ArrayNode) JSON_NODE_READER.createArrayNode();
+        try (BufferedReader reader =
+                new BufferedReader(new InputStreamReader(aresult.getResult(), StandardCharsets.UTF_8))) {
+            reader.lines().forEachOrdered(l -> {
+                try {
+                    result.add(JSON_NODE_READER.readTree(l));
+                } catch (JsonProcessingException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+        }
+        return result;
+    }
+
+    private ArrayNode asJson(ResultSet rs) throws SQLException {
+        ResultSetMetaData rsmd = rs.getMetaData();
+        int rsColumnCount = rsmd.getColumnCount();
+        ArrayNode result = (ArrayNode) JSON_NODE_READER.createArrayNode();
+        while (rs.next()) {
+            ObjectNode row = (ObjectNode) JSON_NODE_READER.createObjectNode();
+            for (int i = 0; i < rsColumnCount; i++) {
+                int jdbcColumnIdx = i + 1;
+                String columnName = rsmd.getColumnName(jdbcColumnIdx);
+                switch (rsmd.getColumnType(jdbcColumnIdx)) {
+                    case Types.INTEGER:
+                        int intValue = rs.getInt(jdbcColumnIdx);
+                        if (rs.wasNull()) {
+                            row.putNull(columnName);
+                        } else {
+                            row.put(columnName, intValue);
+                        }
+                        break;
+                    case Types.BIGINT:
+                        long longValue = rs.getLong(jdbcColumnIdx);
+                        if (rs.wasNull()) {
+                            row.putNull(columnName);
+                        } else {
+                            row.put(columnName, longValue);
+                        }
+                        break;
+                    case Types.VARCHAR:
+                        String stringValue = rs.getString(jdbcColumnIdx);
+                        if (rs.wasNull()) {
+                            row.putNull(columnName);
+                        } else {
+                            row.put(columnName, stringValue);
+                        }
+                        break;
+                    default:
+                        throw new UnsupportedOperationException();
+                }
+            }
+            result.add(row);
+        }
+        return result;
+    }
+
+    private static void loadAsterixData() throws Exception {
+        String tableTypeName = TABLE_NAME + "Type";
+        String createTypeStmtText =
+                String.format("CREATE TYPE %s AS CLOSED { %s }", tableTypeName,
+                        TABLE_SCHEMA.entrySet().stream()
+                                .map(e -> e.getKey() + ':' + getAsterixType(e.getValue()).getTypeName())
+                                .collect(Collectors.joining(",")));
+
+        LOGGER.debug(createTypeStmtText);
+        testExecutor.executeSqlppUpdateOrDdl(createTypeStmtText, TestCaseContext.OutputFormat.ADM);
+
+        String createDatasetStmtText =
+                String.format("CREATE DATASET %s(%s) PRIMARY KEY %s", TABLE_NAME, tableTypeName, UNIQUE_2);
+        LOGGER.debug(createDatasetStmtText);
+        testExecutor.executeSqlppUpdateOrDdl(createDatasetStmtText, TestCaseContext.OutputFormat.ADM);
+
+        String loadStmtText =
+                String.format("LOAD DATASET %s USING localfs ((`path`=`%s`),(`format`=`%s`),(`delimiter`=`%s`))",
+                        TABLE_NAME, "asterix_nc1://" + TABLE_FILE, "delimited-text", "|");
+        LOGGER.debug(loadStmtText);
+        testExecutor.executeSqlppUpdateOrDdl(loadStmtText, TestCaseContext.OutputFormat.ADM);
+    }
+
+    private static void loadSQLData() throws SQLException, IOException {
+        String createTableStmtText = String.format("CREATE TEMPORARY TABLE %s (%s)", TABLE_NAME, TABLE_SCHEMA.entrySet()
+                .stream().map(e -> e.getKey() + ' ' + getSQLType(e.getValue())).collect(Collectors.joining(",")));
+
+        stmt.execute(createTableStmtText);
+
+        String insertStmtText = String.format("INSERT INTO %s VALUES (%s)", TABLE_NAME,
+                StringUtils.repeat("?", ",", TABLE_SCHEMA.size()));
+
+        try (PreparedStatement insertStmt = conn.prepareStatement(insertStmtText)) {
+            Files.lines(TABLE_FILE).forEachOrdered(line -> {
+                String[] values = StringUtils.split(line, TABLE_FILE_COLUMN_SEPARATOR);
+                try {
+                    insertStmt.clearParameters();
+                    int i = 0;
+                    for (JDBCType type : TABLE_SCHEMA.values()) {
+                        setColumnValue(insertStmt, i + 1, type, values[i]);
+                        i++;
+                    }
+                    insertStmt.addBatch();
+                } catch (SQLException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+            insertStmt.executeBatch();
+        }
+    }
+
+    private static LinkedHashMap<String, JDBCType> createTableSchema() {
+        LinkedHashMap<String, JDBCType> schema = new LinkedHashMap<>();
+        schema.put(UNIQUE_1, JDBCType.INTEGER);
+        schema.put(UNIQUE_2, JDBCType.INTEGER);
+        schema.put(TWO, JDBCType.INTEGER);
+        schema.put(FOUR, JDBCType.INTEGER);
+        schema.put(TEN, JDBCType.INTEGER);
+        schema.put(TWENTY, JDBCType.INTEGER);
+        schema.put(HUNDRED, JDBCType.INTEGER);
+        schema.put(THOUSAND, JDBCType.INTEGER);
+        schema.put(TWOTHOUSAND, JDBCType.INTEGER);
+        schema.put(FIVETHOUS, JDBCType.INTEGER);
+        schema.put(TENTHOUS, JDBCType.INTEGER);
+        schema.put(ODD100, JDBCType.INTEGER);
+        schema.put(EVEN100, JDBCType.INTEGER);
+        schema.put(STRINGU1, JDBCType.VARCHAR);
+        schema.put(STRINGU2, JDBCType.VARCHAR);
+        schema.put(STRING4, JDBCType.VARCHAR);
+        return schema;
+    }
+
+    private static String getSQLType(JDBCType type) {
+        String suffix = "";
+        if (type == JDBCType.VARCHAR) {
+            suffix = "(256)";
+        }
+        return type.getName() + suffix;
+    }
+
+    private static IAType getAsterixType(JDBCType type) {
+        switch (type) {
+            case INTEGER:
+                return BuiltinType.AINT32;
+            case VARCHAR:
+                return BuiltinType.ASTRING;
+            default:
+                throw new UnsupportedOperationException();
+        }
+    }
+
+    private static void setColumnValue(PreparedStatement stmt, int jdbcParamIdx, JDBCType type, String value)
+            throws SQLException {
+        switch (type) {
+            case INTEGER:
+                stmt.setInt(jdbcParamIdx, Integer.parseInt(value));
+                break;
+            case VARCHAR:
+                stmt.setString(jdbcParamIdx, value);
+                break;
+            default:
+                throw new UnsupportedOperationException(type.getName());
+        }
+    }
+
+    private static <T> List<T> randomize(Collection<T> input, Random random) {
+        List<T> output = new ArrayList<>(input);
+        Collections.shuffle(output, random);
+        return output;
+    }
+
+    private static String getConfigurationPropertyName(String propertyName) {
+        return String.format("%s.%s", SqlppRQGGroupingSetsIT.class.getSimpleName(), propertyName);
+    }
+
+    private static long getLongConfigurationProperty(String propertyName, long defValue) {
+        String textValue = System.getProperty(propertyName);
+        if (textValue == null) {
+            return defValue;
+        }
+        try {
+            return Long.parseLong(textValue);
+        } catch (NumberFormatException e) {
+            LOGGER.warn(String.format("Cannot parse configuration property: %s. Will use default value: %d",
+                    propertyName, defValue));
+            return defValue;
+        }
+    }
+
+    private File writeResult(ArrayNode result, String resultKind) throws IOException {
+        String outFileName = String.format("%d.%s.txt", testcaseId, resultKind);
+        File outFile = new File(RESULT_OUTPUT_DIR.toFile(), outFileName);
+        try (PrintWriter pw = new PrintWriter(outFile, StandardCharsets.UTF_8.name())) {
+            pw.print("---");
+            pw.println(groupByClause);
+            for (int i = 0, ln = result.size(); i < ln; i++) {
+                pw.println(ResultExtractor.prettyPrint(result.get(i)));
+            }
+        }
+        return outFile;
+    }
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        startAsterix();
+        startPostgres();
+        FileUtils.forceMkdir(RESULT_OUTPUT_DIR.toFile());
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        stopPostgres();
+        stopAsterix();
+    }
+
+    private static void startAsterix() throws Exception {
+        testExecutor = new TestExecutor();
+        LangExecutionUtil.setUp(TEST_CONFIG_FILE_NAME, testExecutor);
+        loadAsterixData();
+    }
+
+    private static void stopAsterix() throws Exception {
+        LangExecutionUtil.tearDown();
+    }
+
+    private static void startPostgres() throws SQLException, IOException {
+        if (!Boolean.parseBoolean(System.getenv(TESTCONTAINERS_RYUK_DISABLED))) {
+            throw new IllegalStateException(
+                    String.format("Set environment variable %s=%s", TESTCONTAINERS_RYUK_DISABLED, true));
+        }
+        LOGGER.info("Starting Postgres");
+        postgres = new PostgreSQLContainer<>(POSTGRES_IMAGE);
+        postgres.start();
+        conn = DriverManager.getConnection(postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword());
+        stmt = conn.createStatement();
+        loadSQLData();
+    }
+
+    private static void stopPostgres() {
+        LOGGER.info("Stopping Postgres");
+        if (stmt != null) {
+            try {
+                stmt.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        if (conn != null) {
+            try {
+                conn.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        if (postgres != null) {
+            try {
+                postgres.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static class TestQuery {
+        final String sqlQuery;
+        final String sqlppQuery;
+        final String groupbyClause;
+
+        TestQuery(String sqlQuery, String sqlppQuery, String groupbyClause) {
+            this.sqlQuery = sqlQuery;
+            this.sqlppQuery = sqlppQuery;
+            this.groupbyClause = groupbyClause;
+        }
+    }
+}
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/group-by/grouping-sets-1.1.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/group-by/grouping-sets-1.1.sqlpp
new file mode 100644
index 0000000..9410948
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/group-by/grouping-sets-1.1.sqlpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test various combinations of grouping sets
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk(tenkType) primary key unique2;
+
+select two, four, ten,
+  grouping(two, four, ten) as grp,
+  sum(twenty) as agg_sum
+from tenk
+group by grouping sets((two), (four), (two, four), (ten))
+order by two, four, ten;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/queries/group-by/grouping-sets-1.2.sqlpp b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/group-by/grouping-sets-1.2.sqlpp
new file mode 100644
index 0000000..1073678
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/queries/group-by/grouping-sets-1.2.sqlpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test various combinations of grouping sets
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk(tenkType) primary key unique2;
+
+select two, four, ten, twenty,
+  grouping(two, four, ten, twenty) as grp,
+  sum(hundred) as agg_sum
+from tenk
+group by rollup(two, four), cube(ten, twenty)
+order by two, four, ten, twenty;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/group-by/grouping-sets-1.1.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/group-by/grouping-sets-1.1.plan
new file mode 100644
index 0000000..72d2bb1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/group-by/grouping-sets-1.1.plan
@@ -0,0 +1,131 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- SORT_MERGE_EXCHANGE [$$245(ASC), $$246(ASC), $$247(ASC) ]  |PARTITIONED|
+        -- STABLE_SORT [$$245(ASC), $$246(ASC), $$247(ASC)]  |PARTITIONED|
+          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+            -- UNION_ALL  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- UNION_ALL  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- UNION_ALL  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- SORT_GROUP_BY[$$253]  |PARTITIONED|
+                                              {
+                                                -- AGGREGATE  |LOCAL|
+                                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                              }
+                                        -- HASH_PARTITION_EXCHANGE [$$253]  |PARTITIONED|
+                                          -- SORT_GROUP_BY[$$232]  |PARTITIONED|
+                                                  {
+                                                    -- AGGREGATE  |LOCAL|
+                                                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                  }
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- REPLICATE  |PARTITIONED|
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- STREAM_PROJECT  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  -- ASSIGN  |PARTITIONED|
+                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                      -- SORT_GROUP_BY[$$255]  |PARTITIONED|
+                                              {
+                                                -- AGGREGATE  |LOCAL|
+                                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                              }
+                                        -- HASH_PARTITION_EXCHANGE [$$255]  |PARTITIONED|
+                                          -- SORT_GROUP_BY[$$233]  |PARTITIONED|
+                                                  {
+                                                    -- AGGREGATE  |LOCAL|
+                                                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                  }
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- REPLICATE  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- DATASOURCE_SCAN  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ASSIGN  |PARTITIONED|
+                            -- STREAM_PROJECT  |PARTITIONED|
+                              -- ASSIGN  |PARTITIONED|
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- SORT_GROUP_BY[$$257, $$258]  |PARTITIONED|
+                                          {
+                                            -- AGGREGATE  |LOCAL|
+                                              -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                          }
+                                    -- HASH_PARTITION_EXCHANGE [$$257, $$258]  |PARTITIONED|
+                                      -- SORT_GROUP_BY[$$234, $$235]  |PARTITIONED|
+                                              {
+                                                -- AGGREGATE  |LOCAL|
+                                                  -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                              }
+                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- REPLICATE  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- DATASOURCE_SCAN  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          -- SORT_GROUP_BY[$$260]  |PARTITIONED|
+                                  {
+                                    -- AGGREGATE  |LOCAL|
+                                      -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                  }
+                            -- HASH_PARTITION_EXCHANGE [$$260]  |PARTITIONED|
+                              -- SORT_GROUP_BY[$$236]  |PARTITIONED|
+                                      {
+                                        -- AGGREGATE  |LOCAL|
+                                          -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                      }
+                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ASSIGN  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- REPLICATE  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- DATASOURCE_SCAN  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/optimizerts/results/group-by/grouping-sets-1.2.plan b/asterixdb/asterix-app/src/test/resources/optimizerts/results/group-by/grouping-sets-1.2.plan
new file mode 100644
index 0000000..bea5ef6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/optimizerts/results/group-by/grouping-sets-1.2.plan
@@ -0,0 +1,375 @@
+-- DISTRIBUTE_RESULT  |PARTITIONED|
+  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+    -- STREAM_PROJECT  |PARTITIONED|
+      -- SORT_MERGE_EXCHANGE [$$759(ASC), $$760(ASC), $$761(ASC), $$762(ASC) ]  |PARTITIONED|
+        -- STABLE_SORT [$$759(ASC), $$760(ASC), $$761(ASC), $$762(ASC)]  |PARTITIONED|
+          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+            -- UNION_ALL  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- UNION_ALL  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- UNION_ALL  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- UNION_ALL  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- UNION_ALL  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- UNION_ALL  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- UNION_ALL  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- UNION_ALL  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- UNION_ALL  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- UNION_ALL  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- UNION_ALL  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- ASSIGN  |PARTITIONED|
+                                                          -- STREAM_PROJECT  |PARTITIONED|
+                                                            -- ASSIGN  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- SORT_GROUP_BY[$$776, $$777, $$778, $$779]  |PARTITIONED|
+                                                                        {
+                                                                          -- AGGREGATE  |LOCAL|
+                                                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                        }
+                                                                  -- HASH_PARTITION_EXCHANGE [$$776, $$777, $$778, $$779]  |PARTITIONED|
+                                                                    -- SORT_GROUP_BY[$$710, $$711, $$712, $$713]  |PARTITIONED|
+                                                                            {
+                                                                              -- AGGREGATE  |LOCAL|
+                                                                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                            }
+                                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                                          -- ASSIGN  |PARTITIONED|
+                                                                            -- STREAM_PROJECT  |PARTITIONED|
+                                                                              -- ASSIGN  |PARTITIONED|
+                                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                  -- REPLICATE  |PARTITIONED|
+                                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                      -- STREAM_PROJECT  |PARTITIONED|
+                                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- ASSIGN  |PARTITIONED|
+                                                          -- STREAM_PROJECT  |PARTITIONED|
+                                                            -- ASSIGN  |PARTITIONED|
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- SORT_GROUP_BY[$$781, $$782, $$783]  |PARTITIONED|
+                                                                        {
+                                                                          -- AGGREGATE  |LOCAL|
+                                                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                        }
+                                                                  -- HASH_PARTITION_EXCHANGE [$$781, $$782, $$783]  |PARTITIONED|
+                                                                    -- SORT_GROUP_BY[$$714, $$715, $$716]  |PARTITIONED|
+                                                                            {
+                                                                              -- AGGREGATE  |LOCAL|
+                                                                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                            }
+                                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                                          -- ASSIGN  |PARTITIONED|
+                                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                              -- REPLICATE  |PARTITIONED|
+                                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                      -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- STREAM_PROJECT  |PARTITIONED|
+                                                        -- ASSIGN  |PARTITIONED|
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- SORT_GROUP_BY[$$785, $$786, $$787]  |PARTITIONED|
+                                                                    {
+                                                                      -- AGGREGATE  |LOCAL|
+                                                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                    }
+                                                              -- HASH_PARTITION_EXCHANGE [$$785, $$786, $$787]  |PARTITIONED|
+                                                                -- SORT_GROUP_BY[$$717, $$718, $$719]  |PARTITIONED|
+                                                                        {
+                                                                          -- AGGREGATE  |LOCAL|
+                                                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                        }
+                                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                                      -- ASSIGN  |PARTITIONED|
+                                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                                          -- ASSIGN  |PARTITIONED|
+                                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                              -- REPLICATE  |PARTITIONED|
+                                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                      -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    -- ASSIGN  |PARTITIONED|
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- SORT_GROUP_BY[$$789, $$790]  |PARTITIONED|
+                                                                {
+                                                                  -- AGGREGATE  |LOCAL|
+                                                                    -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                }
+                                                          -- HASH_PARTITION_EXCHANGE [$$789, $$790]  |PARTITIONED|
+                                                            -- SORT_GROUP_BY[$$720, $$721]  |PARTITIONED|
+                                                                    {
+                                                                      -- AGGREGATE  |LOCAL|
+                                                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                    }
+                                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                                  -- ASSIGN  |PARTITIONED|
+                                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                                      -- ASSIGN  |PARTITIONED|
+                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                          -- REPLICATE  |PARTITIONED|
+                                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                  -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                -- ASSIGN  |PARTITIONED|
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- SORT_GROUP_BY[$$792, $$793, $$794]  |PARTITIONED|
+                                                            {
+                                                              -- AGGREGATE  |LOCAL|
+                                                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                            }
+                                                      -- HASH_PARTITION_EXCHANGE [$$792, $$793, $$794]  |PARTITIONED|
+                                                        -- SORT_GROUP_BY[$$722, $$723, $$724]  |PARTITIONED|
+                                                                {
+                                                                  -- AGGREGATE  |LOCAL|
+                                                                    -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                                }
+                                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                            -- STREAM_PROJECT  |PARTITIONED|
+                                                              -- ASSIGN  |PARTITIONED|
+                                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                                  -- ASSIGN  |PARTITIONED|
+                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                      -- REPLICATE  |PARTITIONED|
+                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                          -- STREAM_PROJECT  |PARTITIONED|
+                                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                              -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- ASSIGN  |PARTITIONED|
+                                          -- STREAM_PROJECT  |PARTITIONED|
+                                            -- ASSIGN  |PARTITIONED|
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- SORT_GROUP_BY[$$796, $$797]  |PARTITIONED|
+                                                        {
+                                                          -- AGGREGATE  |LOCAL|
+                                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                        }
+                                                  -- HASH_PARTITION_EXCHANGE [$$796, $$797]  |PARTITIONED|
+                                                    -- SORT_GROUP_BY[$$725, $$726]  |PARTITIONED|
+                                                            {
+                                                              -- AGGREGATE  |LOCAL|
+                                                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                            }
+                                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          -- ASSIGN  |PARTITIONED|
+                                                            -- STREAM_PROJECT  |PARTITIONED|
+                                                              -- ASSIGN  |PARTITIONED|
+                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                  -- REPLICATE  |PARTITIONED|
+                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                      -- STREAM_PROJECT  |PARTITIONED|
+                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- STREAM_PROJECT  |PARTITIONED|
+                                        -- ASSIGN  |PARTITIONED|
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- SORT_GROUP_BY[$$799, $$800]  |PARTITIONED|
+                                                    {
+                                                      -- AGGREGATE  |LOCAL|
+                                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                    }
+                                              -- HASH_PARTITION_EXCHANGE [$$799, $$800]  |PARTITIONED|
+                                                -- SORT_GROUP_BY[$$727, $$728]  |PARTITIONED|
+                                                        {
+                                                          -- AGGREGATE  |LOCAL|
+                                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                        }
+                                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        -- STREAM_PROJECT  |PARTITIONED|
+                                                          -- ASSIGN  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- REPLICATE  |PARTITIONED|
+                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                      -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- ASSIGN  |PARTITIONED|
+                                  -- STREAM_PROJECT  |PARTITIONED|
+                                    -- ASSIGN  |PARTITIONED|
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- SORT_GROUP_BY[$$802]  |PARTITIONED|
+                                                {
+                                                  -- AGGREGATE  |LOCAL|
+                                                    -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                }
+                                          -- HASH_PARTITION_EXCHANGE [$$802]  |PARTITIONED|
+                                            -- SORT_GROUP_BY[$$729]  |PARTITIONED|
+                                                    {
+                                                      -- AGGREGATE  |LOCAL|
+                                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                    }
+                                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                  -- ASSIGN  |PARTITIONED|
+                                                    -- STREAM_PROJECT  |PARTITIONED|
+                                                      -- ASSIGN  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- REPLICATE  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- STREAM_PROJECT  |PARTITIONED|
+                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                  -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                      -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- STREAM_PROJECT  |PARTITIONED|
+                                -- ASSIGN  |PARTITIONED|
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- SORT_GROUP_BY[$$804, $$805]  |PARTITIONED|
+                                            {
+                                              -- AGGREGATE  |LOCAL|
+                                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                            }
+                                      -- HASH_PARTITION_EXCHANGE [$$804, $$805]  |PARTITIONED|
+                                        -- SORT_GROUP_BY[$$730, $$731]  |PARTITIONED|
+                                                {
+                                                  -- AGGREGATE  |LOCAL|
+                                                    -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                                }
+                                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- STREAM_PROJECT  |PARTITIONED|
+                                                  -- ASSIGN  |PARTITIONED|
+                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                      -- REPLICATE  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- STREAM_PROJECT  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- DATASOURCE_SCAN  |PARTITIONED|
+                                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                                  -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        -- ASSIGN  |PARTITIONED|
+                          -- STREAM_PROJECT  |PARTITIONED|
+                            -- ASSIGN  |PARTITIONED|
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                -- SORT_GROUP_BY[$$807]  |PARTITIONED|
+                                        {
+                                          -- AGGREGATE  |LOCAL|
+                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                        }
+                                  -- HASH_PARTITION_EXCHANGE [$$807]  |PARTITIONED|
+                                    -- SORT_GROUP_BY[$$732]  |PARTITIONED|
+                                            {
+                                              -- AGGREGATE  |LOCAL|
+                                                -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                            }
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- STREAM_PROJECT  |PARTITIONED|
+                                              -- ASSIGN  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- REPLICATE  |PARTITIONED|
+                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                      -- STREAM_PROJECT  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- DATASOURCE_SCAN  |PARTITIONED|
+                                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                    -- ASSIGN  |PARTITIONED|
+                      -- STREAM_PROJECT  |PARTITIONED|
+                        -- ASSIGN  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- SORT_GROUP_BY[$$809]  |PARTITIONED|
+                                    {
+                                      -- AGGREGATE  |LOCAL|
+                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                    }
+                              -- HASH_PARTITION_EXCHANGE [$$809]  |PARTITIONED|
+                                -- SORT_GROUP_BY[$$733]  |PARTITIONED|
+                                        {
+                                          -- AGGREGATE  |LOCAL|
+                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                        }
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ASSIGN  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- REPLICATE  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                      -- DATASOURCE_SCAN  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                -- STREAM_PROJECT  |PARTITIONED|
+                  -- ASSIGN  |PARTITIONED|
+                    -- STREAM_PROJECT  |PARTITIONED|
+                      -- ASSIGN  |PARTITIONED|
+                        -- STREAM_PROJECT  |PARTITIONED|
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            -- SORT_GROUP_BY[$$811]  |PARTITIONED|
+                                    {
+                                      -- AGGREGATE  |LOCAL|
+                                        -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                    }
+                              -- HASH_PARTITION_EXCHANGE [$$811]  |PARTITIONED|
+                                -- SORT_GROUP_BY[$$734]  |PARTITIONED|
+                                        {
+                                          -- AGGREGATE  |LOCAL|
+                                            -- NESTED_TUPLE_SOURCE  |LOCAL|
+                                        }
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    -- STREAM_PROJECT  |PARTITIONED|
+                                      -- ASSIGN  |PARTITIONED|
+                                        -- STREAM_PROJECT  |PARTITIONED|
+                                          -- ASSIGN  |PARTITIONED|
+                                            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                              -- REPLICATE  |PARTITIONED|
+                                                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                  -- STREAM_PROJECT  |PARTITIONED|
+                                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                      -- DATASOURCE_SCAN  |PARTITIONED|
+                                                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.1.ddl.sqlpp
new file mode 100644
index 0000000..e93b929
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.1.ddl.sqlpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test various combinations of grouping sets
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk(tenkType) primary key unique2;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.10.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.10.query.sqlpp
new file mode 100644
index 0000000..9fca153
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.10.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, ten,
+  grouping(two, four, ten) as grp,
+  sum(twenty) as agg_sum
+from tenk
+group by grouping sets((two), (four), (two, four), (ten))
+order by two, four, ten;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.11.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.11.query.sqlpp
new file mode 100644
index 0000000..6fafaa8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.11.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, ten,
+  grouping(two, four, ten) as grp,
+  sum(twenty) as agg_sum
+from tenk
+group by rollup(two, (four, ten))
+order by two, four, ten;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.12.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.12.query.sqlpp
new file mode 100644
index 0000000..c401353
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.12.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, grouping(two) as grp, sum(ten) as agg_sum
+from tenk
+group by grouping sets(grouping sets(grouping sets(grouping sets((two)))))
+order by two;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.13.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.13.query.sqlpp
new file mode 100644
index 0000000..85b3b54
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.13.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, ten, twenty,
+  grouping(two, four, ten, twenty) as grp,
+  sum(hundred) as agg_sum
+from tenk
+group by rollup(two, four), cube(ten, twenty)
+order by two, four, ten, twenty;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.14.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.14.query.sqlpp
new file mode 100644
index 0000000..e7fab5a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.14.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, ten, twenty,
+  grouping(two, four, ten, twenty) as grp,
+  sum(hundred) as agg_sum
+from tenk
+group by grouping sets(rollup(two, four)), grouping sets(cube(ten, twenty))
+order by two, four, ten, twenty;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.15.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.15.query.sqlpp
new file mode 100644
index 0000000..7a870a7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.15.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, ten, twenty,
+  grouping(two, four, ten, twenty) as grp,
+  sum(hundred) as agg_sum
+from tenk
+group by grouping sets(rollup(two, four), cube(ten, twenty))
+order by two, four, ten, twenty;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.2.update.sqlpp
new file mode 100644
index 0000000..2d7e768
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.2.update.sqlpp
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+use test;
+
+load  dataset tenk using localfs ((`path`=`asterix_nc1://data/tenk.tbl`),(`format`=`delimited-text`),(`delimiter`=`|`));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.3.query.sqlpp
new file mode 100644
index 0000000..79f203f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.3.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select sum(ten) as agg_sum
+from tenk
+group by ();
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.4.query.sqlpp
new file mode 100644
index 0000000..15d7250
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.4.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select sum(ten) as agg_sum
+from tenk
+group by grouping sets(());
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.5.query.sqlpp
new file mode 100644
index 0000000..43cc448
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.5.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select sum(ten) as agg_sum
+from tenk
+group by grouping sets((), ());
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.6.query.sqlpp
new file mode 100644
index 0000000..b3a8aac
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.6.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select sum(ten) as agg_sum
+from tenk
+group by grouping sets(()), grouping sets(());
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.7.query.sqlpp
new file mode 100644
index 0000000..baad394
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.7.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, grouping(two, four) as grp, sum(ten) as agg_sum
+from tenk
+group by rollup(two,four)
+order by two, four;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.8.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.8.query.sqlpp
new file mode 100644
index 0000000..0d8fd3e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.8.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, grouping(two, four) as grp, sum(ten) as agg_sum
+from tenk
+group by cube(two,four)
+order by two, four;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.9.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.9.query.sqlpp
new file mode 100644
index 0000000..4e435da
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-1/grouping-sets-1.9.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+use test;
+
+select two, four, ten, grouping(two, four, ten) as grp, sum(twenty) as agg_sum
+from tenk
+group by two, rollup(four, ten)
+order by two, four, ten;
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.1.ddl.sqlpp
new file mode 100644
index 0000000..39bde64
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.1.ddl.sqlpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+/*
+ * Test miscellaneous grouping set features
+ */
+
+drop  dataverse test if exists;
+create  dataverse test;
+
+use test;
+
+create type tenkType as closed {
+  unique1         : integer,
+  unique2         : integer,
+  two             : integer,
+  four            : integer,
+  ten             : integer,
+  twenty          : integer,
+  hundred         : integer,
+  thousand        : integer,
+  twothousand     : integer,
+  fivethous       : integer,
+  tenthous        : integer,
+  odd100          : integer,
+  even100         : integer,
+  stringu1        : string,
+  stringu2        : string,
+  string4         : string
+};
+
+create dataset tenk(tenkType) primary key unique2;
+
+create function gs1() {
+  select two, four, grouping(two, four) as grp, sum(ten) as agg_sum
+  from tenk
+  group by rollup(two,four)
+  order by two, four
+};
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.2.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.2.update.sqlpp
new file mode 100644
index 0000000..2d7e768
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.2.update.sqlpp
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+use test;
+
+load  dataset tenk using localfs ((`path`=`asterix_nc1://data/tenk.tbl`),(`format`=`delimited-text`),(`delimiter`=`|`));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.3.query.sqlpp
new file mode 100644
index 0000000..fd5a82f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.3.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/*
+ * Test grouping sets in UDF
+ */
+
+use test;
+
+gs1();
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.4.query.sqlpp
new file mode 100644
index 0000000..6fcac65
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.4.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+/*
+ * Test aliases in grouping sets
+ */
+
+use test;
+
+select v2, v4, grouping(v2, v4) as grp, sum(ten) as agg_sum
+  from tenk
+  group by rollup(two as v2, four as v4)
+  order by v2, v4;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.5.query.sqlpp
new file mode 100644
index 0000000..38a80f1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-2/grouping-sets-2.5.query.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+/*
+ * Test aliases in grouping sets.
+ * An element is first used with an alias then without an alias.
+ */
+
+use test;
+
+select v2, v4, grouping(v2, v4) as grp, sum(ten) as agg_sum
+  from tenk
+  group by two as v2, rollup(two, four as v4)
+  order by v2, v4;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.1.query.sqlpp
new file mode 100644
index 0000000..b823e5e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.1.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/*
+ * Unexpected alias after the same element without an alias
+ */
+
+with hundred as (
+ select r % 2 as two, r % 4 as four, r % 10 as ten
+ from range(1, 100) r
+)
+
+select two, four, grouping(two, four) as grp, sum(ten) as agg_sum
+from hundred
+group by two, rollup(two as v21, four);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.2.query.sqlpp
new file mode 100644
index 0000000..7370e11
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.2.query.sqlpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+/*
+ * Unexpected alias after the same element with a different alias
+ * in another grouping set
+ */
+
+with hundred as (
+ select r % 2 as two, r % 4 as four, r % 10 as ten
+ from range(1, 100) r
+)
+
+select two, four, grouping(two, four) as grp, sum(ten) as agg_sum
+from hundred
+group by rollup(two as v2, four), cube(two as v22, four);
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.3.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.3.query.sqlpp
new file mode 100644
index 0000000..b481dbf
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.3.query.sqlpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+/*
+ * Unexpected alias after the same element with a different alias
+ * in the same grouping set
+ */
+
+with hundred as (
+ select r % 2 as two, r % 4 as four, r % 10 as ten
+ from range(1, 100) r
+)
+
+select two, four, grouping(two, four) as grp, sum(ten) as agg_sum
+from hundred
+group by grouping sets( (two as v2, two as v23, four) );
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.4.query.sqlpp
new file mode 100644
index 0000000..58a789e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.4.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/*
+ * GROUPING() with no arguments
+ */
+
+with hundred as (
+ select r % 2 as two, r % 4 as four, r % 10 as ten
+ from range(1, 100) r
+)
+
+select two, four, grouping() as grp, sum(ten) as agg_sum
+from hundred
+group by two, four;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.5.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.5.query.sqlpp
new file mode 100644
index 0000000..36f11d1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.5.query.sqlpp
@@ -0,0 +1,31 @@
+
+/*
+ * 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.
+ */
+/*
+ * GROUPING() on a non-grouping element
+ */
+
+with hundred as (
+ select r % 2 as two, r % 4 as four, r % 10 as ten
+ from range(1, 100) r
+)
+
+select two, four, grouping(ten) as grp, sum(ten) as agg_sum
+from hundred
+group by two, four;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.6.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.6.query.sqlpp
new file mode 100644
index 0000000..0876c44
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.6.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/*
+ * GROUPING() with f(grouping element)
+ */
+
+with hundred as (
+ select r % 2 as two, r % 4 as four, r % 10 as ten
+ from range(1, 100) r
+)
+
+select two, four, grouping(two+four) as grp, sum(ten) as agg_sum
+from hundred
+group by two, four;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.7.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.7.query.sqlpp
new file mode 100644
index 0000000..2cb8f36
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.7.query.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+/*
+ * GROUPING() with GROUPING() argument
+ */
+
+with hundred as (
+ select r % 2 as two, r % 4 as four, r % 10 as ten
+ from range(1, 100) r
+)
+
+select two, four, grouping(grouping(two)) as grp, sum(ten) as agg_sum
+from hundred
+group by two, four;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.8.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.8.query.sqlpp
new file mode 100644
index 0000000..4fec641
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/group-by/grouping-sets-3-negative/grouping-sets-3-negative.8.query.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+/*
+ * Too many grouping sets
+ */
+
+with hundred as (
+ select r % 2 as two, r %3 as three, r % 4 as four,
+  r % 5 as five, r % 6 as six, r % 7 as seven,
+  t % 8 as eight, r % 9 as nine, r % 10 as ten
+ from range(1, 100) r
+)
+
+select count(*) as agg_sum
+from hundred
+group by cube(two,three,four,five,six,seven,eight,nine,ten);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.10.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.10.adm
new file mode 100644
index 0000000..9c3921a4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.10.adm
@@ -0,0 +1,20 @@
+{ "two": null, "four": null, "ten": 0, "grp": 6, "agg_sum": 5000 }
+{ "two": null, "four": null, "ten": 1, "grp": 6, "agg_sum": 6000 }
+{ "two": null, "four": null, "ten": 2, "grp": 6, "agg_sum": 7000 }
+{ "two": null, "four": null, "ten": 3, "grp": 6, "agg_sum": 8000 }
+{ "two": null, "four": null, "ten": 4, "grp": 6, "agg_sum": 9000 }
+{ "two": null, "four": null, "ten": 5, "grp": 6, "agg_sum": 10000 }
+{ "two": null, "four": null, "ten": 6, "grp": 6, "agg_sum": 11000 }
+{ "two": null, "four": null, "ten": 7, "grp": 6, "agg_sum": 12000 }
+{ "two": null, "four": null, "ten": 8, "grp": 6, "agg_sum": 13000 }
+{ "two": null, "four": null, "ten": 9, "grp": 6, "agg_sum": 14000 }
+{ "two": null, "four": 0, "ten": null, "grp": 5, "agg_sum": 20000 }
+{ "two": null, "four": 1, "ten": null, "grp": 5, "agg_sum": 22500 }
+{ "two": null, "four": 2, "ten": null, "grp": 5, "agg_sum": 25000 }
+{ "two": null, "four": 3, "ten": null, "grp": 5, "agg_sum": 27500 }
+{ "two": 0, "four": null, "ten": null, "grp": 3, "agg_sum": 45000 }
+{ "two": 0, "four": 0, "ten": null, "grp": 1, "agg_sum": 20000 }
+{ "two": 0, "four": 2, "ten": null, "grp": 1, "agg_sum": 25000 }
+{ "two": 1, "four": null, "ten": null, "grp": 3, "agg_sum": 50000 }
+{ "two": 1, "four": 1, "ten": null, "grp": 1, "agg_sum": 22500 }
+{ "two": 1, "four": 3, "ten": null, "grp": 1, "agg_sum": 27500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.11.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.11.adm
new file mode 100644
index 0000000..dca3762
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.11.adm
@@ -0,0 +1,23 @@
+{ "two": null, "four": null, "ten": null, "grp": 7, "agg_sum": 95000 }
+{ "two": 0, "four": null, "ten": null, "grp": 3, "agg_sum": 45000 }
+{ "two": 0, "four": 0, "ten": 0, "grp": 0, "agg_sum": 0 }
+{ "two": 0, "four": 0, "ten": 2, "grp": 0, "agg_sum": 6000 }
+{ "two": 0, "four": 0, "ten": 4, "grp": 0, "agg_sum": 2000 }
+{ "two": 0, "four": 0, "ten": 6, "grp": 0, "agg_sum": 8000 }
+{ "two": 0, "four": 0, "ten": 8, "grp": 0, "agg_sum": 4000 }
+{ "two": 0, "four": 2, "ten": 0, "grp": 0, "agg_sum": 5000 }
+{ "two": 0, "four": 2, "ten": 2, "grp": 0, "agg_sum": 1000 }
+{ "two": 0, "four": 2, "ten": 4, "grp": 0, "agg_sum": 7000 }
+{ "two": 0, "four": 2, "ten": 6, "grp": 0, "agg_sum": 3000 }
+{ "two": 0, "four": 2, "ten": 8, "grp": 0, "agg_sum": 9000 }
+{ "two": 1, "four": null, "ten": null, "grp": 3, "agg_sum": 50000 }
+{ "two": 1, "four": 1, "ten": 1, "grp": 0, "agg_sum": 500 }
+{ "two": 1, "four": 1, "ten": 3, "grp": 0, "agg_sum": 6500 }
+{ "two": 1, "four": 1, "ten": 5, "grp": 0, "agg_sum": 2500 }
+{ "two": 1, "four": 1, "ten": 7, "grp": 0, "agg_sum": 8500 }
+{ "two": 1, "four": 1, "ten": 9, "grp": 0, "agg_sum": 4500 }
+{ "two": 1, "four": 3, "ten": 1, "grp": 0, "agg_sum": 5500 }
+{ "two": 1, "four": 3, "ten": 3, "grp": 0, "agg_sum": 1500 }
+{ "two": 1, "four": 3, "ten": 5, "grp": 0, "agg_sum": 7500 }
+{ "two": 1, "four": 3, "ten": 7, "grp": 0, "agg_sum": 3500 }
+{ "two": 1, "four": 3, "ten": 9, "grp": 0, "agg_sum": 9500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.12.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.12.adm
new file mode 100644
index 0000000..a398052
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.12.adm
@@ -0,0 +1,2 @@
+{ "two": 0, "grp": 0, "agg_sum": 20000 }
+{ "two": 1, "grp": 0, "agg_sum": 25000 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.13.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.13.adm
new file mode 100644
index 0000000..b05632b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.13.adm
@@ -0,0 +1,167 @@
+{ "two": null, "four": null, "ten": null, "twenty": null, "grp": 15, "agg_sum": 495000 }
+{ "two": null, "four": null, "ten": null, "twenty": 0, "grp": 14, "agg_sum": 20000 }
+{ "two": null, "four": null, "ten": null, "twenty": 1, "grp": 14, "agg_sum": 20500 }
+{ "two": null, "four": null, "ten": null, "twenty": 2, "grp": 14, "agg_sum": 21000 }
+{ "two": null, "four": null, "ten": null, "twenty": 3, "grp": 14, "agg_sum": 21500 }
+{ "two": null, "four": null, "ten": null, "twenty": 4, "grp": 14, "agg_sum": 22000 }
+{ "two": null, "four": null, "ten": null, "twenty": 5, "grp": 14, "agg_sum": 22500 }
+{ "two": null, "four": null, "ten": null, "twenty": 6, "grp": 14, "agg_sum": 23000 }
+{ "two": null, "four": null, "ten": null, "twenty": 7, "grp": 14, "agg_sum": 23500 }
+{ "two": null, "four": null, "ten": null, "twenty": 8, "grp": 14, "agg_sum": 24000 }
+{ "two": null, "four": null, "ten": null, "twenty": 9, "grp": 14, "agg_sum": 24500 }
+{ "two": null, "four": null, "ten": null, "twenty": 10, "grp": 14, "agg_sum": 25000 }
+{ "two": null, "four": null, "ten": null, "twenty": 11, "grp": 14, "agg_sum": 25500 }
+{ "two": null, "four": null, "ten": null, "twenty": 12, "grp": 14, "agg_sum": 26000 }
+{ "two": null, "four": null, "ten": null, "twenty": 13, "grp": 14, "agg_sum": 26500 }
+{ "two": null, "four": null, "ten": null, "twenty": 14, "grp": 14, "agg_sum": 27000 }
+{ "two": null, "four": null, "ten": null, "twenty": 15, "grp": 14, "agg_sum": 27500 }
+{ "two": null, "four": null, "ten": null, "twenty": 16, "grp": 14, "agg_sum": 28000 }
+{ "two": null, "four": null, "ten": null, "twenty": 17, "grp": 14, "agg_sum": 28500 }
+{ "two": null, "four": null, "ten": null, "twenty": 18, "grp": 14, "agg_sum": 29000 }
+{ "two": null, "four": null, "ten": null, "twenty": 19, "grp": 14, "agg_sum": 29500 }
+{ "two": null, "four": null, "ten": 0, "twenty": null, "grp": 13, "agg_sum": 45000 }
+{ "two": null, "four": null, "ten": 0, "twenty": 0, "grp": 12, "agg_sum": 20000 }
+{ "two": null, "four": null, "ten": 0, "twenty": 10, "grp": 12, "agg_sum": 25000 }
+{ "two": null, "four": null, "ten": 1, "twenty": null, "grp": 13, "agg_sum": 46000 }
+{ "two": null, "four": null, "ten": 1, "twenty": 1, "grp": 12, "agg_sum": 20500 }
+{ "two": null, "four": null, "ten": 1, "twenty": 11, "grp": 12, "agg_sum": 25500 }
+{ "two": null, "four": null, "ten": 2, "twenty": null, "grp": 13, "agg_sum": 47000 }
+{ "two": null, "four": null, "ten": 2, "twenty": 2, "grp": 12, "agg_sum": 21000 }
+{ "two": null, "four": null, "ten": 2, "twenty": 12, "grp": 12, "agg_sum": 26000 }
+{ "two": null, "four": null, "ten": 3, "twenty": null, "grp": 13, "agg_sum": 48000 }
+{ "two": null, "four": null, "ten": 3, "twenty": 3, "grp": 12, "agg_sum": 21500 }
+{ "two": null, "four": null, "ten": 3, "twenty": 13, "grp": 12, "agg_sum": 26500 }
+{ "two": null, "four": null, "ten": 4, "twenty": null, "grp": 13, "agg_sum": 49000 }
+{ "two": null, "four": null, "ten": 4, "twenty": 4, "grp": 12, "agg_sum": 22000 }
+{ "two": null, "four": null, "ten": 4, "twenty": 14, "grp": 12, "agg_sum": 27000 }
+{ "two": null, "four": null, "ten": 5, "twenty": null, "grp": 13, "agg_sum": 50000 }
+{ "two": null, "four": null, "ten": 5, "twenty": 5, "grp": 12, "agg_sum": 22500 }
+{ "two": null, "four": null, "ten": 5, "twenty": 15, "grp": 12, "agg_sum": 27500 }
+{ "two": null, "four": null, "ten": 6, "twenty": null, "grp": 13, "agg_sum": 51000 }
+{ "two": null, "four": null, "ten": 6, "twenty": 6, "grp": 12, "agg_sum": 23000 }
+{ "two": null, "four": null, "ten": 6, "twenty": 16, "grp": 12, "agg_sum": 28000 }
+{ "two": null, "four": null, "ten": 7, "twenty": null, "grp": 13, "agg_sum": 52000 }
+{ "two": null, "four": null, "ten": 7, "twenty": 7, "grp": 12, "agg_sum": 23500 }
+{ "two": null, "four": null, "ten": 7, "twenty": 17, "grp": 12, "agg_sum": 28500 }
+{ "two": null, "four": null, "ten": 8, "twenty": null, "grp": 13, "agg_sum": 53000 }
+{ "two": null, "four": null, "ten": 8, "twenty": 8, "grp": 12, "agg_sum": 24000 }
+{ "two": null, "four": null, "ten": 8, "twenty": 18, "grp": 12, "agg_sum": 29000 }
+{ "two": null, "four": null, "ten": 9, "twenty": null, "grp": 13, "agg_sum": 54000 }
+{ "two": null, "four": null, "ten": 9, "twenty": 9, "grp": 12, "agg_sum": 24500 }
+{ "two": null, "four": null, "ten": 9, "twenty": 19, "grp": 12, "agg_sum": 29500 }
+{ "two": 0, "four": null, "ten": null, "twenty": null, "grp": 7, "agg_sum": 245000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 0, "grp": 6, "agg_sum": 20000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 2, "grp": 6, "agg_sum": 21000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 4, "grp": 6, "agg_sum": 22000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 6, "grp": 6, "agg_sum": 23000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 8, "grp": 6, "agg_sum": 24000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 10, "grp": 6, "agg_sum": 25000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 12, "grp": 6, "agg_sum": 26000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 14, "grp": 6, "agg_sum": 27000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 16, "grp": 6, "agg_sum": 28000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 18, "grp": 6, "agg_sum": 29000 }
+{ "two": 0, "four": null, "ten": 0, "twenty": null, "grp": 5, "agg_sum": 45000 }
+{ "two": 0, "four": null, "ten": 0, "twenty": 0, "grp": 4, "agg_sum": 20000 }
+{ "two": 0, "four": null, "ten": 0, "twenty": 10, "grp": 4, "agg_sum": 25000 }
+{ "two": 0, "four": null, "ten": 2, "twenty": null, "grp": 5, "agg_sum": 47000 }
+{ "two": 0, "four": null, "ten": 2, "twenty": 2, "grp": 4, "agg_sum": 21000 }
+{ "two": 0, "four": null, "ten": 2, "twenty": 12, "grp": 4, "agg_sum": 26000 }
+{ "two": 0, "four": null, "ten": 4, "twenty": null, "grp": 5, "agg_sum": 49000 }
+{ "two": 0, "four": null, "ten": 4, "twenty": 4, "grp": 4, "agg_sum": 22000 }
+{ "two": 0, "four": null, "ten": 4, "twenty": 14, "grp": 4, "agg_sum": 27000 }
+{ "two": 0, "four": null, "ten": 6, "twenty": null, "grp": 5, "agg_sum": 51000 }
+{ "two": 0, "four": null, "ten": 6, "twenty": 6, "grp": 4, "agg_sum": 23000 }
+{ "two": 0, "four": null, "ten": 6, "twenty": 16, "grp": 4, "agg_sum": 28000 }
+{ "two": 0, "four": null, "ten": 8, "twenty": null, "grp": 5, "agg_sum": 53000 }
+{ "two": 0, "four": null, "ten": 8, "twenty": 8, "grp": 4, "agg_sum": 24000 }
+{ "two": 0, "four": null, "ten": 8, "twenty": 18, "grp": 4, "agg_sum": 29000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": null, "grp": 3, "agg_sum": 120000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 0, "grp": 2, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 4, "grp": 2, "agg_sum": 22000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 8, "grp": 2, "agg_sum": 24000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 12, "grp": 2, "agg_sum": 26000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 16, "grp": 2, "agg_sum": 28000 }
+{ "two": 0, "four": 0, "ten": 0, "twenty": null, "grp": 1, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "ten": 0, "twenty": 0, "grp": 0, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "ten": 2, "twenty": null, "grp": 1, "agg_sum": 26000 }
+{ "two": 0, "four": 0, "ten": 2, "twenty": 12, "grp": 0, "agg_sum": 26000 }
+{ "two": 0, "four": 0, "ten": 4, "twenty": null, "grp": 1, "agg_sum": 22000 }
+{ "two": 0, "four": 0, "ten": 4, "twenty": 4, "grp": 0, "agg_sum": 22000 }
+{ "two": 0, "four": 0, "ten": 6, "twenty": null, "grp": 1, "agg_sum": 28000 }
+{ "two": 0, "four": 0, "ten": 6, "twenty": 16, "grp": 0, "agg_sum": 28000 }
+{ "two": 0, "four": 0, "ten": 8, "twenty": null, "grp": 1, "agg_sum": 24000 }
+{ "two": 0, "four": 0, "ten": 8, "twenty": 8, "grp": 0, "agg_sum": 24000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": null, "grp": 3, "agg_sum": 125000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 2, "grp": 2, "agg_sum": 21000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 6, "grp": 2, "agg_sum": 23000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 10, "grp": 2, "agg_sum": 25000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 14, "grp": 2, "agg_sum": 27000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 18, "grp": 2, "agg_sum": 29000 }
+{ "two": 0, "four": 2, "ten": 0, "twenty": null, "grp": 1, "agg_sum": 25000 }
+{ "two": 0, "four": 2, "ten": 0, "twenty": 10, "grp": 0, "agg_sum": 25000 }
+{ "two": 0, "four": 2, "ten": 2, "twenty": null, "grp": 1, "agg_sum": 21000 }
+{ "two": 0, "four": 2, "ten": 2, "twenty": 2, "grp": 0, "agg_sum": 21000 }
+{ "two": 0, "four": 2, "ten": 4, "twenty": null, "grp": 1, "agg_sum": 27000 }
+{ "two": 0, "four": 2, "ten": 4, "twenty": 14, "grp": 0, "agg_sum": 27000 }
+{ "two": 0, "four": 2, "ten": 6, "twenty": null, "grp": 1, "agg_sum": 23000 }
+{ "two": 0, "four": 2, "ten": 6, "twenty": 6, "grp": 0, "agg_sum": 23000 }
+{ "two": 0, "four": 2, "ten": 8, "twenty": null, "grp": 1, "agg_sum": 29000 }
+{ "two": 0, "four": 2, "ten": 8, "twenty": 18, "grp": 0, "agg_sum": 29000 }
+{ "two": 1, "four": null, "ten": null, "twenty": null, "grp": 7, "agg_sum": 250000 }
+{ "two": 1, "four": null, "ten": null, "twenty": 1, "grp": 6, "agg_sum": 20500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 3, "grp": 6, "agg_sum": 21500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 5, "grp": 6, "agg_sum": 22500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 7, "grp": 6, "agg_sum": 23500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 9, "grp": 6, "agg_sum": 24500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 11, "grp": 6, "agg_sum": 25500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 13, "grp": 6, "agg_sum": 26500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 15, "grp": 6, "agg_sum": 27500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 17, "grp": 6, "agg_sum": 28500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 19, "grp": 6, "agg_sum": 29500 }
+{ "two": 1, "four": null, "ten": 1, "twenty": null, "grp": 5, "agg_sum": 46000 }
+{ "two": 1, "four": null, "ten": 1, "twenty": 1, "grp": 4, "agg_sum": 20500 }
+{ "two": 1, "four": null, "ten": 1, "twenty": 11, "grp": 4, "agg_sum": 25500 }
+{ "two": 1, "four": null, "ten": 3, "twenty": null, "grp": 5, "agg_sum": 48000 }
+{ "two": 1, "four": null, "ten": 3, "twenty": 3, "grp": 4, "agg_sum": 21500 }
+{ "two": 1, "four": null, "ten": 3, "twenty": 13, "grp": 4, "agg_sum": 26500 }
+{ "two": 1, "four": null, "ten": 5, "twenty": null, "grp": 5, "agg_sum": 50000 }
+{ "two": 1, "four": null, "ten": 5, "twenty": 5, "grp": 4, "agg_sum": 22500 }
+{ "two": 1, "four": null, "ten": 5, "twenty": 15, "grp": 4, "agg_sum": 27500 }
+{ "two": 1, "four": null, "ten": 7, "twenty": null, "grp": 5, "agg_sum": 52000 }
+{ "two": 1, "four": null, "ten": 7, "twenty": 7, "grp": 4, "agg_sum": 23500 }
+{ "two": 1, "four": null, "ten": 7, "twenty": 17, "grp": 4, "agg_sum": 28500 }
+{ "two": 1, "four": null, "ten": 9, "twenty": null, "grp": 5, "agg_sum": 54000 }
+{ "two": 1, "four": null, "ten": 9, "twenty": 9, "grp": 4, "agg_sum": 24500 }
+{ "two": 1, "four": null, "ten": 9, "twenty": 19, "grp": 4, "agg_sum": 29500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": null, "grp": 3, "agg_sum": 122500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 1, "grp": 2, "agg_sum": 20500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 5, "grp": 2, "agg_sum": 22500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 9, "grp": 2, "agg_sum": 24500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 13, "grp": 2, "agg_sum": 26500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 17, "grp": 2, "agg_sum": 28500 }
+{ "two": 1, "four": 1, "ten": 1, "twenty": null, "grp": 1, "agg_sum": 20500 }
+{ "two": 1, "four": 1, "ten": 1, "twenty": 1, "grp": 0, "agg_sum": 20500 }
+{ "two": 1, "four": 1, "ten": 3, "twenty": null, "grp": 1, "agg_sum": 26500 }
+{ "two": 1, "four": 1, "ten": 3, "twenty": 13, "grp": 0, "agg_sum": 26500 }
+{ "two": 1, "four": 1, "ten": 5, "twenty": null, "grp": 1, "agg_sum": 22500 }
+{ "two": 1, "four": 1, "ten": 5, "twenty": 5, "grp": 0, "agg_sum": 22500 }
+{ "two": 1, "four": 1, "ten": 7, "twenty": null, "grp": 1, "agg_sum": 28500 }
+{ "two": 1, "four": 1, "ten": 7, "twenty": 17, "grp": 0, "agg_sum": 28500 }
+{ "two": 1, "four": 1, "ten": 9, "twenty": null, "grp": 1, "agg_sum": 24500 }
+{ "two": 1, "four": 1, "ten": 9, "twenty": 9, "grp": 0, "agg_sum": 24500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": null, "grp": 3, "agg_sum": 127500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 3, "grp": 2, "agg_sum": 21500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 7, "grp": 2, "agg_sum": 23500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 11, "grp": 2, "agg_sum": 25500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 15, "grp": 2, "agg_sum": 27500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 19, "grp": 2, "agg_sum": 29500 }
+{ "two": 1, "four": 3, "ten": 1, "twenty": null, "grp": 1, "agg_sum": 25500 }
+{ "two": 1, "four": 3, "ten": 1, "twenty": 11, "grp": 0, "agg_sum": 25500 }
+{ "two": 1, "four": 3, "ten": 3, "twenty": null, "grp": 1, "agg_sum": 21500 }
+{ "two": 1, "four": 3, "ten": 3, "twenty": 3, "grp": 0, "agg_sum": 21500 }
+{ "two": 1, "four": 3, "ten": 5, "twenty": null, "grp": 1, "agg_sum": 27500 }
+{ "two": 1, "four": 3, "ten": 5, "twenty": 15, "grp": 0, "agg_sum": 27500 }
+{ "two": 1, "four": 3, "ten": 7, "twenty": null, "grp": 1, "agg_sum": 23500 }
+{ "two": 1, "four": 3, "ten": 7, "twenty": 7, "grp": 0, "agg_sum": 23500 }
+{ "two": 1, "four": 3, "ten": 9, "twenty": null, "grp": 1, "agg_sum": 29500 }
+{ "two": 1, "four": 3, "ten": 9, "twenty": 19, "grp": 0, "agg_sum": 29500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.14.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.14.adm
new file mode 100644
index 0000000..e93a964
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.14.adm
@@ -0,0 +1,167 @@
+{ "two": null, "four": null, "ten": null, "twenty": null, "grp": 15, "agg_sum": 495000 }
+{ "two": null, "four": null, "ten": null, "twenty": 0, "grp": 14, "agg_sum": 20000 }
+{ "two": null, "four": null, "ten": null, "twenty": 1, "grp": 14, "agg_sum": 20500 }
+{ "two": null, "four": null, "ten": null, "twenty": 2, "grp": 14, "agg_sum": 21000 }
+{ "two": null, "four": null, "ten": null, "twenty": 3, "grp": 14, "agg_sum": 21500 }
+{ "two": null, "four": null, "ten": null, "twenty": 4, "grp": 14, "agg_sum": 22000 }
+{ "two": null, "four": null, "ten": null, "twenty": 5, "grp": 14, "agg_sum": 22500 }
+{ "two": null, "four": null, "ten": null, "twenty": 6, "grp": 14, "agg_sum": 23000 }
+{ "two": null, "four": null, "ten": null, "twenty": 7, "grp": 14, "agg_sum": 23500 }
+{ "two": null, "four": null, "ten": null, "twenty": 8, "grp": 14, "agg_sum": 24000 }
+{ "two": null, "four": null, "ten": null, "twenty": 9, "grp": 14, "agg_sum": 24500 }
+{ "two": null, "four": null, "ten": null, "twenty": 10, "grp": 14, "agg_sum": 25000 }
+{ "two": null, "four": null, "ten": null, "twenty": 11, "grp": 14, "agg_sum": 25500 }
+{ "two": null, "four": null, "ten": null, "twenty": 12, "grp": 14, "agg_sum": 26000 }
+{ "two": null, "four": null, "ten": null, "twenty": 13, "grp": 14, "agg_sum": 26500 }
+{ "two": null, "four": null, "ten": null, "twenty": 14, "grp": 14, "agg_sum": 27000 }
+{ "two": null, "four": null, "ten": null, "twenty": 15, "grp": 14, "agg_sum": 27500 }
+{ "two": null, "four": null, "ten": null, "twenty": 16, "grp": 14, "agg_sum": 28000 }
+{ "two": null, "four": null, "ten": null, "twenty": 17, "grp": 14, "agg_sum": 28500 }
+{ "two": null, "four": null, "ten": null, "twenty": 18, "grp": 14, "agg_sum": 29000 }
+{ "two": null, "four": null, "ten": null, "twenty": 19, "grp": 14, "agg_sum": 29500 }
+{ "two": null, "four": null, "ten": 0, "twenty": null, "grp": 13, "agg_sum": 45000 }
+{ "two": null, "four": null, "ten": 0, "twenty": 0, "grp": 12, "agg_sum": 20000 }
+{ "two": null, "four": null, "ten": 0, "twenty": 10, "grp": 12, "agg_sum": 25000 }
+{ "two": null, "four": null, "ten": 1, "twenty": null, "grp": 13, "agg_sum": 46000 }
+{ "two": null, "four": null, "ten": 1, "twenty": 1, "grp": 12, "agg_sum": 20500 }
+{ "two": null, "four": null, "ten": 1, "twenty": 11, "grp": 12, "agg_sum": 25500 }
+{ "two": null, "four": null, "ten": 2, "twenty": null, "grp": 13, "agg_sum": 47000 }
+{ "two": null, "four": null, "ten": 2, "twenty": 2, "grp": 12, "agg_sum": 21000 }
+{ "two": null, "four": null, "ten": 2, "twenty": 12, "grp": 12, "agg_sum": 26000 }
+{ "two": null, "four": null, "ten": 3, "twenty": null, "grp": 13, "agg_sum": 48000 }
+{ "two": null, "four": null, "ten": 3, "twenty": 3, "grp": 12, "agg_sum": 21500 }
+{ "two": null, "four": null, "ten": 3, "twenty": 13, "grp": 12, "agg_sum": 26500 }
+{ "two": null, "four": null, "ten": 4, "twenty": null, "grp": 13, "agg_sum": 49000 }
+{ "two": null, "four": null, "ten": 4, "twenty": 4, "grp": 12, "agg_sum": 22000 }
+{ "two": null, "four": null, "ten": 4, "twenty": 14, "grp": 12, "agg_sum": 27000 }
+{ "two": null, "four": null, "ten": 5, "twenty": null, "grp": 13, "agg_sum": 50000 }
+{ "two": null, "four": null, "ten": 5, "twenty": 5, "grp": 12, "agg_sum": 22500 }
+{ "two": null, "four": null, "ten": 5, "twenty": 15, "grp": 12, "agg_sum": 27500 }
+{ "two": null, "four": null, "ten": 6, "twenty": null, "grp": 13, "agg_sum": 51000 }
+{ "two": null, "four": null, "ten": 6, "twenty": 6, "grp": 12, "agg_sum": 23000 }
+{ "two": null, "four": null, "ten": 6, "twenty": 16, "grp": 12, "agg_sum": 28000 }
+{ "two": null, "four": null, "ten": 7, "twenty": null, "grp": 13, "agg_sum": 52000 }
+{ "two": null, "four": null, "ten": 7, "twenty": 7, "grp": 12, "agg_sum": 23500 }
+{ "two": null, "four": null, "ten": 7, "twenty": 17, "grp": 12, "agg_sum": 28500 }
+{ "two": null, "four": null, "ten": 8, "twenty": null, "grp": 13, "agg_sum": 53000 }
+{ "two": null, "four": null, "ten": 8, "twenty": 8, "grp": 12, "agg_sum": 24000 }
+{ "two": null, "four": null, "ten": 8, "twenty": 18, "grp": 12, "agg_sum": 29000 }
+{ "two": null, "four": null, "ten": 9, "twenty": null, "grp": 13, "agg_sum": 54000 }
+{ "two": null, "four": null, "ten": 9, "twenty": 9, "grp": 12, "agg_sum": 24500 }
+{ "two": null, "four": null, "ten": 9, "twenty": 19, "grp": 12, "agg_sum": 29500 }
+{ "two": 0, "four": null, "ten": null, "twenty": null, "grp": 7, "agg_sum": 245000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 0, "grp": 6, "agg_sum": 20000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 2, "grp": 6, "agg_sum": 21000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 4, "grp": 6, "agg_sum": 22000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 6, "grp": 6, "agg_sum": 23000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 8, "grp": 6, "agg_sum": 24000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 10, "grp": 6, "agg_sum": 25000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 12, "grp": 6, "agg_sum": 26000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 14, "grp": 6, "agg_sum": 27000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 16, "grp": 6, "agg_sum": 28000 }
+{ "two": 0, "four": null, "ten": null, "twenty": 18, "grp": 6, "agg_sum": 29000 }
+{ "two": 0, "four": null, "ten": 0, "twenty": null, "grp": 5, "agg_sum": 45000 }
+{ "two": 0, "four": null, "ten": 0, "twenty": 0, "grp": 4, "agg_sum": 20000 }
+{ "two": 0, "four": null, "ten": 0, "twenty": 10, "grp": 4, "agg_sum": 25000 }
+{ "two": 0, "four": null, "ten": 2, "twenty": null, "grp": 5, "agg_sum": 47000 }
+{ "two": 0, "four": null, "ten": 2, "twenty": 2, "grp": 4, "agg_sum": 21000 }
+{ "two": 0, "four": null, "ten": 2, "twenty": 12, "grp": 4, "agg_sum": 26000 }
+{ "two": 0, "four": null, "ten": 4, "twenty": null, "grp": 5, "agg_sum": 49000 }
+{ "two": 0, "four": null, "ten": 4, "twenty": 4, "grp": 4, "agg_sum": 22000 }
+{ "two": 0, "four": null, "ten": 4, "twenty": 14, "grp": 4, "agg_sum": 27000 }
+{ "two": 0, "four": null, "ten": 6, "twenty": null, "grp": 5, "agg_sum": 51000 }
+{ "two": 0, "four": null, "ten": 6, "twenty": 6, "grp": 4, "agg_sum": 23000 }
+{ "two": 0, "four": null, "ten": 6, "twenty": 16, "grp": 4, "agg_sum": 28000 }
+{ "two": 0, "four": null, "ten": 8, "twenty": null, "grp": 5, "agg_sum": 53000 }
+{ "two": 0, "four": null, "ten": 8, "twenty": 8, "grp": 4, "agg_sum": 24000 }
+{ "two": 0, "four": null, "ten": 8, "twenty": 18, "grp": 4, "agg_sum": 29000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": null, "grp": 3, "agg_sum": 120000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 0, "grp": 2, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 4, "grp": 2, "agg_sum": 22000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 8, "grp": 2, "agg_sum": 24000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 12, "grp": 2, "agg_sum": 26000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": 16, "grp": 2, "agg_sum": 28000 }
+{ "two": 0, "four": 0, "ten": 0, "twenty": null, "grp": 1, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "ten": 0, "twenty": 0, "grp": 0, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "ten": 2, "twenty": null, "grp": 1, "agg_sum": 26000 }
+{ "two": 0, "four": 0, "ten": 2, "twenty": 12, "grp": 0, "agg_sum": 26000 }
+{ "two": 0, "four": 0, "ten": 4, "twenty": null, "grp": 1, "agg_sum": 22000 }
+{ "two": 0, "four": 0, "ten": 4, "twenty": 4, "grp": 0, "agg_sum": 22000 }
+{ "two": 0, "four": 0, "ten": 6, "twenty": null, "grp": 1, "agg_sum": 28000 }
+{ "two": 0, "four": 0, "ten": 6, "twenty": 16, "grp": 0, "agg_sum": 28000 }
+{ "two": 0, "four": 0, "ten": 8, "twenty": null, "grp": 1, "agg_sum": 24000 }
+{ "two": 0, "four": 0, "ten": 8, "twenty": 8, "grp": 0, "agg_sum": 24000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": null, "grp": 3, "agg_sum": 125000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 2, "grp": 2, "agg_sum": 21000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 6, "grp": 2, "agg_sum": 23000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 10, "grp": 2, "agg_sum": 25000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 14, "grp": 2, "agg_sum": 27000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": 18, "grp": 2, "agg_sum": 29000 }
+{ "two": 0, "four": 2, "ten": 0, "twenty": null, "grp": 1, "agg_sum": 25000 }
+{ "two": 0, "four": 2, "ten": 0, "twenty": 10, "grp": 0, "agg_sum": 25000 }
+{ "two": 0, "four": 2, "ten": 2, "twenty": null, "grp": 1, "agg_sum": 21000 }
+{ "two": 0, "four": 2, "ten": 2, "twenty": 2, "grp": 0, "agg_sum": 21000 }
+{ "two": 0, "four": 2, "ten": 4, "twenty": null, "grp": 1, "agg_sum": 27000 }
+{ "two": 0, "four": 2, "ten": 4, "twenty": 14, "grp": 0, "agg_sum": 27000 }
+{ "two": 0, "four": 2, "ten": 6, "twenty": null, "grp": 1, "agg_sum": 23000 }
+{ "two": 0, "four": 2, "ten": 6, "twenty": 6, "grp": 0, "agg_sum": 23000 }
+{ "two": 0, "four": 2, "ten": 8, "twenty": null, "grp": 1, "agg_sum": 29000 }
+{ "two": 0, "four": 2, "ten": 8, "twenty": 18, "grp": 0, "agg_sum": 29000 }
+{ "two": 1, "four": null, "ten": null, "twenty": null, "grp": 7, "agg_sum": 250000 }
+{ "two": 1, "four": null, "ten": null, "twenty": 1, "grp": 6, "agg_sum": 20500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 3, "grp": 6, "agg_sum": 21500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 5, "grp": 6, "agg_sum": 22500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 7, "grp": 6, "agg_sum": 23500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 9, "grp": 6, "agg_sum": 24500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 11, "grp": 6, "agg_sum": 25500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 13, "grp": 6, "agg_sum": 26500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 15, "grp": 6, "agg_sum": 27500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 17, "grp": 6, "agg_sum": 28500 }
+{ "two": 1, "four": null, "ten": null, "twenty": 19, "grp": 6, "agg_sum": 29500 }
+{ "two": 1, "four": null, "ten": 1, "twenty": null, "grp": 5, "agg_sum": 46000 }
+{ "two": 1, "four": null, "ten": 1, "twenty": 1, "grp": 4, "agg_sum": 20500 }
+{ "two": 1, "four": null, "ten": 1, "twenty": 11, "grp": 4, "agg_sum": 25500 }
+{ "two": 1, "four": null, "ten": 3, "twenty": null, "grp": 5, "agg_sum": 48000 }
+{ "two": 1, "four": null, "ten": 3, "twenty": 3, "grp": 4, "agg_sum": 21500 }
+{ "two": 1, "four": null, "ten": 3, "twenty": 13, "grp": 4, "agg_sum": 26500 }
+{ "two": 1, "four": null, "ten": 5, "twenty": null, "grp": 5, "agg_sum": 50000 }
+{ "two": 1, "four": null, "ten": 5, "twenty": 5, "grp": 4, "agg_sum": 22500 }
+{ "two": 1, "four": null, "ten": 5, "twenty": 15, "grp": 4, "agg_sum": 27500 }
+{ "two": 1, "four": null, "ten": 7, "twenty": null, "grp": 5, "agg_sum": 52000 }
+{ "two": 1, "four": null, "ten": 7, "twenty": 7, "grp": 4, "agg_sum": 23500 }
+{ "two": 1, "four": null, "ten": 7, "twenty": 17, "grp": 4, "agg_sum": 28500 }
+{ "two": 1, "four": null, "ten": 9, "twenty": null, "grp": 5, "agg_sum": 54000 }
+{ "two": 1, "four": null, "ten": 9, "twenty": 9, "grp": 4, "agg_sum": 24500 }
+{ "two": 1, "four": null, "ten": 9, "twenty": 19, "grp": 4, "agg_sum": 29500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": null, "grp": 3, "agg_sum": 122500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 1, "grp": 2, "agg_sum": 20500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 5, "grp": 2, "agg_sum": 22500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 9, "grp": 2, "agg_sum": 24500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 13, "grp": 2, "agg_sum": 26500 }
+{ "two": 1, "four": 1, "ten": null, "twenty": 17, "grp": 2, "agg_sum": 28500 }
+{ "two": 1, "four": 1, "ten": 1, "twenty": null, "grp": 1, "agg_sum": 20500 }
+{ "two": 1, "four": 1, "ten": 1, "twenty": 1, "grp": 0, "agg_sum": 20500 }
+{ "two": 1, "four": 1, "ten": 3, "twenty": null, "grp": 1, "agg_sum": 26500 }
+{ "two": 1, "four": 1, "ten": 3, "twenty": 13, "grp": 0, "agg_sum": 26500 }
+{ "two": 1, "four": 1, "ten": 5, "twenty": null, "grp": 1, "agg_sum": 22500 }
+{ "two": 1, "four": 1, "ten": 5, "twenty": 5, "grp": 0, "agg_sum": 22500 }
+{ "two": 1, "four": 1, "ten": 7, "twenty": null, "grp": 1, "agg_sum": 28500 }
+{ "two": 1, "four": 1, "ten": 7, "twenty": 17, "grp": 0, "agg_sum": 28500 }
+{ "two": 1, "four": 1, "ten": 9, "twenty": null, "grp": 1, "agg_sum": 24500 }
+{ "two": 1, "four": 1, "ten": 9, "twenty": 9, "grp": 0, "agg_sum": 24500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": null, "grp": 3, "agg_sum": 127500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 3, "grp": 2, "agg_sum": 21500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 7, "grp": 2, "agg_sum": 23500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 11, "grp": 2, "agg_sum": 25500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 15, "grp": 2, "agg_sum": 27500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": 19, "grp": 2, "agg_sum": 29500 }
+{ "two": 1, "four": 3, "ten": 1, "twenty": null, "grp": 1, "agg_sum": 25500 }
+{ "two": 1, "four": 3, "ten": 1, "twenty": 11, "grp": 0, "agg_sum": 25500 }
+{ "two": 1, "four": 3, "ten": 3, "twenty": null, "grp": 1, "agg_sum": 21500 }
+{ "two": 1, "four": 3, "ten": 3, "twenty": 3, "grp": 0, "agg_sum": 21500 }
+{ "two": 1, "four": 3, "ten": 5, "twenty": null, "grp": 1, "agg_sum": 27500 }
+{ "two": 1, "four": 3, "ten": 5, "twenty": 15, "grp": 0, "agg_sum": 27500 }
+{ "two": 1, "four": 3, "ten": 7, "twenty": null, "grp": 1, "agg_sum": 23500 }
+{ "two": 1, "four": 3, "ten": 7, "twenty": 7, "grp": 0, "agg_sum": 23500 }
+{ "two": 1, "four": 3, "ten": 9, "twenty": null, "grp": 1, "agg_sum": 29500 }
+{ "two": 1, "four": 3, "ten": 9, "twenty": 19, "grp": 0, "agg_sum": 29500 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.15.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.15.adm
new file mode 100644
index 0000000..f2c77b2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.15.adm
@@ -0,0 +1,58 @@
+{ "two": null, "four": null, "ten": null, "twenty": null, "grp": 15, "agg_sum": 495000 }
+{ "two": null, "four": null, "ten": null, "twenty": null, "grp": 15, "agg_sum": 495000 }
+{ "two": null, "four": null, "ten": null, "twenty": 0, "grp": 14, "agg_sum": 20000 }
+{ "two": null, "four": null, "ten": null, "twenty": 1, "grp": 14, "agg_sum": 20500 }
+{ "two": null, "four": null, "ten": null, "twenty": 2, "grp": 14, "agg_sum": 21000 }
+{ "two": null, "four": null, "ten": null, "twenty": 3, "grp": 14, "agg_sum": 21500 }
+{ "two": null, "four": null, "ten": null, "twenty": 4, "grp": 14, "agg_sum": 22000 }
+{ "two": null, "four": null, "ten": null, "twenty": 5, "grp": 14, "agg_sum": 22500 }
+{ "two": null, "four": null, "ten": null, "twenty": 6, "grp": 14, "agg_sum": 23000 }
+{ "two": null, "four": null, "ten": null, "twenty": 7, "grp": 14, "agg_sum": 23500 }
+{ "two": null, "four": null, "ten": null, "twenty": 8, "grp": 14, "agg_sum": 24000 }
+{ "two": null, "four": null, "ten": null, "twenty": 9, "grp": 14, "agg_sum": 24500 }
+{ "two": null, "four": null, "ten": null, "twenty": 10, "grp": 14, "agg_sum": 25000 }
+{ "two": null, "four": null, "ten": null, "twenty": 11, "grp": 14, "agg_sum": 25500 }
+{ "two": null, "four": null, "ten": null, "twenty": 12, "grp": 14, "agg_sum": 26000 }
+{ "two": null, "four": null, "ten": null, "twenty": 13, "grp": 14, "agg_sum": 26500 }
+{ "two": null, "four": null, "ten": null, "twenty": 14, "grp": 14, "agg_sum": 27000 }
+{ "two": null, "four": null, "ten": null, "twenty": 15, "grp": 14, "agg_sum": 27500 }
+{ "two": null, "four": null, "ten": null, "twenty": 16, "grp": 14, "agg_sum": 28000 }
+{ "two": null, "four": null, "ten": null, "twenty": 17, "grp": 14, "agg_sum": 28500 }
+{ "two": null, "four": null, "ten": null, "twenty": 18, "grp": 14, "agg_sum": 29000 }
+{ "two": null, "four": null, "ten": null, "twenty": 19, "grp": 14, "agg_sum": 29500 }
+{ "two": null, "four": null, "ten": 0, "twenty": null, "grp": 13, "agg_sum": 45000 }
+{ "two": null, "four": null, "ten": 0, "twenty": 0, "grp": 12, "agg_sum": 20000 }
+{ "two": null, "four": null, "ten": 0, "twenty": 10, "grp": 12, "agg_sum": 25000 }
+{ "two": null, "four": null, "ten": 1, "twenty": null, "grp": 13, "agg_sum": 46000 }
+{ "two": null, "four": null, "ten": 1, "twenty": 1, "grp": 12, "agg_sum": 20500 }
+{ "two": null, "four": null, "ten": 1, "twenty": 11, "grp": 12, "agg_sum": 25500 }
+{ "two": null, "four": null, "ten": 2, "twenty": null, "grp": 13, "agg_sum": 47000 }
+{ "two": null, "four": null, "ten": 2, "twenty": 2, "grp": 12, "agg_sum": 21000 }
+{ "two": null, "four": null, "ten": 2, "twenty": 12, "grp": 12, "agg_sum": 26000 }
+{ "two": null, "four": null, "ten": 3, "twenty": null, "grp": 13, "agg_sum": 48000 }
+{ "two": null, "four": null, "ten": 3, "twenty": 3, "grp": 12, "agg_sum": 21500 }
+{ "two": null, "four": null, "ten": 3, "twenty": 13, "grp": 12, "agg_sum": 26500 }
+{ "two": null, "four": null, "ten": 4, "twenty": null, "grp": 13, "agg_sum": 49000 }
+{ "two": null, "four": null, "ten": 4, "twenty": 4, "grp": 12, "agg_sum": 22000 }
+{ "two": null, "four": null, "ten": 4, "twenty": 14, "grp": 12, "agg_sum": 27000 }
+{ "two": null, "four": null, "ten": 5, "twenty": null, "grp": 13, "agg_sum": 50000 }
+{ "two": null, "four": null, "ten": 5, "twenty": 5, "grp": 12, "agg_sum": 22500 }
+{ "two": null, "four": null, "ten": 5, "twenty": 15, "grp": 12, "agg_sum": 27500 }
+{ "two": null, "four": null, "ten": 6, "twenty": null, "grp": 13, "agg_sum": 51000 }
+{ "two": null, "four": null, "ten": 6, "twenty": 6, "grp": 12, "agg_sum": 23000 }
+{ "two": null, "four": null, "ten": 6, "twenty": 16, "grp": 12, "agg_sum": 28000 }
+{ "two": null, "four": null, "ten": 7, "twenty": null, "grp": 13, "agg_sum": 52000 }
+{ "two": null, "four": null, "ten": 7, "twenty": 7, "grp": 12, "agg_sum": 23500 }
+{ "two": null, "four": null, "ten": 7, "twenty": 17, "grp": 12, "agg_sum": 28500 }
+{ "two": null, "four": null, "ten": 8, "twenty": null, "grp": 13, "agg_sum": 53000 }
+{ "two": null, "four": null, "ten": 8, "twenty": 8, "grp": 12, "agg_sum": 24000 }
+{ "two": null, "four": null, "ten": 8, "twenty": 18, "grp": 12, "agg_sum": 29000 }
+{ "two": null, "four": null, "ten": 9, "twenty": null, "grp": 13, "agg_sum": 54000 }
+{ "two": null, "four": null, "ten": 9, "twenty": 9, "grp": 12, "agg_sum": 24500 }
+{ "two": null, "four": null, "ten": 9, "twenty": 19, "grp": 12, "agg_sum": 29500 }
+{ "two": 0, "four": null, "ten": null, "twenty": null, "grp": 7, "agg_sum": 245000 }
+{ "two": 0, "four": 0, "ten": null, "twenty": null, "grp": 3, "agg_sum": 120000 }
+{ "two": 0, "four": 2, "ten": null, "twenty": null, "grp": 3, "agg_sum": 125000 }
+{ "two": 1, "four": null, "ten": null, "twenty": null, "grp": 7, "agg_sum": 250000 }
+{ "two": 1, "four": 1, "ten": null, "twenty": null, "grp": 3, "agg_sum": 122500 }
+{ "two": 1, "four": 3, "ten": null, "twenty": null, "grp": 3, "agg_sum": 127500 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.3.adm
new file mode 100644
index 0000000..66820e4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.3.adm
@@ -0,0 +1 @@
+{ "agg_sum": 45000 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.4.adm
new file mode 100644
index 0000000..66820e4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.4.adm
@@ -0,0 +1 @@
+{ "agg_sum": 45000 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.5.adm
new file mode 100644
index 0000000..4939818
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.5.adm
@@ -0,0 +1,2 @@
+{ "agg_sum": 45000 }
+{ "agg_sum": 45000 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.6.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.6.adm
new file mode 100644
index 0000000..66820e4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.6.adm
@@ -0,0 +1 @@
+{ "agg_sum": 45000 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.7.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.7.adm
new file mode 100644
index 0000000..7bbace1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.7.adm
@@ -0,0 +1,7 @@
+{ "two": null, "four": null, "grp": 3, "agg_sum": 45000 }
+{ "two": 0, "four": null, "grp": 1, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "grp": 0, "agg_sum": 10000 }
+{ "two": 0, "four": 2, "grp": 0, "agg_sum": 10000 }
+{ "two": 1, "four": null, "grp": 1, "agg_sum": 25000 }
+{ "two": 1, "four": 1, "grp": 0, "agg_sum": 12500 }
+{ "two": 1, "four": 3, "grp": 0, "agg_sum": 12500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.8.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.8.adm
new file mode 100644
index 0000000..0db7590
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.8.adm
@@ -0,0 +1,11 @@
+{ "two": null, "four": null, "grp": 3, "agg_sum": 45000 }
+{ "two": null, "four": 0, "grp": 2, "agg_sum": 10000 }
+{ "two": null, "four": 1, "grp": 2, "agg_sum": 12500 }
+{ "two": null, "four": 2, "grp": 2, "agg_sum": 10000 }
+{ "two": null, "four": 3, "grp": 2, "agg_sum": 12500 }
+{ "two": 0, "four": null, "grp": 1, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "grp": 0, "agg_sum": 10000 }
+{ "two": 0, "four": 2, "grp": 0, "agg_sum": 10000 }
+{ "two": 1, "four": null, "grp": 1, "agg_sum": 25000 }
+{ "two": 1, "four": 1, "grp": 0, "agg_sum": 12500 }
+{ "two": 1, "four": 3, "grp": 0, "agg_sum": 12500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.9.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.9.adm
new file mode 100644
index 0000000..134743a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-1/grouping-sets-1.9.adm
@@ -0,0 +1,26 @@
+{ "two": 0, "four": null, "ten": null, "grp": 3, "agg_sum": 45000 }
+{ "two": 0, "four": 0, "ten": null, "grp": 1, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "ten": 0, "grp": 0, "agg_sum": 0 }
+{ "two": 0, "four": 0, "ten": 2, "grp": 0, "agg_sum": 6000 }
+{ "two": 0, "four": 0, "ten": 4, "grp": 0, "agg_sum": 2000 }
+{ "two": 0, "four": 0, "ten": 6, "grp": 0, "agg_sum": 8000 }
+{ "two": 0, "four": 0, "ten": 8, "grp": 0, "agg_sum": 4000 }
+{ "two": 0, "four": 2, "ten": null, "grp": 1, "agg_sum": 25000 }
+{ "two": 0, "four": 2, "ten": 0, "grp": 0, "agg_sum": 5000 }
+{ "two": 0, "four": 2, "ten": 2, "grp": 0, "agg_sum": 1000 }
+{ "two": 0, "four": 2, "ten": 4, "grp": 0, "agg_sum": 7000 }
+{ "two": 0, "four": 2, "ten": 6, "grp": 0, "agg_sum": 3000 }
+{ "two": 0, "four": 2, "ten": 8, "grp": 0, "agg_sum": 9000 }
+{ "two": 1, "four": null, "ten": null, "grp": 3, "agg_sum": 50000 }
+{ "two": 1, "four": 1, "ten": null, "grp": 1, "agg_sum": 22500 }
+{ "two": 1, "four": 1, "ten": 1, "grp": 0, "agg_sum": 500 }
+{ "two": 1, "four": 1, "ten": 3, "grp": 0, "agg_sum": 6500 }
+{ "two": 1, "four": 1, "ten": 5, "grp": 0, "agg_sum": 2500 }
+{ "two": 1, "four": 1, "ten": 7, "grp": 0, "agg_sum": 8500 }
+{ "two": 1, "four": 1, "ten": 9, "grp": 0, "agg_sum": 4500 }
+{ "two": 1, "four": 3, "ten": null, "grp": 1, "agg_sum": 27500 }
+{ "two": 1, "four": 3, "ten": 1, "grp": 0, "agg_sum": 5500 }
+{ "two": 1, "four": 3, "ten": 3, "grp": 0, "agg_sum": 1500 }
+{ "two": 1, "four": 3, "ten": 5, "grp": 0, "agg_sum": 7500 }
+{ "two": 1, "four": 3, "ten": 7, "grp": 0, "agg_sum": 3500 }
+{ "two": 1, "four": 3, "ten": 9, "grp": 0, "agg_sum": 9500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.3.adm
new file mode 100644
index 0000000..7bbace1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.3.adm
@@ -0,0 +1,7 @@
+{ "two": null, "four": null, "grp": 3, "agg_sum": 45000 }
+{ "two": 0, "four": null, "grp": 1, "agg_sum": 20000 }
+{ "two": 0, "four": 0, "grp": 0, "agg_sum": 10000 }
+{ "two": 0, "four": 2, "grp": 0, "agg_sum": 10000 }
+{ "two": 1, "four": null, "grp": 1, "agg_sum": 25000 }
+{ "two": 1, "four": 1, "grp": 0, "agg_sum": 12500 }
+{ "two": 1, "four": 3, "grp": 0, "agg_sum": 12500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.4.adm
new file mode 100644
index 0000000..d633a62
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.4.adm
@@ -0,0 +1,7 @@
+{ "v2": null, "v4": null, "grp": 3, "agg_sum": 45000 }
+{ "v2": 0, "v4": null, "grp": 1, "agg_sum": 20000 }
+{ "v2": 0, "v4": 0, "grp": 0, "agg_sum": 10000 }
+{ "v2": 0, "v4": 2, "grp": 0, "agg_sum": 10000 }
+{ "v2": 1, "v4": null, "grp": 1, "agg_sum": 25000 }
+{ "v2": 1, "v4": 1, "grp": 0, "agg_sum": 12500 }
+{ "v2": 1, "v4": 3, "grp": 0, "agg_sum": 12500 }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.5.adm
new file mode 100644
index 0000000..d5e2489
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/group-by/grouping-sets-2/grouping-sets-2.5.adm
@@ -0,0 +1,8 @@
+{ "v2": 0, "v4": null, "grp": 1, "agg_sum": 20000 }
+{ "v2": 0, "v4": null, "grp": 1, "agg_sum": 20000 }
+{ "v2": 0, "v4": 0, "grp": 0, "agg_sum": 10000 }
+{ "v2": 0, "v4": 2, "grp": 0, "agg_sum": 10000 }
+{ "v2": 1, "v4": null, "grp": 1, "agg_sum": 25000 }
+{ "v2": 1, "v4": null, "grp": 1, "agg_sum": 25000 }
+{ "v2": 1, "v4": 1, "grp": 0, "agg_sum": 12500 }
+{ "v2": 1, "v4": 3, "grp": 0, "agg_sum": 12500 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.adm
index 7a8668b..1f1e3a0 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.3.adm
@@ -16,7 +16,7 @@
               -- BTREE_SEARCH  |PARTITIONED|
                 exchange
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  order (ASC, $$22) (ASC, $$23) 
+                  order (ASC, $$22) (ASC, $$23)
                   -- STABLE_SORT [$$22(ASC), $$23(ASC)]  |PARTITIONED|
                     exchange
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.adm
index 45e0602..a0e070b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup-select/push-limit-to-primary-lookup-select.5.adm
@@ -22,7 +22,7 @@
                     -- BTREE_SEARCH  |PARTITIONED|
                       exchange
                       -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                        order (ASC, $$25) (ASC, $$26) 
+                        order (ASC, $$25) (ASC, $$26)
                         -- STABLE_SORT [$$25(ASC), $$26(ASC)]  |PARTITIONED|
                           exchange
                           -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.adm
index e741aec..4aba1a7 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.3.adm
@@ -16,7 +16,7 @@
               -- BTREE_SEARCH  |PARTITIONED|
                 exchange
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  order (ASC, $$18) (ASC, $$19) 
+                  order (ASC, $$18) (ASC, $$19)
                   -- STABLE_SORT [$$18(ASC), $$19(ASC)]  |PARTITIONED|
                     exchange
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.adm
index b64b9c5..f611c2b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/limit/push-limit-to-primary-lookup/push-limit-to-primary-lookup.5.adm
@@ -16,7 +16,7 @@
               -- BTREE_SEARCH  |PARTITIONED|
                 exchange
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  order (ASC, $$20) (ASC, $$21) 
+                  order (ASC, $$20) (ASC, $$21)
                   -- STABLE_SORT [$$20(ASC), $$21(ASC)]  |PARTITIONED|
                     exchange
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.1.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.1.ast
new file mode 100644
index 0000000..8e03d54
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.1.ast
@@ -0,0 +1,22 @@
+DataverseUse test
+TypeDecl tenkType [
+  closed RecordType {
+    unique1 : integer,
+    unique2 : integer,
+    two : integer,
+    four : integer,
+    ten : integer,
+    twenty : integer,
+    hundred : integer,
+    thousand : integer,
+    twothousand : integer,
+    fivethous : integer,
+    tenthous : integer,
+    odd100 : integer,
+    even100 : integer,
+    stringu1 : string,
+    stringu2 : string,
+    string4 : string
+  }
+]
+DatasetDecl tenk(tenkType) partitioned by [[unique2]]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.10.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.10.ast
new file mode 100644
index 0000000..9c6df5d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.10.ast
@@ -0,0 +1,93 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=$ten ]
+ten
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=twenty
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@3[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+    Variable [ Name=$ten ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+  Variable [ Name=$ten ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.11.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.11.ast
new file mode 100644
index 0000000..462e1e4
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.11.ast
@@ -0,0 +1,85 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=$ten ]
+ten
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=twenty
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@3[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+    Variable [ Name=$ten ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+  Variable [ Name=$ten ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.12.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.12.ast
new file mode 100644
index 0000000..f89980c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.12.ast
@@ -0,0 +1,51 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  Variable [ Name=$two ]
+  :=
+  FieldAccessor [
+    Variable [ Name=$tenk ]
+    Field=two
+  ]
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@1[
+    Variable [ Name=$two ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.13.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.13.ast
new file mode 100644
index 0000000..5fa0a46
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.13.ast
@@ -0,0 +1,228 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=$ten ]
+ten
+Variable [ Name=$twenty ]
+twenty
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=hundred
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@4[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+    Variable [ Name=$ten ]
+    Variable [ Name=$twenty ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+  Variable [ Name=$ten ]
+  ASC
+  Variable [ Name=$twenty ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.14.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.14.ast
new file mode 100644
index 0000000..5fa0a46
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.14.ast
@@ -0,0 +1,228 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=$ten ]
+ten
+Variable [ Name=$twenty ]
+twenty
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=hundred
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@4[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+    Variable [ Name=$ten ]
+    Variable [ Name=$twenty ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+  Variable [ Name=$ten ]
+  ASC
+  Variable [ Name=$twenty ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.15.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.15.ast
new file mode 100644
index 0000000..767e94f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.15.ast
@@ -0,0 +1,116 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=$ten ]
+ten
+Variable [ Name=$twenty ]
+twenty
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=hundred
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUPING SET (
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$twenty ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=twenty
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@4[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+    Variable [ Name=$ten ]
+    Variable [ Name=$twenty ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+  Variable [ Name=$ten ]
+  ASC
+  Variable [ Name=$twenty ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.2.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.2.ast
new file mode 100644
index 0000000..916a59e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.2.ast
@@ -0,0 +1 @@
+DataverseUse test
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.3.ast
new file mode 100644
index 0000000..fae9857
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.3.ast
@@ -0,0 +1,34 @@
+DataverseUse test
+Query:
+SELECT [
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#2 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#2 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+  )
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.4.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.4.ast
new file mode 100644
index 0000000..fae9857
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.4.ast
@@ -0,0 +1,34 @@
+DataverseUse test
+Query:
+SELECT [
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#2 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#2 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+  )
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.5.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.5.ast
new file mode 100644
index 0000000..f9f3410
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.5.ast
@@ -0,0 +1,36 @@
+DataverseUse test
+Query:
+SELECT [
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#2 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#2 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+  )
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.6.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.6.ast
new file mode 100644
index 0000000..fae9857
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.6.ast
@@ -0,0 +1,34 @@
+DataverseUse test
+Query:
+SELECT [
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#2 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#2 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+  )
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.7.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.7.ast
new file mode 100644
index 0000000..b2898d1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.7.ast
@@ -0,0 +1,74 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@2[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.8.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.8.ast
new file mode 100644
index 0000000..11c6281
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.8.ast
@@ -0,0 +1,82 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@2[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.9.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.9.ast
new file mode 100644
index 0000000..775f28e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-1/grouping-sets-1.9.ast
@@ -0,0 +1,97 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$two ]
+two
+Variable [ Name=$four ]
+four
+Variable [ Name=$ten ]
+ten
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=twenty
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+    Variable [ Name=$ten ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=ten
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$four ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$two ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@3[
+    Variable [ Name=$two ]
+    Variable [ Name=$four ]
+    Variable [ Name=$ten ]
+  ]
+Orderby
+  Variable [ Name=$two ]
+  ASC
+  Variable [ Name=$four ]
+  ASC
+  Variable [ Name=$ten ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.1.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.1.ast
new file mode 100644
index 0000000..8e03d54
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.1.ast
@@ -0,0 +1,22 @@
+DataverseUse test
+TypeDecl tenkType [
+  closed RecordType {
+    unique1 : integer,
+    unique2 : integer,
+    two : integer,
+    four : integer,
+    ten : integer,
+    twenty : integer,
+    hundred : integer,
+    thousand : integer,
+    twothousand : integer,
+    fivethous : integer,
+    tenthous : integer,
+    odd100 : integer,
+    even100 : integer,
+    stringu1 : string,
+    stringu2 : string,
+    string4 : string
+  }
+]
+DatasetDecl tenk(tenkType) partitioned by [[unique2]]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.2.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.2.ast
new file mode 100644
index 0000000..916a59e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.2.ast
@@ -0,0 +1 @@
+DataverseUse test
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.3.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.3.ast
new file mode 100644
index 0000000..6022629
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.3.ast
@@ -0,0 +1,4 @@
+DataverseUse test
+Query:
+FunctionCall test.gs1@0[
+]
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.4.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.4.ast
new file mode 100644
index 0000000..04c8aec
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.4.ast
@@ -0,0 +1,74 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$v2 ]
+v2
+Variable [ Name=$v4 ]
+v4
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$v2 ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$v4 ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$v2 ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@2[
+    Variable [ Name=$v2 ]
+    Variable [ Name=$v4 ]
+  ]
+Orderby
+  Variable [ Name=$v2 ]
+  ASC
+  Variable [ Name=$v4 ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.5.ast b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.5.ast
new file mode 100644
index 0000000..af33220
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_parser_sqlpp/group-by/grouping-sets-2/grouping-sets-2.5.ast
@@ -0,0 +1,80 @@
+DataverseUse test
+Query:
+SELECT [
+Variable [ Name=$v2 ]
+v2
+Variable [ Name=$v4 ]
+v4
+Variable [ Name=#2 ]
+grp
+FunctionCall asterix.sql-sum@1[
+  (
+    SELECT ELEMENT [
+    FieldAccessor [
+      FieldAccessor [
+        Variable [ Name=#3 ]
+        Field=tenk
+      ]
+      Field=ten
+    ]
+    ]
+    FROM [      Variable [ Name=#1 ]
+      AS Variable [ Name=#3 ]
+    ]
+  )
+]
+agg_sum
+]
+FROM [  FunctionCall asterix.dataset@1[
+    LiteralExpr [STRING] [test.tenk]
+  ]
+  AS Variable [ Name=$tenk ]
+]
+Groupby
+  GROUPING SET (
+    Variable [ Name=$v2 ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+    Variable [ Name=$v4 ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=four
+    ]
+  )
+  GROUPING SET (
+    Variable [ Name=$v2 ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUPING SET (
+    Variable [ Name=$v2 ]
+    :=
+    FieldAccessor [
+      Variable [ Name=$tenk ]
+      Field=two
+    ]
+  ),
+  GROUP AS Variable [ Name=#1 ]
+  (
+    tenk:=Variable [ Name=$tenk ]
+  )
+
+Let Variable [ Name=#2 ]
+  :=
+  FunctionCall test.grouping@2[
+    Variable [ Name=$v2 ]
+    Variable [ Name=$v4 ]
+  ]
+Orderby
+  Variable [ Name=$v2 ]
+  ASC
+  Variable [ Name=$v4 ]
+  ASC
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 281c97e..fc7372e 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -5382,6 +5382,29 @@
       </compilation-unit>
     </test-case>
     <test-case FilePath="group-by">
+      <compilation-unit name="grouping-sets-1">
+        <output-dir compare="Text">grouping-sets-1</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
+      <compilation-unit name="grouping-sets-2">
+        <output-dir compare="Text">grouping-sets-2</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
+      <compilation-unit name="grouping-sets-3-negative">
+        <output-dir compare="Text">grouping-sets-2</output-dir>
+        <expected-error>ASX1113: Unexpected alias: v21</expected-error>
+        <expected-error>ASX1113: Unexpected alias: v22</expected-error>
+        <expected-error>ASX1113: Unexpected alias: v23</expected-error>
+        <expected-error>ASX1087: Invalid number of arguments for function grouping</expected-error>
+        <expected-error>ASX1112: Invalid argument to grouping() function</expected-error>
+        <expected-error>ASX1112: Invalid argument to grouping() function</expected-error>
+        <expected-error>ASX1112: Invalid argument to grouping() function</expected-error>
+        <expected-error>ASX1111: Too many grouping sets in group by clause: 512. Maximum allowed: 128.</expected-error>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
       <compilation-unit name="having">
         <output-dir compare="Text">core-02</output-dir>
       </compilation-unit>
@@ -11266,107 +11289,6 @@
       </compilation-unit>
     </test-case>
   </test-group>
-  <test-group name="window">
-    <test-case FilePath="window">
-      <compilation-unit name="cume_dist_01">
-        <output-dir compare="Text">cume_dist_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="dense_rank_01">
-        <output-dir compare="Text">dense_rank_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="first_value_01">
-        <output-dir compare="Text">first_value_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="lag_01">
-        <output-dir compare="Text">lag_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="last_value_01">
-        <output-dir compare="Text">last_value_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="lead_01">
-        <output-dir compare="Text">lead_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="misc_01">
-        <output-dir compare="Text">misc_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="nth_value_01">
-        <output-dir compare="Text">nth_value_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="ntile_01">
-        <output-dir compare="Text">ntile_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="percent_rank_01">
-        <output-dir compare="Text">percent_rank_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="pg_win">
-        <output-dir compare="Text">pg_win</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="rank_01">
-        <output-dir compare="Text">rank_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="ratio_to_report_01">
-        <output-dir compare="Text">ratio_to_report_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="row_number_01">
-        <output-dir compare="Text">row_number_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="win_negative">
-        <output-dir compare="Text">misc_01</output-dir>
-        <expected-error>ASX0002: Type mismatch</expected-error>
-        <expected-error>ASX1104: Invalid modifier FROM FIRST/LAST for function</expected-error>
-        <expected-error>ASX1037: Invalid query parameter compiler.windowmemory</expected-error>
-        <expected-error>ASX1102: Expected window or aggregate function, got: unknown_func</expected-error>
-        <expected-error>ASX1079: Compilation error: count is a SQL-92 aggregate function</expected-error>
-        <expected-error>ASX1104: Invalid modifier RESPECT/IGNORE NULLS for function</expected-error>
-        <expected-error>ASX1104: Invalid modifier RESPECT/IGNORE NULLS for function</expected-error>
-        <expected-error>ASX1104: Invalid modifier FROM FIRST/LAST for function</expected-error>
-        <source-location>false</source-location>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="win_null_missing">
-        <output-dir compare="Text">win_null_missing</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="win_opt_01">
-        <output-dir compare="Text">win_opt_01</output-dir>
-      </compilation-unit>
-    </test-case>
-    <test-case FilePath="window">
-      <compilation-unit name="win_opt_02">
-        <output-dir compare="Text">win_opt_02</output-dir>
-      </compilation-unit>
-    </test-case>
-  </test-group>
   <test-group name="writers">
     <test-case FilePath="writers">
       <compilation-unit name="print_01">
@@ -14002,4 +13924,105 @@
       </compilation-unit>
     </test-case>
   </test-group>
+  <test-group name="window">
+    <test-case FilePath="window">
+      <compilation-unit name="cume_dist_01">
+        <output-dir compare="Text">cume_dist_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="dense_rank_01">
+        <output-dir compare="Text">dense_rank_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="first_value_01">
+        <output-dir compare="Text">first_value_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="lag_01">
+        <output-dir compare="Text">lag_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="last_value_01">
+        <output-dir compare="Text">last_value_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="lead_01">
+        <output-dir compare="Text">lead_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="misc_01">
+        <output-dir compare="Text">misc_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="nth_value_01">
+        <output-dir compare="Text">nth_value_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="ntile_01">
+        <output-dir compare="Text">ntile_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="percent_rank_01">
+        <output-dir compare="Text">percent_rank_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="pg_win">
+        <output-dir compare="Text">pg_win</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="rank_01">
+        <output-dir compare="Text">rank_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="ratio_to_report_01">
+        <output-dir compare="Text">ratio_to_report_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="row_number_01">
+        <output-dir compare="Text">row_number_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="win_negative">
+        <output-dir compare="Text">misc_01</output-dir>
+        <expected-error>ASX0002: Type mismatch</expected-error>
+        <expected-error>ASX1104: Invalid modifier FROM FIRST/LAST for function</expected-error>
+        <expected-error>ASX1037: Invalid query parameter compiler.windowmemory</expected-error>
+        <expected-error>ASX1102: Expected window or aggregate function, got: unknown_func</expected-error>
+        <expected-error>ASX1079: Compilation error: count is a SQL-92 aggregate function</expected-error>
+        <expected-error>ASX1104: Invalid modifier RESPECT/IGNORE NULLS for function</expected-error>
+        <expected-error>ASX1104: Invalid modifier RESPECT/IGNORE NULLS for function</expected-error>
+        <expected-error>ASX1104: Invalid modifier FROM FIRST/LAST for function</expected-error>
+        <source-location>false</source-location>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="win_null_missing">
+        <output-dir compare="Text">win_null_missing</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="win_opt_01">
+        <output-dir compare="Text">win_opt_01</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="window">
+      <compilation-unit name="win_opt_02">
+        <output-dir compare="Text">win_opt_02</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
 </test-suite>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp_parser.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp_parser.xml
index ffde5ff..e9b1de0 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp_parser.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp_parser.xml
@@ -6713,4 +6713,16 @@
       </compilation-unit>
     </test-case>
   </test-group>
+  <test-group name="group-by">
+    <test-case FilePath="group-by">
+      <compilation-unit name="grouping-sets-1">
+        <output-dir compare="AST">grouping-sets-1</output-dir>
+      </compilation-unit>
+    </test-case>
+    <test-case FilePath="group-by">
+      <compilation-unit name="grouping-sets-2">
+        <output-dir compare="AST">grouping-sets-2</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
 </test-suite>
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
index a30119a..3d30360 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
@@ -198,6 +198,9 @@
     public static final int SYNONYM_EXISTS = 1108;
     public static final int UNKNOWN_SYNONYM = 1109;
     public static final int UNKNOWN_LIBRARY = 1110;
+    public static final int COMPILATION_GROUPING_SETS_OVERFLOW = 1111;
+    public static final int COMPILATION_GROUPING_OPERATION_INVALID_ARG = 1112;
+    public static final int COMPILATION_UNEXPECTED_ALIAS = 1113;
 
     // Feed errors
     public static final int DATAFLOW_ILLEGAL_STATE = 3001;
diff --git a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
index 5dcc8ec..fc356e9 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -193,6 +193,9 @@
 1108 = A synonym with this name %1$s already exists
 1109 = Cannot find synonym with name %1$s
 1110 = Unknown library %1$s
+1111 = Too many grouping sets in group by clause: %1$s. Maximum allowed: %2$s.
+1112 = Invalid argument to grouping() function
+1113 = Unexpected alias: %1$s
 
 # Feed Errors
 3001 = Illegal state.
diff --git a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java
index 142a183..369d4c2 100644
--- a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java
+++ b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/rewrites/AqlQueryRewriter.java
@@ -187,8 +187,10 @@
 
         @Override
         public Void visit(GroupbyClause gc, Void arg) throws CompilationException {
-            for (GbyVariableExpressionPair p : gc.getGbyPairList()) {
-                p.getExpr().accept(this, arg);
+            for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+                for (GbyVariableExpressionPair p : gbyPairList) {
+                    p.getExpr().accept(this, arg);
+                }
             }
             if (gc.hasDecorList()) {
                 for (GbyVariableExpressionPair p : gc.getDecorPairList()) {
diff --git a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLAstPrintVisitor.java b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLAstPrintVisitor.java
index d8614ac..d78640c 100644
--- a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLAstPrintVisitor.java
+++ b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLAstPrintVisitor.java
@@ -19,8 +19,11 @@
 package org.apache.asterix.lang.aql.visitor;
 
 import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.lang.aql.clause.DistinctClause;
 import org.apache.asterix.lang.aql.clause.ForClause;
 import org.apache.asterix.lang.aql.expression.FLWOGRExpression;
@@ -28,7 +31,10 @@
 import org.apache.asterix.lang.aql.visitor.base.IAQLVisitor;
 import org.apache.asterix.lang.common.base.Clause;
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
 import org.apache.asterix.lang.common.expression.ListSliceExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
 
 class AQLAstPrintVisitor extends QueryPrintVisitor implements IAQLVisitor<Void, Integer> {
@@ -83,4 +89,45 @@
         return null;
     }
 
+    @Override
+    public Void visit(GroupbyClause gc, Integer step) throws CompilationException {
+        out.println(skip(step) + "Groupby");
+        List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+        if (gbyList.size() == 1) {
+            for (GbyVariableExpressionPair pair : gbyList.get(0)) {
+                if (pair.getVar() != null) {
+                    pair.getVar().accept(this, step + 1);
+                    out.println(skip(step + 1) + ":=");
+                }
+                pair.getExpr().accept(this, step + 1);
+            }
+        } else {
+            // AQL does not support grouping sets
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, gc.getSourceLocation(), "");
+        }
+        if (gc.hasDecorList()) {
+            out.println(skip(step + 1) + "Decor");
+            for (GbyVariableExpressionPair pair : gc.getDecorPairList()) {
+                if (pair.getVar() != null) {
+                    pair.getVar().accept(this, step + 1);
+                    out.println(skip(step + 1) + ":=");
+                }
+                pair.getExpr().accept(this, step + 1);
+            }
+        }
+        if (gc.hasWithMap()) {
+            out.println(skip(step + 1) + "With");
+            for (Map.Entry<Expression, VariableExpr> entry : gc.getWithVarMap().entrySet()) {
+                Expression key = entry.getKey();
+                VariableExpr value = entry.getValue();
+                key.accept(this, step + 1);
+                if (!key.equals(value)) {
+                    out.println(skip(step + 1) + "AS");
+                    value.accept(this, step + 1);
+                }
+            }
+        }
+        out.println();
+        return null;
+    }
 }
diff --git a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLFormatPrintVisitor.java b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLFormatPrintVisitor.java
index eea398f..3edbcce 100644
--- a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLFormatPrintVisitor.java
+++ b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLFormatPrintVisitor.java
@@ -19,14 +19,21 @@
 package org.apache.asterix.lang.aql.visitor;
 
 import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.lang.aql.clause.DistinctClause;
 import org.apache.asterix.lang.aql.clause.ForClause;
 import org.apache.asterix.lang.aql.expression.FLWOGRExpression;
 import org.apache.asterix.lang.aql.expression.UnionExpr;
 import org.apache.asterix.lang.aql.visitor.base.IAQLVisitor;
 import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.visitor.FormatPrintVisitor;
 
 public class AQLFormatPrintVisitor extends FormatPrintVisitor implements IAQLVisitor<Void, Integer> {
@@ -72,4 +79,43 @@
         out.println();
         return null;
     }
+
+    @Override
+    public Void visit(GroupbyClause gc, Integer step) throws CompilationException {
+        if (gc.hasHashGroupByHint()) {
+            out.println(skip(step) + "/* +hash */");
+        }
+        out.print(skip(step) + "group by ");
+        List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+        if (gbyList.size() == 1) {
+            printDelimitedGbyExpressions(gbyList.get(0), step + 2);
+        } else {
+            // AQL does not support grouping sets
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, gc.getSourceLocation(), "");
+        }
+        if (gc.hasDecorList()) {
+            out.print(" decor ");
+            printDelimitedGbyExpressions(gc.getDecorPairList(), step + 2);
+        }
+        if (gc.hasWithMap()) {
+            out.print(" with ");
+            Map<Expression, VariableExpr> withVarMap = gc.getWithVarMap();
+            int index = 0;
+            int size = withVarMap.size();
+            for (Map.Entry<Expression, VariableExpr> entry : withVarMap.entrySet()) {
+                Expression key = entry.getKey();
+                VariableExpr value = entry.getValue();
+                key.accept(this, step + 2);
+                if (!key.equals(value)) {
+                    out.print(" as ");
+                    value.accept(this, step + 2);
+                }
+                if (++index < size) {
+                    out.print(COMMA);
+                }
+            }
+        }
+        out.println();
+        return null;
+    }
 }
diff --git a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLToSQLPPPrintVisitor.java b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLToSQLPPPrintVisitor.java
index 35a3b45..d0d88c8 100644
--- a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLToSQLPPPrintVisitor.java
+++ b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/AQLToSQLPPPrintVisitor.java
@@ -30,6 +30,7 @@
 import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.lang.aql.clause.DistinctClause;
 import org.apache.asterix.lang.aql.clause.ForClause;
@@ -327,7 +328,13 @@
             out.println(skip(step) + "/* +hash */");
         }
         out.print(skip(step) + "group by ");
-        printDelimitedGbyExpressions(gc.getGbyPairList(), step + 2);
+        List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+        if (gbyList.size() == 1) {
+            printDelimitedGbyExpressions(gbyList.get(0), step + 2);
+        } else {
+            // AQL does not support grouping sets
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, gc.getSourceLocation(), "");
+        }
         out.println();
         return null;
     }
@@ -404,9 +411,11 @@
 
     // Collects produced variables from group-by.
     private List<VariableExpr> collectProducedVariablesFromGroupby(GroupbyClause gbyClause) {
-        List<VariableExpr> producedVars = new ArrayList<VariableExpr>();
-        for (GbyVariableExpressionPair keyPair : gbyClause.getGbyPairList()) {
-            producedVars.add(keyPair.getVar());
+        List<VariableExpr> producedVars = new ArrayList<>();
+        for (List<GbyVariableExpressionPair> gbyPairList : gbyClause.getGbyPairList()) {
+            for (GbyVariableExpressionPair keyPair : gbyPairList) {
+                producedVars.add(keyPair.getVar());
+            }
         }
         if (gbyClause.hasDecorList()) {
             for (GbyVariableExpressionPair keyPair : gbyClause.getDecorPairList()) {
diff --git a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/base/AbstractAqlSimpleExpressionVisitor.java b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/base/AbstractAqlSimpleExpressionVisitor.java
index 9fb0840..16bf19e 100644
--- a/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/base/AbstractAqlSimpleExpressionVisitor.java
+++ b/asterixdb/asterix-lang-aql/src/main/java/org/apache/asterix/lang/aql/visitor/base/AbstractAqlSimpleExpressionVisitor.java
@@ -108,8 +108,10 @@
 
     @Override
     public Expression visit(GroupbyClause gc, ILangExpression arg) throws CompilationException {
-        for (GbyVariableExpressionPair gbyVarExpr : gc.getGbyPairList()) {
-            gbyVarExpr.setExpr(visit(gbyVarExpr.getExpr(), gc));
+        for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+            for (GbyVariableExpressionPair gbyVarExpr : gbyPairList) {
+                gbyVarExpr.setExpr(visit(gbyVarExpr.getExpr(), gc));
+            }
         }
         return null;
     }
diff --git a/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj b/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
index a5ffe03..88017db 100644
--- a/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
+++ b/asterixdb/asterix-lang-aql/src/main/javacc/AQL.jj
@@ -40,6 +40,7 @@
 import java.io.Reader;
 import java.io.StringReader;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -2589,7 +2590,7 @@
       newScope.addNewVarSymbolToScope(withVar.getVar());
     })*
     {
-      gbc.setGbyPairList(vePairList);
+      gbc.setGbyPairList(Collections.singletonList(vePairList));
       gbc.setDecorPairList(decorPairList);
       gbc.setWithVarMap(withVarMap);
       replaceCurrentScope(newScope);
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java
index 20dba52..425abd6 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/clause/GroupbyClause.java
@@ -34,7 +34,7 @@
 
 public class GroupbyClause extends AbstractClause {
 
-    private List<GbyVariableExpressionPair> gbyPairList;
+    private List<List<GbyVariableExpressionPair>> gbyPairList;
     private List<GbyVariableExpressionPair> decorPairList;
     private Map<Expression, VariableExpr> withVarMap;
     private VariableExpr groupVar;
@@ -46,15 +46,16 @@
         // Default constructor.
     }
 
-    public GroupbyClause(List<GbyVariableExpressionPair> gbyPairList, List<GbyVariableExpressionPair> decorPairList,
-            Map<Expression, VariableExpr> withVarList, VariableExpr groupVarExpr,
-            List<Pair<Expression, Identifier>> groupFieldList, boolean hashGroupByHint) {
+    public GroupbyClause(List<List<GbyVariableExpressionPair>> gbyPairList,
+            List<GbyVariableExpressionPair> decorPairList, Map<Expression, VariableExpr> withVarList,
+            VariableExpr groupVarExpr, List<Pair<Expression, Identifier>> groupFieldList, boolean hashGroupByHint) {
         this(gbyPairList, decorPairList, withVarList, groupVarExpr, groupFieldList, hashGroupByHint, false);
     }
 
-    public GroupbyClause(List<GbyVariableExpressionPair> gbyPairList, List<GbyVariableExpressionPair> decorPairList,
-            Map<Expression, VariableExpr> withVarList, VariableExpr groupVarExpr,
-            List<Pair<Expression, Identifier>> groupFieldList, boolean hashGroupByHint, boolean groupAll) {
+    public GroupbyClause(List<List<GbyVariableExpressionPair>> gbyPairList,
+            List<GbyVariableExpressionPair> decorPairList, Map<Expression, VariableExpr> withVarList,
+            VariableExpr groupVarExpr, List<Pair<Expression, Identifier>> groupFieldList, boolean hashGroupByHint,
+            boolean groupAll) {
         this.gbyPairList = gbyPairList;
         this.decorPairList = decorPairList;
         this.withVarMap = withVarList;
@@ -66,11 +67,11 @@
         this.groupAll = groupAll;
     }
 
-    public List<GbyVariableExpressionPair> getGbyPairList() {
+    public List<List<GbyVariableExpressionPair>> getGbyPairList() {
         return gbyPairList;
     }
 
-    public void setGbyPairList(List<GbyVariableExpressionPair> vePairList) {
+    public void setGbyPairList(List<List<GbyVariableExpressionPair>> vePairList) {
         this.gbyPairList = vePairList;
     }
 
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
index f934d60..282be2f 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/AbstractInlineUdfsVisitor.java
@@ -208,9 +208,15 @@
 
     @Override
     public Boolean visit(GroupbyClause gc, List<FunctionDecl> arg) throws CompilationException {
-        Pair<Boolean, List<GbyVariableExpressionPair>> p1 = inlineUdfsInGbyPairList(gc.getGbyPairList(), arg);
-        gc.setGbyPairList(p1.second);
-        boolean changed = p1.first;
+        boolean changed = false;
+        List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+        List<List<GbyVariableExpressionPair>> newGbyList = new ArrayList<>(gbyList.size());
+        for (List<GbyVariableExpressionPair> gbyPairList : gbyList) {
+            Pair<Boolean, List<GbyVariableExpressionPair>> p1 = inlineUdfsInGbyPairList(gbyPairList, arg);
+            newGbyList.add(p1.second);
+            changed |= p1.first;
+        }
+        gc.setGbyPairList(newGbyList);
         if (gc.hasDecorList()) {
             Pair<Boolean, List<GbyVariableExpressionPair>> p2 = inlineUdfsInGbyPairList(gc.getDecorPairList(), arg);
             gc.setDecorPairList(p2.second);
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
index 6efb2ce..8d37e41 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/CloneAndSubstituteVariablesVisitor.java
@@ -83,8 +83,11 @@
     public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(GroupbyClause gc,
             VariableSubstitutionEnvironment env) throws CompilationException {
         VariableSubstitutionEnvironment newSubs = env;
-        List<GbyVariableExpressionPair> newGbyList =
-                VariableCloneAndSubstitutionUtil.substInVarExprPair(context, gc.getGbyPairList(), newSubs, this);
+        List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+        List<List<GbyVariableExpressionPair>> newGbyList = new ArrayList<>(gbyList.size());
+        for (List<GbyVariableExpressionPair> gbyPairList : gbyList) {
+            newGbyList.add(VariableCloneAndSubstitutionUtil.substInVarExprPair(context, gbyPairList, newSubs, this));
+        }
         List<GbyVariableExpressionPair> newDecorList = gc.hasDecorList()
                 ? VariableCloneAndSubstitutionUtil.substInVarExprPair(context, gc.getDecorPairList(), newSubs, this)
                 : new ArrayList<>();
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
index 9c65c2a..4903e4b 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/FormatPrintVisitor.java
@@ -37,7 +37,6 @@
 import org.apache.asterix.external.dataset.adapter.AdapterIdentifier;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.Literal;
-import org.apache.asterix.lang.common.clause.GroupbyClause;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.clause.LimitClause;
 import org.apache.asterix.lang.common.clause.OrderbyClause;
@@ -110,7 +109,7 @@
 import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
-public class FormatPrintVisitor implements ILangVisitor<Void, Integer> {
+public abstract class FormatPrintVisitor implements ILangVisitor<Void, Integer> {
 
     protected final static String COMMA = ",";
     protected final static String SEMICOLON = ";";
@@ -311,39 +310,6 @@
     }
 
     @Override
-    public Void visit(GroupbyClause gc, Integer step) throws CompilationException {
-        if (gc.hasHashGroupByHint()) {
-            out.println(skip(step) + "/* +hash */");
-        }
-        out.print(skip(step) + "group by ");
-        printDelimitedGbyExpressions(gc.getGbyPairList(), step + 2);
-        if (gc.hasDecorList()) {
-            out.print(" decor ");
-            printDelimitedGbyExpressions(gc.getDecorPairList(), step + 2);
-        }
-        if (gc.hasWithMap()) {
-            out.print(" with ");
-            Map<Expression, VariableExpr> withVarMap = gc.getWithVarMap();
-            int index = 0;
-            int size = withVarMap.size();
-            for (Entry<Expression, VariableExpr> entry : withVarMap.entrySet()) {
-                Expression key = entry.getKey();
-                VariableExpr value = entry.getValue();
-                key.accept(this, step + 2);
-                if (!key.equals(value)) {
-                    out.print(" as ");
-                    value.accept(this, step + 2);
-                }
-                if (++index < size) {
-                    out.print(COMMA);
-                }
-            }
-        }
-        out.println();
-        return null;
-    }
-
-    @Override
     public Void visit(LimitClause lc, Integer step) throws CompilationException {
         out.print(skip(step) + "limit ");
         lc.getLimitExpr().accept(this, step + 1);
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
index 680e15f..73f489c 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/GatherFunctionCallsVisitor.java
@@ -20,6 +20,7 @@
 package org.apache.asterix.lang.common.visitor;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -75,8 +76,10 @@
 
     @Override
     public Void visit(GroupbyClause gc, Void arg) throws CompilationException {
-        for (GbyVariableExpressionPair p : gc.getGbyPairList()) {
-            p.getExpr().accept(this, arg);
+        for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+            for (GbyVariableExpressionPair p : gbyPairList) {
+                p.getExpr().accept(this, arg);
+            }
         }
         if (gc.hasDecorList()) {
             for (GbyVariableExpressionPair p : gc.getDecorPairList()) {
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/QueryPrintVisitor.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/QueryPrintVisitor.java
index 6afe4e0..a734918 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/QueryPrintVisitor.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/visitor/QueryPrintVisitor.java
@@ -21,14 +21,12 @@
 import java.io.PrintWriter;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map.Entry;
 
 import org.apache.asterix.common.config.DatasetConfig.DatasetType;
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.Literal;
-import org.apache.asterix.lang.common.clause.GroupbyClause;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.clause.LimitClause;
 import org.apache.asterix.lang.common.clause.OrderbyClause;
@@ -37,7 +35,6 @@
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
 import org.apache.asterix.lang.common.expression.FieldBinding;
-import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
 import org.apache.asterix.lang.common.expression.IfExpr;
 import org.apache.asterix.lang.common.expression.IndexAccessor;
 import org.apache.asterix.lang.common.expression.ListConstructor;
@@ -247,42 +244,6 @@
     }
 
     @Override
-    public Void visit(GroupbyClause gc, Integer step) throws CompilationException {
-        out.println(skip(step) + "Groupby");
-        for (GbyVariableExpressionPair pair : gc.getGbyPairList()) {
-            if (pair.getVar() != null) {
-                pair.getVar().accept(this, step + 1);
-                out.println(skip(step + 1) + ":=");
-            }
-            pair.getExpr().accept(this, step + 1);
-        }
-        if (gc.hasDecorList()) {
-            out.println(skip(step + 1) + "Decor");
-            for (GbyVariableExpressionPair pair : gc.getDecorPairList()) {
-                if (pair.getVar() != null) {
-                    pair.getVar().accept(this, step + 1);
-                    out.println(skip(step + 1) + ":=");
-                }
-                pair.getExpr().accept(this, step + 1);
-            }
-        }
-        if (gc.hasWithMap()) {
-            out.println(skip(step + 1) + "With");
-            for (Entry<Expression, VariableExpr> entry : gc.getWithVarMap().entrySet()) {
-                Expression key = entry.getKey();
-                VariableExpr value = entry.getValue();
-                key.accept(this, step + 1);
-                if (!key.equals(value)) {
-                    out.println(skip(step + 1) + "AS");
-                    value.accept(this, step + 1);
-                }
-            }
-        }
-        out.println();
-        return null;
-    }
-
-    @Override
     public Void visit(LimitClause lc, Integer step) throws CompilationException {
         out.println(skip(step) + "Limit");
         lc.getLimitExpr().accept(this, step + 1);
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/parser/SqlppGroupingSetsParser.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/parser/SqlppGroupingSetsParser.java
new file mode 100644
index 0000000..f9c63de
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/parser/SqlppGroupingSetsParser.java
@@ -0,0 +1,391 @@
+/*
+ * 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.asterix.lang.sqlpp.parser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+/**
+ * Parses GROUP BY's grouping elements into GROUPING SETS:
+ * <p>
+ * GROUP BY {0},{1},... => GROUP BY GROUPING SETS (...)
+ */
+public final class SqlppGroupingSetsParser {
+
+    // max number of grouping sets in a single group by clause
+    static final int GROUPING_SETS_LIMIT = 128;
+
+    private SourceLocation sourceLoc;
+
+    private List<GroupingElement> workList;
+
+    private List<GbyVariableExpressionPair> gbyPairWorkList;
+
+    private LinkedHashMap<Expression, GbyVariableExpressionPair> gbyPairWorkMap;
+
+    private List<List<List<GbyVariableExpressionPair>>> crossProductWorkLists;
+
+    public List<List<GbyVariableExpressionPair>> parse(List<GroupingElement> inList, SourceLocation sourceLoc)
+            throws CompilationException {
+        this.sourceLoc = sourceLoc;
+        List<GbyVariableExpressionPair> primitiveList = transformSimpleToPrimitive(inList);
+        if (primitiveList != null) {
+            return Collections.singletonList(primitiveList);
+        } else {
+            if (workList == null) {
+                workList = new ArrayList<>();
+            } else {
+                workList.clear();
+            }
+            eliminateComplexGroupingSets(inList, workList, false);
+            return crossProductGroupingSets(workList);
+        }
+    }
+
+    private List<GbyVariableExpressionPair> transformSimpleToPrimitive(List<GroupingElement> inList)
+            throws CompilationException {
+        if (findComplexElement(inList) != null) {
+            return null;
+        }
+        if (gbyPairWorkList == null) {
+            gbyPairWorkList = new ArrayList<>();
+        } else {
+            gbyPairWorkList.clear();
+        }
+        concatOrdinary(inList, inList.size(), gbyPairWorkList);
+        return removeDuplicates(gbyPairWorkList);
+    }
+
+    private void eliminateComplexGroupingSets(List<? extends GroupingElement> inList, List<GroupingElement> outList,
+            boolean isInsideGroupingSets) throws CompilationException {
+        for (GroupingElement element : inList) {
+            switch (element.getKind()) {
+                case GROUPING_SET:
+                    outList.add(element);
+                    break;
+                case ROLLUP_CUBE:
+                    RollupCube rollupCube = (RollupCube) element;
+                    List<GroupingSet> rollupCubeTransform = expandRollupCube(rollupCube);
+                    if (isInsideGroupingSets) {
+                        outList.addAll(rollupCubeTransform);
+                    } else { // top level
+                        outList.add(new GroupingSets(rollupCubeTransform));
+                    }
+                    break;
+                case GROUPING_SETS:
+                    GroupingSets groupingSets = (GroupingSets) element;
+                    List<? extends GroupingElement> groupingSetsItems = groupingSets.getItems();
+                    List<GroupingElement> groupingSetsTransform = new ArrayList<>(groupingSetsItems.size());
+                    eliminateComplexGroupingSets(groupingSetsItems, groupingSetsTransform, true);
+                    if (isInsideGroupingSets) {
+                        outList.addAll(groupingSetsTransform);
+                    } else { // top level
+                        outList.add(new GroupingSets(groupingSetsTransform));
+                    }
+                    break;
+                default:
+                    throw new IllegalStateException(String.valueOf(element.getKind()));
+            }
+        }
+    }
+
+    private List<GroupingSet> expandRollupCube(RollupCube rollupCube) throws CompilationException {
+        List<GroupingSet> rollupCubeItems = rollupCube.getItems();
+        return rollupCube.isCube() ? expandCube(rollupCubeItems) : expandRollup(rollupCubeItems);
+    }
+
+    private List<GroupingSet> expandRollup(List<GroupingSet> items) throws CompilationException {
+        int n = items.size();
+        int rollupSize = n + 1;
+        checkGroupingSetsLimit(rollupSize);
+        List<GroupingSet> result = new ArrayList<>(rollupSize);
+        for (int i = n; i > 0; i--) {
+            List<GbyVariableExpressionPair> groupingSetItems = new ArrayList<>();
+            concatOrdinary(items, i, groupingSetItems);
+            result.add(new GroupingSet(groupingSetItems));
+        }
+        result.add(GroupingSet.EMPTY);
+        return result;
+    }
+
+    private List<GroupingSet> expandCube(List<GroupingSet> items) throws CompilationException {
+        int n = items.size();
+        int cubeSize = 1 << n;
+        checkGroupingSetsLimit(cubeSize);
+        List<GroupingSet> result = new ArrayList<>(cubeSize);
+        List<GroupingSet> permutation = new ArrayList<>(n);
+        for (long v = cubeSize - 1; v > 0; v--) {
+            permutation.clear();
+            for (int i = n - 1; i >= 0; i--) {
+                if ((v & (1L << i)) != 0) {
+                    permutation.add(items.get(n - i - 1));
+                }
+            }
+            List<GbyVariableExpressionPair> groupingSetItems = new ArrayList<>();
+            concatOrdinary(permutation, permutation.size(), groupingSetItems);
+            result.add(new GroupingSet(groupingSetItems));
+        }
+        result.add(GroupingSet.EMPTY);
+        return result;
+    }
+
+    private List<List<GbyVariableExpressionPair>> crossProductGroupingSets(List<GroupingElement> inList)
+            throws CompilationException {
+        if (crossProductWorkLists == null) {
+            crossProductWorkLists = Arrays.asList(new ArrayList<>(), new ArrayList<>());
+        } else {
+            for (List<List<GbyVariableExpressionPair>> list : crossProductWorkLists) {
+                list.clear();
+            }
+        }
+
+        int workInListIdx = 0;
+        for (int inListPos = 0, inListSize = inList.size(); inListPos < inListSize; inListPos++) {
+            List<List<GbyVariableExpressionPair>> workInList = crossProductWorkLists.get(workInListIdx);
+            int workOutListIdx = 1 - workInListIdx;
+            List<List<GbyVariableExpressionPair>> workOutList = crossProductWorkLists.get(workOutListIdx);
+            workOutList.clear();
+
+            GroupingElement element = inList.get(inListPos);
+            GroupingSet groupingSet = null;
+            GroupingSets groupingSets = null;
+            switch (element.getKind()) {
+                case GROUPING_SET:
+                    groupingSet = (GroupingSet) element;
+                    break;
+                case GROUPING_SETS:
+                    groupingSets = (GroupingSets) element;
+                    break;
+                default:
+                    throw new IllegalStateException(String.valueOf(element.getKind()));
+            }
+
+            if (inListPos == 0) {
+                if (groupingSet != null) {
+                    workOutList.add(groupingSet.getItems());
+                } else {
+                    for (GroupingElement item : groupingSets.getItems()) {
+                        workOutList.add(((GroupingSet) item).getItems());
+                    }
+                }
+            } else {
+                for (List<GbyVariableExpressionPair> workGroupingSet : workInList) {
+                    if (groupingSet != null) {
+                        workOutList.add(concatOrdinary(workGroupingSet, groupingSet.getItems()));
+                    } else {
+                        for (GroupingElement groupingElement : groupingSets.getItems()) {
+                            workOutList
+                                    .add(concatOrdinary(workGroupingSet, ((GroupingSet) groupingElement).getItems()));
+                        }
+                    }
+                }
+            }
+
+            checkGroupingSetsLimit(workOutList.size());
+
+            workInListIdx = workOutListIdx;
+        }
+
+        List<List<GbyVariableExpressionPair>> crossProductList = crossProductWorkLists.get(workInListIdx);
+
+        // check for unexpected aliases
+        Map<Expression, GbyVariableExpressionPair> gbyPairMap = new HashMap<>();
+        List<List<GbyVariableExpressionPair>> result = new ArrayList<>(crossProductList.size());
+        for (List<GbyVariableExpressionPair> groupingSet : crossProductList) {
+            List<GbyVariableExpressionPair> gsNoDups = removeDuplicates(groupingSet);
+            for (int i = 0, n = gsNoDups.size(); i < n; i++) {
+                GbyVariableExpressionPair gbyPair = gsNoDups.get(i);
+                GbyVariableExpressionPair existingPair = gbyPairMap.get(gbyPair.getExpr());
+                if (existingPair == null) {
+                    gbyPairMap.put(gbyPair.getExpr(), gbyPair);
+                } else if (!Objects.equals(existingPair.getVar(), gbyPair.getVar())) {
+                    if (gbyPair.getVar() != null) {
+                        // existing pair's alias was different or null
+                        throw new CompilationException(ErrorCode.COMPILATION_UNEXPECTED_ALIAS,
+                                gbyPair.getVar().getSourceLocation(),
+                                SqlppVariableUtil.toUserDefinedName(gbyPair.getVar().getVar().getValue()));
+                    } else {
+                        // this pair's alias is null, but the existing one was not null -> use the existing one
+                        VariableExpr newVar = new VariableExpr(new VarIdentifier(existingPair.getVar().getVar()));
+                        newVar.setSourceLocation(existingPair.getVar().getSourceLocation());
+                        gbyPair = new GbyVariableExpressionPair(newVar, gbyPair.getExpr());
+                        gsNoDups.set(i, gbyPair);
+                    }
+                }
+            }
+            result.add(gsNoDups);
+        }
+        return result;
+    }
+
+    private List<GbyVariableExpressionPair> removeDuplicates(List<GbyVariableExpressionPair> inList)
+            throws CompilationException {
+        if (gbyPairWorkMap == null) {
+            gbyPairWorkMap = new LinkedHashMap<>();
+        } else {
+            gbyPairWorkMap.clear();
+        }
+        for (GbyVariableExpressionPair gbyPair : inList) {
+            GbyVariableExpressionPair existingPair = gbyPairWorkMap.get(gbyPair.getExpr());
+            if (existingPair == null) {
+                gbyPairWorkMap.put(gbyPair.getExpr(), gbyPair);
+            } else if (!Objects.equals(existingPair.getVar(), gbyPair.getVar())) {
+                if (gbyPair.getVar() != null) {
+                    // existing pair's alias was different or null
+                    throw new CompilationException(ErrorCode.COMPILATION_UNEXPECTED_ALIAS,
+                            gbyPair.getVar().getSourceLocation(),
+                            SqlppVariableUtil.toUserDefinedName(gbyPair.getVar().getVar().getValue()));
+                }
+                // otherwise this pair's alias is null, but the existing one was not null -> use the existing one
+            }
+        }
+        return new ArrayList<>(gbyPairWorkMap.values());
+    }
+
+    private List<GbyVariableExpressionPair> concatOrdinary(List<GbyVariableExpressionPair> groupingSet1,
+            List<GbyVariableExpressionPair> groupingSet2) {
+        List<GbyVariableExpressionPair> outList = new ArrayList<>(groupingSet1.size() + groupingSet2.size());
+        outList.addAll(groupingSet1);
+        outList.addAll(groupingSet2);
+        return outList;
+    }
+
+    private void concatOrdinary(List<? extends GroupingElement> inList, int endIdx,
+            List<GbyVariableExpressionPair> outList) {
+        for (int i = 0; i < endIdx; i++) {
+            GroupingElement element = inList.get(i);
+            if (element.getKind() != GroupingElement.Kind.GROUPING_SET) {
+                throw new IllegalStateException(String.valueOf(element.getKind()));
+            }
+            outList.addAll(((GroupingSet) element).getItems());
+        }
+    }
+
+    private static GroupingElement findComplexElement(List<GroupingElement> inList) {
+        for (GroupingElement element : inList) {
+            switch (element.getKind()) {
+                case ROLLUP_CUBE:
+                case GROUPING_SETS:
+                    return element;
+                case GROUPING_SET:
+                    break;
+                default:
+                    throw new IllegalStateException(String.valueOf(element.getKind()));
+            }
+        }
+        return null;
+    }
+
+    private void checkGroupingSetsLimit(int n) throws CompilationException {
+        if (n > GROUPING_SETS_LIMIT) {
+            throw new CompilationException(ErrorCode.COMPILATION_GROUPING_SETS_OVERFLOW, sourceLoc, String.valueOf(n),
+                    String.valueOf(GROUPING_SETS_LIMIT));
+        }
+    }
+
+    public abstract static class GroupingElement {
+        public enum Kind {
+            GROUPING_SET,
+            GROUPING_SETS,
+            ROLLUP_CUBE,
+        }
+
+        public abstract Kind getKind();
+    }
+
+    // ordinary grouping set, empty grouping set
+    public static final class GroupingSet extends GroupingElement {
+
+        public static final GroupingSet EMPTY = new GroupingSet(Collections.emptyList());
+
+        private final List<GbyVariableExpressionPair> items;
+
+        public GroupingSet(List<GbyVariableExpressionPair> items) {
+            this.items = items;
+        }
+
+        @Override
+        public Kind getKind() {
+            return Kind.GROUPING_SET;
+        }
+
+        public List<GbyVariableExpressionPair> getItems() {
+            return items;
+        }
+    }
+
+    public static final class RollupCube extends GroupingElement {
+
+        private final List<GroupingSet> items;
+
+        private final boolean isCube;
+
+        public RollupCube(List<GroupingSet> items, boolean isCube) {
+            this.items = items;
+            this.isCube = isCube;
+        }
+
+        @Override
+        public Kind getKind() {
+            return Kind.ROLLUP_CUBE;
+        }
+
+        public List<GroupingSet> getItems() {
+            return items;
+        }
+
+        public boolean isCube() {
+            return isCube;
+        }
+    }
+
+    public static final class GroupingSets extends GroupingElement {
+
+        private final List<? extends GroupingElement> items;
+
+        public GroupingSets(List<? extends GroupingElement> items) {
+            this.items = items;
+        }
+
+        @Override
+        public Kind getKind() {
+            return Kind.GROUPING_SETS;
+        }
+
+        public List<? extends GroupingElement> getItems() {
+            return items;
+        }
+    }
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
index 1e16b70..1085e03 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
@@ -51,15 +51,19 @@
         // Group-by core rewrites
         rewriteGroupBys();
 
-        // Window expression core rewrites.
-        rewriteWindowExpressions();
-
         // Rewrites set operations.
         rewriteSetOperations();
 
         // Inlines column aliases.
         inlineColumnAlias();
 
+        // Window expression core rewrites.
+        rewriteWindowExpressions();
+
+        // Rewrites Group-By clauses with multiple grouping sets into UNION ALL
+        // Must run after rewriteSetOperations() and before variableCheckAndRewrite()
+        rewriteGroupingSets();
+
         // Generate ids for variables (considering scopes) and replace global variable access with the dataset function.
         variableCheckAndRewrite();
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
index 72de614..e52d604 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppQueryRewriter.java
@@ -66,6 +66,7 @@
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppBuiltinFunctionRewriteVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByAggregationSugarVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByVisitor;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupingSetsVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppInlineUdfsVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppListInputFunctionRewriteVisitor;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppWindowAggregationSugarVisitor;
@@ -144,6 +145,10 @@
         // Window expression core rewrites.
         rewriteWindowExpressions();
 
+        // Rewrites Group-By clauses with multiple grouping sets into UNION ALL
+        // Must run after rewriteSetOperations() and before variableCheckAndRewrite()
+        rewriteGroupingSets();
+
         // Generate ids for variables (considering scopes) and replace global variable access with the dataset function.
         variableCheckAndRewrite();
 
@@ -242,6 +247,11 @@
         rewriteTopExpr(groupByVisitor, null);
     }
 
+    protected void rewriteGroupingSets() throws CompilationException {
+        SqlppGroupingSetsVisitor groupingSetsVisitor = new SqlppGroupingSetsVisitor(context);
+        rewriteTopExpr(groupingSetsVisitor, null);
+    }
+
     protected void rewriteWindowExpressions() throws CompilationException {
         // Create window variables and extract aggregation inputs into LET clauses
         SqlppWindowRewriteVisitor windowVisitor = new SqlppWindowRewriteVisitor(context);
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/AbstractSqlppExpressionExtractionVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/AbstractSqlppExpressionExtractionVisitor.java
index 57bfad5..fc2e25b 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/AbstractSqlppExpressionExtractionVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/AbstractSqlppExpressionExtractionVisitor.java
@@ -39,13 +39,12 @@
 
 /**
  * Base class for visitors that extract expressions into LET clauses.
- * Subclasses should call {@link #extractExpressions(List, int)} to perform the extraction.
  */
 abstract class AbstractSqlppExpressionExtractionVisitor extends AbstractSqlppSimpleExpressionVisitor {
 
     protected final LangRewritingContext context;
 
-    private final Deque<List<Pair<Expression, VarIdentifier>>> stack = new ArrayDeque<>();
+    protected final Deque<StackElement> stack = new ArrayDeque<>();
 
     AbstractSqlppExpressionExtractionVisitor(LangRewritingContext context) {
         this.context = context;
@@ -53,30 +52,31 @@
 
     @Override
     public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
-        List<Pair<Expression, VarIdentifier>> extractionList = new ArrayList<>();
-        stack.push(extractionList);
+        StackElement stackElement = new StackElement(selectBlock);
+        stack.push(stackElement);
 
         if (selectBlock.hasFromClause()) {
             FromClause clause = selectBlock.getFromClause();
             clause.accept(this, arg);
-            if (!extractionList.isEmpty()) {
-                handleUnsupportedClause(clause, extractionList);
+            if (!stackElement.extractionList.isEmpty()) {
+                handleUnsupportedClause(clause);
             }
         }
         List<AbstractClause> letWhereList = selectBlock.getLetWhereList();
         if (!letWhereList.isEmpty()) {
-            visitLetWhereClauses(letWhereList, extractionList, arg);
+            visitLetWhereClauses(letWhereList, stackElement.extractionList, arg);
         }
         if (selectBlock.hasGroupbyClause()) {
             selectBlock.getGroupbyClause().accept(this, arg);
-            introduceLetClauses(extractionList, letWhereList);
+            introduceLetClauses(stackElement.extractionList, letWhereList);
         }
         List<AbstractClause> letHavingListAfterGby = selectBlock.getLetHavingListAfterGroupby();
         if (!letHavingListAfterGby.isEmpty()) {
-            visitLetWhereClauses(letHavingListAfterGby, extractionList, arg);
+            visitLetWhereClauses(letHavingListAfterGby, stackElement.extractionList, arg);
         }
         selectBlock.getSelectClause().accept(this, arg);
-        introduceLetClauses(extractionList, selectBlock.hasGroupbyClause() ? letHavingListAfterGby : letWhereList);
+        introduceLetClauses(stackElement.extractionList,
+                selectBlock.hasGroupbyClause() ? letHavingListAfterGby : letWhereList);
 
         stack.pop();
         return null;
@@ -108,32 +108,27 @@
         fromBindingList.clear();
     }
 
-    List<Expression> extractExpressions(List<Expression> exprList, int limit) {
-        List<Pair<Expression, VarIdentifier>> outLetList = stack.peek();
-        if (outLetList == null) {
-            return null;
+    abstract void handleUnsupportedClause(FromClause clause) throws CompilationException;
+
+    protected final class StackElement {
+
+        private final SelectBlock selectBlock;
+
+        private final List<Pair<Expression, VarIdentifier>> extractionList;
+
+        private StackElement(SelectBlock selectBlock) {
+            this.selectBlock = selectBlock;
+            this.extractionList = new ArrayList<>();
         }
-        int n = exprList.size();
-        List<Expression> newExprList = new ArrayList<>(n);
-        for (int i = 0; i < n; i++) {
-            Expression expr = exprList.get(i);
-            Expression newExpr;
-            if (i < limit && isExtractableExpression(expr)) {
-                VarIdentifier v = context.newVariable();
-                VariableExpr vExpr = new VariableExpr(v);
-                vExpr.setSourceLocation(expr.getSourceLocation());
-                outLetList.add(new Pair<>(expr, v));
-                newExpr = vExpr;
-            } else {
-                newExpr = expr;
-            }
-            newExprList.add(newExpr);
+
+        public SelectBlock getSelectBlock() {
+            return selectBlock;
         }
-        return newExprList;
+
+        public VarIdentifier addPendingLetClause(Expression expression) {
+            VarIdentifier letVar = context.newVariable();
+            extractionList.add(new Pair<>(expression, letVar));
+            return letVar;
+        }
     }
-
-    abstract boolean isExtractableExpression(Expression expr);
-
-    abstract void handleUnsupportedClause(FromClause clause, List<Pair<Expression, VarIdentifier>> extractionList)
-            throws CompilationException;
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/GenerateColumnNameVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/GenerateColumnNameVisitor.java
index 6d2a0eb..3067641 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/GenerateColumnNameVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/GenerateColumnNameVisitor.java
@@ -19,7 +19,10 @@
 
 package org.apache.asterix.lang.sqlpp.rewrites.visitor;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -47,6 +50,8 @@
  */
 public class GenerateColumnNameVisitor extends AbstractSqlppExpressionScopingVisitor {
 
+    private final Map<Expression, VarIdentifier> gbyKeyExprMap = new HashMap<>();
+
     private final Set<VarIdentifier> gbyKeyVars = new HashSet<>();
 
     public GenerateColumnNameVisitor(LangRewritingContext context) {
@@ -70,24 +75,34 @@
 
     @Override
     public Expression visit(GroupbyClause groupbyClause, ILangExpression arg) throws CompilationException {
+        //TODO:FIXME:GBY:REVISIT
+        gbyKeyExprMap.clear();
         gbyKeyVars.clear();
-        for (GbyVariableExpressionPair gbyKeyPair : groupbyClause.getGbyPairList()) {
-            if (gbyKeyPair.getVar() == null) {
-                Expression gbyKeyExpr = gbyKeyPair.getExpr();
-                SourceLocation sourceLoc = gbyKeyExpr.getSourceLocation();
-                VariableExpr varExpr;
-                try {
-                    varExpr = ExpressionToVariableUtil.getGeneratedVariable(gbyKeyExpr, false);
-                } catch (ParseException e) {
-                    throw new CompilationException(ErrorCode.PARSE_ERROR, e, sourceLoc);
+        for (List<GbyVariableExpressionPair> gbyPairList : groupbyClause.getGbyPairList()) {
+            for (GbyVariableExpressionPair gbyKeyPair : gbyPairList) {
+                if (gbyKeyPair.getVar() == null) {
+                    Expression gbyKeyExpr = gbyKeyPair.getExpr();
+                    SourceLocation sourceLoc = gbyKeyExpr.getSourceLocation();
+                    VariableExpr varExpr;
+                    VarIdentifier varId = gbyKeyExprMap.get(gbyKeyExpr);
+                    if (varId == null) {
+                        try {
+                            varExpr = ExpressionToVariableUtil.getGeneratedVariable(gbyKeyExpr, false);
+                        } catch (ParseException e) {
+                            throw new CompilationException(ErrorCode.PARSE_ERROR, e, sourceLoc);
+                        }
+                        if (varExpr == null || gbyKeyVars.contains(varExpr.getVar())) {
+                            varExpr = new VariableExpr(context.newVariable());
+                        }
+                        gbyKeyExprMap.put(gbyKeyExpr, varExpr.getVar());
+                    } else {
+                        varExpr = new VariableExpr(varId);
+                    }
+                    varExpr.setSourceLocation(sourceLoc);
+                    gbyKeyPair.setVar(varExpr);
                 }
-                if (varExpr == null || gbyKeyVars.contains(varExpr.getVar())) {
-                    varExpr = new VariableExpr(context.newVariable());
-                }
-                varExpr.setSourceLocation(sourceLoc);
-                gbyKeyPair.setVar(varExpr);
+                gbyKeyVars.add(gbyKeyPair.getVar().getVar());
             }
-            gbyKeyVars.add(gbyKeyPair.getVar().getVar());
         }
         return super.visit(groupbyClause, arg);
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SetOperationVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SetOperationVisitor.java
index dd02dbe..fe86cfc 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SetOperationVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SetOperationVisitor.java
@@ -87,16 +87,7 @@
         // Wraps the set operation part with a subquery.
         SelectExpression nestedSelectExpression = new SelectExpression(null, selectSetOperation, null, null, true);
         nestedSelectExpression.setSourceLocation(sourceLoc);
-        VariableExpr newBindingVar = new VariableExpr(context.newVariable()); // Binding variable for the subquery.
-        newBindingVar.setSourceLocation(sourceLoc);
-        FromTerm newFromTerm = new FromTerm(nestedSelectExpression, newBindingVar, null, null);
-        newFromTerm.setSourceLocation(sourceLoc);
-        FromClause newFromClause = new FromClause(new ArrayList<>(Collections.singletonList(newFromTerm)));
-        newFromClause.setSourceLocation(sourceLoc);
-        SelectClause selectClause = new SelectClause(new SelectElement(newBindingVar), null, false);
-        selectClause.setSourceLocation(sourceLoc);
-        SelectBlock selectBlock = new SelectBlock(selectClause, newFromClause, null, null, null);
-        selectBlock.setSourceLocation(sourceLoc);
+        SelectBlock selectBlock = createSelectBlock(nestedSelectExpression, context);
         SelectSetOperation newSelectSetOperation =
                 new SelectSetOperation(new SetOperationInput(selectBlock, null), null);
         newSelectSetOperation.setSourceLocation(sourceLoc);
@@ -107,4 +98,18 @@
         return super.visit(newSelectExpression, arg);
     }
 
+    static SelectBlock createSelectBlock(Expression inputExpr, LangRewritingContext context) {
+        SourceLocation sourceLoc = inputExpr.getSourceLocation();
+        VariableExpr newBindingVar = new VariableExpr(context.newVariable()); // Binding variable for the subquery.
+        newBindingVar.setSourceLocation(sourceLoc);
+        FromTerm newFromTerm = new FromTerm(inputExpr, newBindingVar, null, null);
+        newFromTerm.setSourceLocation(sourceLoc);
+        FromClause newFromClause = new FromClause(new ArrayList<>(Collections.singletonList(newFromTerm)));
+        newFromClause.setSourceLocation(sourceLoc);
+        SelectClause selectClause = new SelectClause(new SelectElement(newBindingVar), null, false);
+        selectClause.setSourceLocation(sourceLoc);
+        SelectBlock selectBlock = new SelectBlock(selectClause, newFromClause, null, null, null);
+        selectBlock.setSourceLocation(sourceLoc);
+        return selectBlock;
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupByVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupByVisitor.java
index c21e989..d337de9 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupByVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupByVisitor.java
@@ -23,34 +23,38 @@
 import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
 import org.apache.asterix.lang.sqlpp.clause.SelectClause;
 import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
 import org.apache.asterix.lang.sqlpp.visitor.CheckSql92AggregateVisitor;
-import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.hyracks.algebricks.common.utils.Pair;
 
 /**
  * A pre-processor that
  * <ul>
- *     <li>adds the group variable as well as its group field
- *         list into the AST. e.g., GROUP AS eis(e AS e, i AS i, s AS s)</li>
- *     <li>adds group by clause if select block contains SQL-92 agregate function but there's no group by clause</li>
+ * <li>adds the group variable as well as its group field
+ * list into the AST. e.g., GROUP AS eis(e AS e, i AS i, s AS s)</li>
+ * <li>adds group by clause if select block contains SQL-92 aggregate function but there's no group by clause</li>
+ * <li>extracts GROUPING(...) operations into LET clauses</li>
  * </ul>
  */
-public class SqlppGroupByVisitor extends AbstractSqlppSimpleExpressionVisitor {
-
-    private final LangRewritingContext context;
+public class SqlppGroupByVisitor extends AbstractSqlppExpressionExtractionVisitor {
 
     public SqlppGroupByVisitor(LangRewritingContext context) {
-        this.context = context;
+        super(context);
     }
 
     @Override
@@ -92,7 +96,7 @@
     private void rewriteSelectWithoutGroupBy(SelectBlock selectBlock) throws CompilationException {
         if (hasSql92Aggregate(selectBlock)) {
             // Adds an implicit group-by clause for SQL-92 global aggregate.
-            List<GbyVariableExpressionPair> gbyPairList = new ArrayList<>();
+            List<List<GbyVariableExpressionPair>> gbyPairList = new ArrayList<>();
             List<GbyVariableExpressionPair> decorPairList = new ArrayList<>();
             VariableExpr groupVar = new VariableExpr(context.newVariable());
             groupVar.setSourceLocation(selectBlock.getSourceLocation());
@@ -132,4 +136,37 @@
             SqlppVariableUtil.addToFieldVariableList(varExpr, outFieldList);
         }
     }
+
+    // AbstractSqlppExpressionExtractionVisitor
+
+    @Override
+    public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
+        Expression resultExpr = super.visit(callExpr, arg);
+        if (isGroupingOperation(resultExpr)) {
+            StackElement stackElement = stack.peek();
+            if (stackElement != null && stackElement.getSelectBlock().hasGroupbyClause()) {
+                VarIdentifier v = stackElement.addPendingLetClause(resultExpr);
+                VariableExpr vExpr = new VariableExpr(v);
+                vExpr.setSourceLocation(callExpr.getSourceLocation());
+                resultExpr = vExpr;
+            }
+        }
+        return resultExpr;
+    }
+
+    static boolean isGroupingOperation(Expression expr) {
+        if (expr.getKind() == Expression.Kind.CALL_EXPRESSION) {
+            CallExpr callExpr = (CallExpr) expr;
+            FunctionSignature fs = callExpr.getFunctionSignature();
+            return BuiltinFunctions.GROUPING.getName().equalsIgnoreCase(fs.getName());
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    void handleUnsupportedClause(FromClause clause) throws CompilationException {
+        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_USE_OF_IDENTIFIER, clause.getSourceLocation(),
+                BuiltinFunctions.GROUPING.getName());
+    }
 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupingSetsVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupingSetsVisitor.java
new file mode 100644
index 0000000..4e517da
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppGroupingSetsVisitor.java
@@ -0,0 +1,258 @@
+/*
+ * 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.asterix.lang.sqlpp.rewrites.visitor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.LongIntegerLiteral;
+import org.apache.asterix.lang.common.literal.NullLiteral;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+
+/**
+ * Rewrites GROUP BY clauses with multiple grouping sets into UNION ALL.
+ * Also rewrites valid GROUPING(...) operations into constants.
+ */
+public final class SqlppGroupingSetsVisitor extends AbstractSqlppSimpleExpressionVisitor {
+
+    private final LangRewritingContext context;
+
+    private final List<List<GbyVariableExpressionPair>> tmpGroupingSets = new ArrayList<>(1);
+
+    private final List<GbyVariableExpressionPair> tmpDecorPairList = new ArrayList<>();
+
+    private final Set<VariableExpr> tmpAllGroupingSetsVars = new LinkedHashSet<>();
+
+    private final Set<VariableExpr> tmpCurrentGroupingSetVars = new LinkedHashSet<>();
+
+    public SqlppGroupingSetsVisitor(LangRewritingContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public Expression visit(SelectSetOperation setOp, ILangExpression arg) throws CompilationException {
+        super.visit(setOp, arg);
+
+        SetOperationInput setOpInputLeft = setOp.getLeftInput();
+        SelectBlock selectBlockLeft = setOpInputLeft.getSelectBlock();
+        if (selectBlockLeft != null && selectBlockLeft.hasGroupbyClause()) {
+            setOpInputLeft.setSelectBlock(rewriteSelectBlock(selectBlockLeft));
+        }
+        if (setOp.hasRightInputs()) {
+            for (SetOperationRight setOpRight : setOp.getRightInputs()) {
+                SetOperationInput setOpInputRight = setOpRight.getSetOperationRightInput();
+                SelectBlock selectBlockRight = setOpInputRight.getSelectBlock();
+                if (selectBlockRight != null && selectBlockRight.hasGroupbyClause()) {
+                    setOpInputRight.setSelectBlock(rewriteSelectBlock(selectBlockRight));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private SelectBlock rewriteSelectBlock(SelectBlock selectBlock) throws CompilationException {
+        return selectBlock.getGroupbyClause().getGbyPairList().size() <= 1 ? rewriteZeroOrOneGroupingSet(selectBlock)
+                : rewriteMultipleGroupingSets(selectBlock);
+    }
+
+    private SelectBlock rewriteZeroOrOneGroupingSet(SelectBlock selectBlock) throws CompilationException {
+        // no UNION ALL, we only need to rewrite GROUPING(..) operations
+        GroupbyClause gby = selectBlock.getGroupbyClause();
+        List<List<GbyVariableExpressionPair>> groupingSets = gby.getGbyPairList();
+        if (groupingSets.size() > 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, gby.getSourceLocation(), "");
+        }
+        tmpAllGroupingSetsVars.clear();
+        getAllGroupingSetsVars(groupingSets, tmpAllGroupingSetsVars);
+        rewriteGroupingOperations(selectBlock, tmpAllGroupingSetsVars, tmpAllGroupingSetsVars);
+        return selectBlock;
+    }
+
+    private SelectBlock rewriteMultipleGroupingSets(SelectBlock selectBlock) throws CompilationException {
+        GroupbyClause gby = selectBlock.getGroupbyClause();
+        List<List<GbyVariableExpressionPair>> groupingSets = gby.getGbyPairList();
+        if (groupingSets.size() <= 1 || !gby.getDecorPairList().isEmpty()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, gby.getSourceLocation(), "");
+        }
+
+        tmpAllGroupingSetsVars.clear();
+        getAllGroupingSetsVars(groupingSets, tmpAllGroupingSetsVars);
+
+        int nGroupingSets = groupingSets.size();
+        List<SetOperationRight> newSetOpRightInputs = new ArrayList<>(nGroupingSets - 1);
+
+        // process 2nd and following grouping sets
+
+        for (int i = 1; i < nGroupingSets; i++) {
+            List<GbyVariableExpressionPair> groupingSet = groupingSets.get(i);
+
+            tmpCurrentGroupingSetVars.clear();
+            getGroupingSetVars(groupingSet, tmpCurrentGroupingSetVars);
+
+            tmpGroupingSets.clear();
+            tmpGroupingSets.add(groupingSet);
+            gby.setGbyPairList(tmpGroupingSets); // will be cloned by deepCopy() below
+
+            tmpDecorPairList.clear();
+            computeDecorVars(tmpAllGroupingSetsVars, tmpCurrentGroupingSetVars, tmpDecorPairList);
+            gby.setDecorPairList(tmpDecorPairList); // will be cloned by deepCopy() below
+
+            SelectBlock newSelectBlock = (SelectBlock) SqlppRewriteUtil.deepCopy(selectBlock);
+
+            rewriteGroupingOperations(newSelectBlock, tmpAllGroupingSetsVars, tmpCurrentGroupingSetVars);
+
+            SetOperationRight newSetOpRight =
+                    new SetOperationRight(SetOpType.UNION, false, new SetOperationInput(newSelectBlock, null));
+            newSetOpRightInputs.add(newSetOpRight);
+        }
+
+        // process 1st grouping set
+
+        List<GbyVariableExpressionPair> groupingSet = groupingSets.get(0);
+        gby.setGbyPairList(Collections.singletonList(groupingSet));
+
+        tmpCurrentGroupingSetVars.clear();
+        getGroupingSetVars(groupingSet, tmpCurrentGroupingSetVars);
+
+        List<GbyVariableExpressionPair> newDecorPairList = new ArrayList<>();
+        computeDecorVars(tmpAllGroupingSetsVars, tmpCurrentGroupingSetVars, newDecorPairList);
+        gby.setDecorPairList(newDecorPairList);
+
+        rewriteGroupingOperations(selectBlock, tmpAllGroupingSetsVars, tmpCurrentGroupingSetVars);
+
+        SetOperationInput newSetOpInput = new SetOperationInput(selectBlock, null);
+
+        SelectSetOperation newSetOp = new SelectSetOperation(newSetOpInput, newSetOpRightInputs);
+        newSetOp.setSourceLocation(selectBlock.getSourceLocation());
+
+        SelectExpression newSelectExpr = new SelectExpression(null, newSetOp, null, null, true);
+        newSelectExpr.setSourceLocation(selectBlock.getSourceLocation());
+
+        return SetOperationVisitor.createSelectBlock(newSelectExpr, context);
+    }
+
+    /**
+     * Valid GROUPING() operations can only be in LET clauses after GROUP BY.
+     * This is guaranteed by {@link SqlppGroupByVisitor}.
+     * These operations a rewritten into constants by this method.
+     * The remaining GROUPING() operations are invalid and will lead to a compile-time failure later
+     * because there's no runtime for GROUPING() function.
+     */
+    private void rewriteGroupingOperations(SelectBlock selectBlock, Set<VariableExpr> allGroupingSetsVars,
+            Set<VariableExpr> currentGroupingSetVars) throws CompilationException {
+        if (selectBlock.hasLetHavingClausesAfterGroupby()) {
+            for (Clause clause : selectBlock.getLetHavingListAfterGroupby()) {
+                if (clause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                    LetClause letClause = (LetClause) clause;
+                    Expression letExpr = letClause.getBindingExpr();
+                    if (SqlppGroupByVisitor.isGroupingOperation(letExpr)) {
+                        Expression newLetExpr = rewriteGroupingOperation((CallExpr) letExpr, allGroupingSetsVars,
+                                currentGroupingSetVars);
+                        letClause.setBindingExpr(newLetExpr);
+                    }
+                }
+            }
+        }
+    }
+
+    private Expression rewriteGroupingOperation(CallExpr callExpr, Set<VariableExpr> allGroupingSetsVars,
+            Set<VariableExpr> currentGroupingSetVars) throws CompilationException {
+        List<Expression> argList = callExpr.getExprList();
+        if (argList.isEmpty()) {
+            throw new CompilationException(ErrorCode.COMPILATION_INVALID_NUM_OF_ARGS, callExpr.getSourceLocation(),
+                    BuiltinFunctions.GROUPING.getName());
+        }
+        long result = 0;
+        for (Expression argExpr : argList) {
+            int v;
+            if (argExpr.getKind() == Expression.Kind.VARIABLE_EXPRESSION) {
+                VariableExpr varExpr = (VariableExpr) argExpr;
+                if (currentGroupingSetVars.contains(varExpr)) {
+                    v = 0;
+                } else if (allGroupingSetsVars.contains(varExpr)) {
+                    v = 1;
+                } else {
+                    throw new CompilationException(ErrorCode.COMPILATION_GROUPING_OPERATION_INVALID_ARG,
+                            argExpr.getSourceLocation());
+                }
+            } else {
+                throw new CompilationException(ErrorCode.COMPILATION_GROUPING_OPERATION_INVALID_ARG,
+                        argExpr.getSourceLocation());
+            }
+            result = (result << 1) + v;
+        }
+
+        LiteralExpr resultExpr = new LiteralExpr(new LongIntegerLiteral(result));
+        resultExpr.setSourceLocation(callExpr.getSourceLocation());
+        return resultExpr;
+    }
+
+    private static void getAllGroupingSetsVars(List<List<GbyVariableExpressionPair>> gbyList,
+            Set<VariableExpr> outVars) {
+        for (List<GbyVariableExpressionPair> gbyPairList : gbyList) {
+            getGroupingSetVars(gbyPairList, outVars);
+        }
+    }
+
+    private static void getGroupingSetVars(List<GbyVariableExpressionPair> groupingSet,
+            Collection<VariableExpr> outVars) {
+        for (GbyVariableExpressionPair gbyPair : groupingSet) {
+            outVars.add(gbyPair.getVar());
+        }
+    }
+
+    private static void computeDecorVars(Set<VariableExpr> allGroupingSetsVars,
+            Set<VariableExpr> currentGroupingSetVars, List<GbyVariableExpressionPair> outDecorPairList) {
+        for (VariableExpr var : allGroupingSetsVars) {
+            if (!currentGroupingSetVars.contains(var)) {
+                LiteralExpr nullExpr = new LiteralExpr(NullLiteral.INSTANCE);
+                nullExpr.setSourceLocation(var.getSourceLocation());
+                VariableExpr newDecorVarExpr = new VariableExpr(var.getVar());
+                newDecorVarExpr.setSourceLocation(var.getSourceLocation());
+                outDecorPairList.add(new GbyVariableExpressionPair(newDecorVarExpr, nullExpr));
+            }
+        }
+    }
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppWindowRewriteVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppWindowRewriteVisitor.java
index ba1e7f9..5c20bec 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppWindowRewriteVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppWindowRewriteVisitor.java
@@ -19,6 +19,7 @@
 
 package org.apache.asterix.lang.sqlpp.rewrites.visitor;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -34,7 +35,6 @@
 import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
 import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
 import org.apache.asterix.om.functions.BuiltinFunctions;
-import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 
 /**
@@ -88,8 +88,30 @@
         return winExpr;
     }
 
-    @Override
-    protected boolean isExtractableExpression(Expression expr) {
+    private List<Expression> extractExpressions(List<Expression> exprList, int limit) {
+        StackElement stackElement = stack.peek();
+        if (stackElement == null) {
+            return null;
+        }
+        int n = exprList.size();
+        List<Expression> newExprList = new ArrayList<>(n);
+        for (int i = 0; i < n; i++) {
+            Expression expr = exprList.get(i);
+            Expression newExpr;
+            if (i < limit && isExtractableExpression(expr)) {
+                VarIdentifier v = stackElement.addPendingLetClause(expr);
+                VariableExpr vExpr = new VariableExpr(v);
+                vExpr.setSourceLocation(expr.getSourceLocation());
+                newExpr = vExpr;
+            } else {
+                newExpr = expr;
+            }
+            newExprList.add(newExpr);
+        }
+        return newExprList;
+    }
+
+    private boolean isExtractableExpression(Expression expr) {
         switch (expr.getKind()) {
             case LITERAL_EXPRESSION:
             case VARIABLE_EXPRESSION:
@@ -100,8 +122,7 @@
     }
 
     @Override
-    void handleUnsupportedClause(FromClause clause, List<Pair<Expression, VarIdentifier>> extractionList)
-            throws CompilationException {
+    void handleUnsupportedClause(FromClause clause) throws CompilationException {
         throw new CompilationException(ErrorCode.COMPILATION_UNEXPECTED_WINDOW_EXPRESSION, clause.getSourceLocation());
     }
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SubstituteGroupbyExpressionWithVariableVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SubstituteGroupbyExpressionWithVariableVisitor.java
index 13f1128..69e28aa 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SubstituteGroupbyExpressionWithVariableVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SubstituteGroupbyExpressionWithVariableVisitor.java
@@ -20,6 +20,7 @@
 package org.apache.asterix.lang.sqlpp.rewrites.visitor;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -54,10 +55,12 @@
     public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
         if (selectBlock.hasGroupbyClause()) {
             Map<Expression, Expression> map = new HashMap<>();
-            for (GbyVariableExpressionPair gbyKeyPair : selectBlock.getGroupbyClause().getGbyPairList()) {
-                Expression gbyKeyExpr = gbyKeyPair.getExpr();
-                if (gbyKeyExpr.getKind() != Kind.VARIABLE_EXPRESSION) {
-                    map.put(gbyKeyExpr, gbyKeyPair.getVar());
+            for (List<GbyVariableExpressionPair> gbyPairList : selectBlock.getGroupbyClause().getGbyPairList()) {
+                for (GbyVariableExpressionPair gbyKeyPair : gbyPairList) {
+                    Expression gbyKeyExpr = gbyKeyPair.getExpr();
+                    if (gbyKeyExpr.getKind() != Kind.VARIABLE_EXPRESSION) {
+                        map.putIfAbsent(gbyKeyExpr, gbyKeyPair.getVar());
+                    }
                 }
             }
 
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java
index a369010..894aff3 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/struct/SetOperationInput.java
@@ -40,10 +40,18 @@
         return selectBlock;
     }
 
+    public void setSelectBlock(SelectBlock selectBlock) {
+        this.selectBlock = selectBlock;
+    }
+
     public SelectExpression getSubquery() {
         return subquery;
     }
 
+    public void setSubquery(SelectExpression subquery) {
+        this.subquery = subquery;
+    }
+
     public boolean selectBlock() {
         return selectBlock != null;
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java
index 361bcf6..26f2a35 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/util/SqlppVariableUtil.java
@@ -161,10 +161,13 @@
         if (gbyClause == null) {
             return bindingVars;
         }
-        for (GbyVariableExpressionPair gbyKey : gbyClause.getGbyPairList()) {
-            VariableExpr var = gbyKey.getVar();
-            if (var != null) {
-                bindingVars.add(var);
+        Set<VariableExpr> gbyKeyVars = new HashSet<>();
+        for (List<GbyVariableExpressionPair> gbyPairList : gbyClause.getGbyPairList()) {
+            for (GbyVariableExpressionPair gbyKey : gbyPairList) {
+                VariableExpr var = gbyKey.getVar();
+                if (var != null && gbyKeyVars.add(var)) {
+                    bindingVars.add(var);
+                }
             }
         }
         if (gbyClause.hasDecorList()) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java
index f918b41..18c2b35 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/CheckSubqueryVisitor.java
@@ -20,6 +20,7 @@
 package org.apache.asterix.lang.sqlpp.visitor;
 
 import java.util.Collection;
+import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.lang.common.base.ILangExpression;
@@ -264,9 +265,11 @@
 
     @Override
     public Boolean visit(GroupbyClause gc, ILangExpression arg) throws CompilationException {
-        for (GbyVariableExpressionPair key : gc.getGbyPairList()) {
-            if (visit(key.getExpr(), arg)) {
-                return true;
+        for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+            for (GbyVariableExpressionPair key : gbyPairList) {
+                if (visit(key.getExpr(), arg)) {
+                    return true;
+                }
             }
         }
         if (gc.hasDecorList()) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
index 56f160b..e661ac1 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/DeepCopyVisitor.java
@@ -283,11 +283,17 @@
 
     @Override
     public GroupbyClause visit(GroupbyClause gc, Void arg) throws CompilationException {
-        List<GbyVariableExpressionPair> gbyPairList = new ArrayList<>();
-        for (GbyVariableExpressionPair gbyVarExpr : gc.getGbyPairList()) {
-            VariableExpr var = gbyVarExpr.getVar();
-            gbyPairList.add(new GbyVariableExpressionPair(var == null ? null : (VariableExpr) var.accept(this, arg),
-                    (Expression) gbyVarExpr.getExpr().accept(this, arg)));
+        List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+        List<List<GbyVariableExpressionPair>> newGbyList = new ArrayList<>(gbyList.size());
+        for (List<GbyVariableExpressionPair> gbyPairList : gbyList) {
+            List<GbyVariableExpressionPair> newGbyPairList = new ArrayList<>(gbyPairList.size());
+            for (GbyVariableExpressionPair gbyVarExpr : gbyPairList) {
+                VariableExpr var = gbyVarExpr.getVar();
+                newGbyPairList
+                        .add(new GbyVariableExpressionPair(var == null ? null : (VariableExpr) var.accept(this, arg),
+                                (Expression) gbyVarExpr.getExpr().accept(this, arg)));
+            }
+            newGbyList.add(newGbyPairList);
         }
         List<GbyVariableExpressionPair> decorPairList = new ArrayList<>();
         if (gc.hasDecorList()) {
@@ -310,7 +316,7 @@
             groupVarExpr = (VariableExpr) gc.getGroupVar().accept(this, arg);
         }
         List<Pair<Expression, Identifier>> groupFieldList = copyFieldList(gc.getGroupFieldList(), arg);
-        GroupbyClause copy = new GroupbyClause(gbyPairList, decorPairList, withVarMap, groupVarExpr, groupFieldList,
+        GroupbyClause copy = new GroupbyClause(newGbyList, decorPairList, withVarMap, groupVarExpr, groupFieldList,
                 gc.hasHashGroupByHint(), gc.isGroupAll());
         copy.setSourceLocation(gc.getSourceLocation());
         return copy;
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java
index 7a4febd..e1ff4a7 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/FreeVariableVisitor.java
@@ -274,8 +274,10 @@
     @Override
     public Void visit(GroupbyClause gc, Collection<VariableExpr> freeVars) throws CompilationException {
         // Puts all group-by variables into the symbol set of the new scope.
-        for (GbyVariableExpressionPair gbyVarExpr : gc.getGbyPairList()) {
-            gbyVarExpr.getExpr().accept(this, freeVars);
+        for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+            for (GbyVariableExpressionPair gbyVarExpr : gbyPairList) {
+                gbyVarExpr.getExpr().accept(this, freeVars);
+            }
         }
         if (gc.hasDecorList()) {
             for (GbyVariableExpressionPair decorVarExpr : gc.getDecorPairList()) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java
index 33446de..d7ad1cb 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppAstPrintVisitor.java
@@ -275,23 +275,23 @@
             out.println(skip(step) + "Group All");
         } else {
             out.println(skip(step) + "Groupby");
-            for (GbyVariableExpressionPair pair : gc.getGbyPairList()) {
-                if (pair.getVar() != null) {
-                    pair.getVar().accept(this, step + 1);
-                    out.println(skip(step + 1) + ":=");
+            List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+            if (gbyList.size() == 1 && !gbyList.get(0).isEmpty()) {
+                // simplified format for the most common case
+                printGroupByPairList(gbyList.get(0), step + 1);
+            } else {
+                String sep = "";
+                for (List<GbyVariableExpressionPair> groupingSet : gbyList) {
+                    out.println(skip(step + 1) + "GROUPING SET (");
+                    printGroupByPairList(groupingSet, step + 2);
+                    out.println(skip(step + 1) + ")" + sep);
+                    sep = ",";
                 }
-                pair.getExpr().accept(this, step + 1);
             }
         }
         if (gc.hasDecorList()) {
             out.println(skip(step + 1) + "DECOR");
-            for (GbyVariableExpressionPair pair : gc.getDecorPairList()) {
-                if (pair.getVar() != null) {
-                    pair.getVar().accept(this, step + 1);
-                    out.println(skip(step + 1) + ":=");
-                }
-                pair.getExpr().accept(this, step + 1);
-            }
+            printGroupByPairList(gc.getDecorPairList(), step + 1);
         }
         if (gc.hasGroupVar()) {
             out.print(skip(step + 1) + "GROUP AS ");
@@ -312,6 +312,17 @@
         return null;
     }
 
+    private void printGroupByPairList(List<GbyVariableExpressionPair> gbyPairList, Integer step)
+            throws CompilationException {
+        for (GbyVariableExpressionPair pair : gbyPairList) {
+            if (pair.getVar() != null) {
+                pair.getVar().accept(this, step);
+                out.println(skip(step) + ":=");
+            }
+            pair.getExpr().accept(this, step);
+        }
+    }
+
     @Override
     public Void visit(CaseExpression caseExpr, Integer step) throws CompilationException {
         out.print(skip(step) + "CASE");
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java
index 9901c8c..bf71c05 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/SqlppFormatPrintVisitor.java
@@ -258,7 +258,21 @@
             out.println(skip(step) + "/* +hash */");
         }
         out.print(skip(step) + "group by ");
-        printDelimitedGbyExpressions(gc.getGbyPairList(), step + 2);
+        List<List<GbyVariableExpressionPair>> gbyList = gc.getGbyPairList();
+        if (gbyList.size() == 1) {
+            printDelimitedGbyExpressions(gbyList.get(0), step + 2);
+        } else {
+            out.print("grouping sets (");
+            for (int i = 0, n = gbyList.size(); i < n; i++) {
+                if (i > 0) {
+                    out.print(COMMA);
+                }
+                out.print("(");
+                printDelimitedGbyExpressions(gbyList.get(i), step + 2);
+                out.print(")");
+            }
+            out.print(")");
+        }
         out.println();
         return null;
     }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java
index e3f65e6..6495742 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppContainsExpressionVisitor.java
@@ -20,6 +20,7 @@
 package org.apache.asterix.lang.sqlpp.visitor.base;
 
 import java.util.Collection;
+import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.lang.common.base.ILangExpression;
@@ -256,9 +257,11 @@
 
     @Override
     public Boolean visit(GroupbyClause gc, T arg) throws CompilationException {
-        for (GbyVariableExpressionPair key : gc.getGbyPairList()) {
-            if (visit(key.getExpr(), arg)) {
-                return true;
+        for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+            for (GbyVariableExpressionPair key : gbyPairList) {
+                if (visit(key.getExpr(), arg)) {
+                    return true;
+                }
             }
         }
         if (gc.hasDecorList()) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
index 68f18d6..db85d73 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppExpressionScopingVisitor.java
@@ -22,6 +22,8 @@
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -237,11 +239,14 @@
         // or an outer scope query) should still be visible.
         Scope newScope = new Scope(scopeChecker, scopeChecker.getPrecedingScope());
         // Puts all group-by variables into the symbol set of the new scope.
-        for (GbyVariableExpressionPair gbyKeyVarExpr : gc.getGbyPairList()) {
-            gbyKeyVarExpr.setExpr(visit(gbyKeyVarExpr.getExpr(), gc));
-            VariableExpr gbyKeyVar = gbyKeyVarExpr.getVar();
-            if (gbyKeyVar != null) {
-                addNewVarSymbolToScope(newScope, gbyKeyVar.getVar(), gbyKeyVar.getSourceLocation());
+        Set<VariableExpr> gbyKeyVars = new HashSet<>(); // bindings from prior grouping sets //TODO:FIXME:GBY:REVISIT
+        for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+            for (GbyVariableExpressionPair gbyKeyVarExpr : gbyPairList) {
+                gbyKeyVarExpr.setExpr(visit(gbyKeyVarExpr.getExpr(), gc));
+                VariableExpr gbyKeyVar = gbyKeyVarExpr.getVar();
+                if (gbyKeyVar != null && gbyKeyVars.add(gbyKeyVar)) {
+                    addNewVarSymbolToScope(newScope, gbyKeyVar.getVar(), gbyKeyVar.getSourceLocation());
+                }
             }
         }
         if (gc.hasGroupFieldList()) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
index 0428a73..3d39dc0 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/visitor/base/AbstractSqlppSimpleExpressionVisitor.java
@@ -208,8 +208,10 @@
 
     @Override
     public Expression visit(GroupbyClause gc, ILangExpression arg) throws CompilationException {
-        for (GbyVariableExpressionPair gbyVarExpr : gc.getGbyPairList()) {
-            gbyVarExpr.setExpr(visit(gbyVarExpr.getExpr(), gc));
+        for (List<GbyVariableExpressionPair> gbyPairList : gc.getGbyPairList()) {
+            for (GbyVariableExpressionPair gbyVarExpr : gbyPairList) {
+                gbyVarExpr.setExpr(visit(gbyVarExpr.getExpr(), gc));
+            }
         }
         if (gc.hasDecorList()) {
             for (GbyVariableExpressionPair decVarExpr : gc.getDecorPairList()) {
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 0b9c394..53faff9 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -178,6 +178,11 @@
 import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
 import org.apache.asterix.lang.sqlpp.optype.JoinType;
 import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser;
+import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser.GroupingElement;
+import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser.GroupingSet;
+import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser.GroupingSets;
+import org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser.RollupCube;
 import org.apache.asterix.lang.sqlpp.parser.SqlppHint;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
@@ -204,10 +209,12 @@
 class SQLPPParser extends ScopeChecker implements IParser {
 
     // tokens parsed as identifiers
+    private static final String CUBE = "CUBE";
     private static final String CURRENT = "CURRENT";
     private static final String EXCLUDE = "EXCLUDE";
     private static final String FIRST = "FIRST";
     private static final String FOLLOWING = "FOLLOWING";
+    private static final String GROUPING = "GROUPING";
     private static final String GROUPS = "GROUPS";
     private static final String IGNORE = "IGNORE";
     private static final String LAST = "LAST";
@@ -218,8 +225,10 @@
     private static final String PRECEDING = "PRECEDING";
     private static final String RANGE = "RANGE";
     private static final String RESPECT = "RESPECT";
+    private static final String ROLLUP = "ROLLUP";
     private static final String ROW = "ROW";
     private static final String ROWS = "ROWS";
+    private static final String SETS = "SETS";
     private static final String TIES = "TIES";
     private static final String UNBOUNDED = "UNBOUNDED";
     private static final String ACTION = "ACTION";
@@ -239,6 +248,8 @@
 
     private DataverseName defaultDataverse;
 
+    private SqlppGroupingSetsParser groupingSetsParser;
+
     private final WarningCollector warningCollector = new WarningCollector();
 
     private final Map<SourceLocation, String> hintCollector = new HashMap<SourceLocation, String>();
@@ -476,6 +487,11 @@
       return new SqlppParseException(getSourceLocation(token), message);
     }
 
+    private boolean laToken(int idx, int kind) {
+      Token t = getToken(idx);
+      return t.kind == kind;
+    }
+
     private boolean laToken(int idx, int kind, String image) {
       Token t = getToken(idx);
       return t.kind == kind && t.image.equalsIgnoreCase(image);
@@ -4039,59 +4055,44 @@
 {
     Token startToken = null;
     GroupbyClause gbc = new GroupbyClause();
-    List<GbyVariableExpressionPair> vePairList = new ArrayList<GbyVariableExpressionPair>();
-    VariableExpr var = null;
-    Expression expr = null;
-    VariableExpr decorVar = null;
-    Expression decorExpr = null;
+    List<List<GbyVariableExpressionPair>> gbyList = null;
+    List<GroupingElement> groupingElementList = null;
     Pair<VariableExpr, List<Pair<Expression, Identifier>>> groupVarWithFieldList = null;
     VariableExpr groupVar = null;
     List<Pair<Expression, Identifier>> groupFieldList = null;
 }
 {
-      {
-        Scope newScope = extendCurrentScopeNoPush(true);
-        // extendCurrentScope(true);
-      }
+    {
+      Scope newScope = extendCurrentScopeNoPush(true);
+      // extendCurrentScope(true);
+    }
     <GROUP>
-      {
-         startToken = token;
-         Token hintToken = fetchHint(token, SqlppHint.HASH_GROUP_BY_HINT);
-         if (hintToken != null) {
-            gbc.setHashGroupByHint(true);
-         }
+    {
+      startToken = token;
+      Token hintToken = fetchHint(token, SqlppHint.HASH_GROUP_BY_HINT);
+      if (hintToken != null) {
+        gbc.setHashGroupByHint(true);
       }
-    <BY> (
-       expr = Expression()
-       (LOOKAHEAD(1) (<AS>)?
-        var = Variable()
-        )?
-        {
-            GbyVariableExpressionPair pair1 = new GbyVariableExpressionPair(var, expr);
-            vePairList.add(pair1);
-        }
-       ( LOOKAHEAD(1) <COMMA>
-         {
-            var = null;
-         }
-         expr = Expression()
-         (LOOKAHEAD(1)  (<AS>)?
-         var = Variable()
-         )?
-         {
-             GbyVariableExpressionPair pair2 = new GbyVariableExpressionPair(var, expr);
-             vePairList.add(pair2);
-         }
-        )*
-    )
-    (<GROUP> <AS> groupVarWithFieldList = VariableWithFieldMap()
+    }
+    <BY> groupingElementList = GroupingElementList()
+    (
+      <GROUP> <AS> groupVarWithFieldList = VariableWithFieldMap()
       {
         groupVar = groupVarWithFieldList.first;
         groupFieldList = groupVarWithFieldList.second;
       }
     )?
     {
-      gbc.setGbyPairList(vePairList);
+      if (groupingSetsParser == null) {
+        groupingSetsParser = new SqlppGroupingSetsParser();
+      }
+      SourceLocation sourceLoc = getSourceLocation(startToken);
+      try {
+        gbyList = groupingSetsParser.parse(groupingElementList, sourceLoc);
+      } catch (CompilationException e) {
+        throw new SqlppParseException(sourceLoc, e.getMessage());
+      }
+      gbc.setGbyPairList(gbyList);
       gbc.setDecorPairList(new ArrayList<GbyVariableExpressionPair>());
       gbc.setWithVarMap(new HashMap<Expression, VariableExpr>());
       gbc.setGroupVar(groupVar);
@@ -4101,6 +4102,114 @@
     }
 }
 
+List<GroupingElement> GroupingElementList() throws ParseException:
+{
+  List<GroupingElement> groupingElementList = new ArrayList<GroupingElement>();
+  GroupingElement groupingElement = null;
+}
+{
+  groupingElement = GroupingElement() { groupingElementList.add(groupingElement); }
+  ( LOOKAHEAD(1) <COMMA> groupingElement = GroupingElement() { groupingElementList.add(groupingElement); } )*
+  {
+    return groupingElementList;
+  }
+}
+
+GroupingElement GroupingElement() throws ParseException:
+{
+  GroupingElement groupingElement = null;
+  List<GroupingSet> groupingSets = null;
+  List<GroupingElement> groupingElements = null;
+}
+{
+  (
+    LOOKAHEAD(2)
+    <LEFTPAREN> <RIGHTPAREN>
+    {
+      groupingElement = GroupingSet.EMPTY;
+    }
+    |
+    LOOKAHEAD({ laIdentifier(ROLLUP) && laToken(2, LEFTPAREN) })
+    <IDENTIFIER> { expectToken(ROLLUP); }
+    <LEFTPAREN> groupingSets = OrdinaryGroupingSetList() <RIGHTPAREN>
+    {
+      groupingElement = new RollupCube(groupingSets, false);
+    }
+    |
+    LOOKAHEAD({ laIdentifier(CUBE) && laToken(2, LEFTPAREN) })
+    <IDENTIFIER> { expectToken(CUBE); }
+    <LEFTPAREN> groupingSets = OrdinaryGroupingSetList() <RIGHTPAREN>
+    {
+      groupingElement = new RollupCube(groupingSets, true);
+    }
+    |
+    LOOKAHEAD({ laIdentifier(GROUPING) && laIdentifier(2, SETS) && laToken(3, LEFTPAREN) })
+    <IDENTIFIER> { expectToken(GROUPING); } <IDENTIFIER> { expectToken(SETS); }
+    <LEFTPAREN> groupingElements = GroupingElementList() <RIGHTPAREN>
+    {
+      groupingElement = new GroupingSets(groupingElements);
+    }
+    |
+    groupingElement = OrdinaryGroupingSet()
+  )
+  {
+    return groupingElement;
+  }
+}
+
+GroupingSet OrdinaryGroupingSet() throws ParseException:
+{
+  GbyVariableExpressionPair gbyExprPair = null;
+  List<GbyVariableExpressionPair> items = null;
+}
+{
+  (
+    LOOKAHEAD(1) <LEFTPAREN> items = GbyVariableExpressionPairList() <RIGHTPAREN>
+    | gbyExprPair = GbyVariableExpressionPair() { items = Collections.singletonList(gbyExprPair); }
+  )
+  {
+    return new GroupingSet(items);
+  }
+}
+
+List<GroupingSet> OrdinaryGroupingSetList() throws ParseException:
+{
+  GroupingSet groupingSet = null;
+  List<GroupingSet> items = new ArrayList<GroupingSet>();
+}
+{
+  groupingSet = OrdinaryGroupingSet() { items.add(groupingSet); }
+  ( LOOKAHEAD(1) <COMMA> groupingSet = OrdinaryGroupingSet() { items.add(groupingSet); } )*
+  {
+    return items;
+  }
+}
+
+List<GbyVariableExpressionPair> GbyVariableExpressionPairList() throws ParseException:
+{
+  GbyVariableExpressionPair gbyExprPair = null;
+  List<GbyVariableExpressionPair> items = new ArrayList<GbyVariableExpressionPair>();
+}
+{
+  gbyExprPair = GbyVariableExpressionPair() { items.add(gbyExprPair); }
+  ( LOOKAHEAD(1) <COMMA> gbyExprPair = GbyVariableExpressionPair() { items.add(gbyExprPair); } )*
+  {
+    return items;
+  }
+}
+
+GbyVariableExpressionPair GbyVariableExpressionPair() throws ParseException:
+{
+  Expression expr = null;
+  VariableExpr var = null;
+}
+{
+  expr = Expression() ( (<AS>)? var = Variable() )?
+  {
+    return new GbyVariableExpressionPair(var, expr);
+  }
+}
+
 HavingClause HavingClause() throws ParseException:
 {
    Token startToken = null;
diff --git a/asterixdb/asterix-lang-sqlpp/src/test/java/org/apache/asterix/lang/sqlpp/parser/SqlppGroupingSetsParserTest.java b/asterixdb/asterix-lang-sqlpp/src/test/java/org/apache/asterix/lang/sqlpp/parser/SqlppGroupingSetsParserTest.java
new file mode 100644
index 0000000..f597c6b
--- /dev/null
+++ b/asterixdb/asterix-lang-sqlpp/src/test/java/org/apache/asterix/lang/sqlpp/parser/SqlppGroupingSetsParserTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.asterix.lang.sqlpp.parser;
+
+import static org.apache.asterix.lang.sqlpp.parser.SqlppGroupingSetsParser.GROUPING_SETS_LIMIT;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.base.IParser;
+import org.apache.asterix.lang.common.base.Statement;
+import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.SqlppFormatPrintVisitor;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+import org.apache.hyracks.util.MathUtil;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test rewriting from GROUP BY's grouping elements into grouping sets:
+ * <p>
+ * GROUP BY {0} => GROUP BY GROUPING SETS ({1})
+ */
+@RunWith(Parameterized.class)
+public class SqlppGroupingSetsParserTest {
+
+    private static final int GROUPING_SETS_LIMIT_LOG2 = MathUtil.log2Floor(GROUPING_SETS_LIMIT);
+
+    private static final int GROUPING_SETS_LIMIT_SQRT = (int) Math.ceil(Math.sqrt(GROUPING_SETS_LIMIT));
+
+    private static final String ERR_PREFIX = "ASX";
+
+    private static final String ERR_SYNTAX = ERR_PREFIX + ErrorCode.PARSE_ERROR;
+
+    private static final String ERR_OVERFLOW = ERR_PREFIX + ErrorCode.COMPILATION_GROUPING_SETS_OVERFLOW;
+
+    private static final String ERR_ALIAS = ERR_PREFIX + ErrorCode.COMPILATION_UNEXPECTED_ALIAS;
+
+    @Parameterized.Parameters(name = "{index}: GROUP BY {0}")
+    public static Collection<Object[]> data() {
+        return Arrays
+                .asList(new Object[][] {
+                        // GROUP BY {0} => GROUP BY GROUPING SETS ({1})
+                        //
+                        // 1. Basic
+                        //
+                        { "()", "()" },
+                        //
+                        { "(), ()", "()" },
+                        //
+                        { "a", "(a)" },
+                        //
+                        { "a,b", "(a,b)" },
+                        //
+                        { "a,(b,c)", "(a,b,c)" },
+                        //
+                        { "(a,b),(c,d)", "(a,b,c,d)" },
+                        //
+                        // 2. Rollup
+                        //
+                        { "rollup(a,b,c)", "(a,b,c)(a,b)(a)()" },
+                        //
+                        { "Rollup(a,(b,c),d)", "(a,b,c,d)(a,b,c)(a)()" },
+                        //
+                        { "RollUP((a,b),(c,d))", "(a,b,c,d)(a,b)()" },
+                        //
+                        { "a,ROLLUP(a,b)", "(a,b)(a)(a)" },
+                        //
+                        { "a,ROLLUP(a,b),c", "(a,b,c)(a,c)(a,c)" },
+                        //
+                        { "a,b,ROLLUP(c,d)", "(a,b,c,d)(a,b,c)(a,b)" },
+                        //
+                        { "ROLLUP(a,b),ROLLUP(c,d)", "(a,b,c,d)(a,b,c)(a,b)(a,c,d)(a,c)(a)(c,d)(c)()" },
+                        //
+                        // 3. Cube
+                        //
+                        { "cube(a,b,c)", "(a,b,c)(a,b)(a,c)(a)(b,c)(b)(c)()" },
+                        //
+                        { "Cube(a,(b,c),d)", "(a,b,c,d)(a,b,c)(a,d)(a)(b,c,d)(b,c)(d)()" },
+                        //
+                        { "CubE((a,b),(c,d))", "(a,b,c,d)(a,b)(c,d)()" },
+                        //
+                        { "a,CUBE(a,b)", "(a,b)(a)(a,b)(a)" },
+                        //
+                        { "a,CUBE(a,b),c", "(a,b,c)(a,c)(a,b,c)(a,c)" },
+                        //
+                        { "a,b,CUBE(c,d)", "(a,b,c,d)(a,b,c)(a,b,d)(a,b)" },
+                        //
+                        { "CUBE(a,b),CUBE(c,d)",
+                                "(a,b,c,d)(a,b,c)(a,b,d)(a,b)(a,c,d)(a,c)(a,d)(a)(b,c,d)(b,c)(b,d)(b)(c,d)(c)(d)()" },
+                        //
+                        // 4. Rollup + Cube
+                        //
+                        { "ROLLUP(a,b),CUBE(c,d)", "(a,b,c,d)(a,b,c)(a,b,d)(a,b)(a,c,d)(a,c)(a,d)(a)(c,d)(c)(d)()" },
+                        //
+                        { "CUBE(a,b),ROLLUP(c,d)", "(a,b,c,d)(a,b,c)(a,b)(a,c,d)(a,c)(a)(b,c,d)(b,c)(b)(c,d)(c)()" },
+                        //
+                        // 5. Grouping Sets
+                        //
+                        { "grouping sets(())", "()" },
+                        //
+                        { "Grouping Sets((), ())", "()()" },
+                        //
+                        { "Grouping setS(()), GROUPING SETS(())", "()" },
+                        //
+                        { "GROUPING SETS((a),(a,b))", "(a)(a,b)" },
+                        //
+                        { "GROUPING SETS((a,b),(a,b))", "(a,b)(a,b)" },
+                        //
+                        { "GROUPING SETS((a,b)),GROUPING SETS((a,b))", "(a,b)" },
+                        //
+                        { "GROUPING SETS((a,b),(c)),GROUPING SETS((d,e),())", "(a,b,d,e)(a,b)(c,d,e)(c)" },
+                        //
+                        { "GROUPING SETS(ROLLUP(a,b),ROLLUP(c,d))", "(a,b)(a)()(c,d)(c)()" },
+                        //
+                        { "GROUPING SETS(ROLLUP(a,b)), GROUPING SETS(ROLLUP(c,d))",
+                                "(a,b,c,d)(a,b,c)(a,b)(a,c,d)(a,c)(a)(c,d)(c)()" },
+                        //
+                        { "GROUPING SETS((a, b), GROUPING SETS((c,d), (e,f)))", "(a,b)(c,d)(e,f)" },
+                        //
+                        { "GROUPING SETS(ROLLUP(a,b)),GROUPING SETS(ROLLUP(c,d))",
+                                "(a,b,c,d)(a,b,c)(a,b)(a,c,d)(a,c)(a)(c,d)(c)()" },
+                        //
+                        // 6. Variable names (aliases)
+                        //
+                        { "a as x, b as y", "(a as x,b as y)" },
+                        //
+                        { "ROLLUP(a as x, b as y),ROLLUP(a, b)",
+                                "(a as x,b as y)(a as x,b as y)(a as x,b as y)(a as x,b as y)"
+                                        + "(a as x)(a as x)(a as x,b as y)(a as x)()" },
+                        //
+                        { "CUBE(a as x, b as y),CUBE(a, b)",
+                                "(a as x,b as y)(a as x,b as y)(a as x,b as y)(a as x,b as y)"
+                                        + "(a as x,b as y)(a as x)(a as x,b as y)(a as x)(b as y,a as x)(b as y,a as x)(b as y)(b as y)"
+                                        + "(a as x,b as y)(a as x)(b as y)()" },
+                        //
+                        { "GROUPING SETS((e1 as x, e2 as y)), GROUPING SETS((e1, e2))", "(e1 as x,e2 as y)" },
+                        //
+                        { "GROUPING SETS((e1 as x, e2 as y), (e1, e2))", "(e1 as x,e2 as y)(e1 as x,e2 as y)" },
+                        //
+                        // 7. Errors
+                        //
+                        // Syntax error
+                        { "ROLLUP()", ERR_SYNTAX },
+                        //
+                        // Syntax error
+                        { "CUBE()", ERR_SYNTAX },
+                        //
+                        // Too many grouping sets when expanding a rollup
+                        { String.format("ROLLUP(%s)", generateSimpleGroupingSet("a", GROUPING_SETS_LIMIT)),
+                                ERR_OVERFLOW },
+                        //
+                        // Too many grouping sets when expanding a cube
+                        { String.format("CUBE(%s)", generateSimpleGroupingSet("a", GROUPING_SETS_LIMIT_LOG2 + 1)),
+                                ERR_OVERFLOW },
+                        //
+                        // Too many grouping sets when doing a cross product of grouping sets
+                        { String.format("GROUPING SETS(%s), GROUPING SETS(%s)",
+                                generateSimpleGroupingSet("a", GROUPING_SETS_LIMIT_SQRT),
+                                generateSimpleGroupingSet("b", GROUPING_SETS_LIMIT_SQRT)), ERR_OVERFLOW },
+                        //
+                        // Unexpected aliases
+                        //
+                        { "ROLLUP(a as x, b),ROLLUP(a, b as y)", ERR_ALIAS },
+                        //
+                        { "CUBE(a as x, b),CUBE(a, b as y)", ERR_ALIAS },
+                        //
+                        { "GROUPING SETS((e1 as x, e2), (e1, e2 as y))", ERR_ALIAS },
+                        //
+                        { "GROUPING SETS((e1 as x, e2)), GROUPING SETS((e1, e2 as y))", ERR_ALIAS },
+                        //
+                        { "GROUPING SETS((e1 as a, e2 as b)), GROUPING SETS((e1 as c, e2 as d))", ERR_ALIAS }, });
+    }
+
+    private final String groupbyInput;
+
+    private final String expectedGroupingSets;
+
+    private final String expectedErrorCode;
+
+    public SqlppGroupingSetsParserTest(String groupbyInput, String expectedResult) {
+        this.groupbyInput = groupbyInput;
+        if (expectedResult.startsWith(ERR_PREFIX)) {
+            this.expectedGroupingSets = null;
+            this.expectedErrorCode = expectedResult;
+        } else {
+            this.expectedGroupingSets = expectedResult;
+            this.expectedErrorCode = null;
+        }
+    }
+
+    @Test
+    public void test() throws Exception {
+        SqlppParserFactory parserFactory = new SqlppParserFactory();
+        String groupbyClause = "GROUP BY " + groupbyInput;
+        String query = "SELECT COUNT(*) FROM test " + groupbyClause + ";";
+        // parse 2 queries so we can test calling rewrite() multiple times on the same instance
+        IParser parser = parserFactory.createParser(query + query);
+        List<Statement> statements;
+        try {
+            statements = parser.parse();
+        } catch (CompilationException e) {
+            if (expectedErrorCode == null) {
+                throw e;
+            } else if (e.getMessage().contains(expectedErrorCode)) {
+                return; // Found expected error code. SUCCESS
+            } else {
+                Assert.fail(String.format("Unable to find expected error code %s in error message: %s",
+                        expectedErrorCode, e.getMessage()));
+                throw new IllegalStateException();
+            }
+        }
+        Assert.assertEquals(2, statements.size());
+
+        for (Statement statement : statements) {
+            String groupingSets = extractGroupingSets(statement);
+            Assert.assertEquals(expectedGroupingSets, groupingSets);
+        }
+    }
+
+    private String extractGroupingSets(Statement stmt) throws Exception {
+        SqlppGroupingSetsRewriterTestVisitor visitor = new SqlppGroupingSetsRewriterTestVisitor();
+        stmt.accept(visitor, null);
+        return visitor.printGroupingSets();
+    }
+
+    private static class SqlppGroupingSetsRewriterTestVisitor extends AbstractSqlppSimpleExpressionVisitor {
+
+        private final List<List<GbyVariableExpressionPair>> groupingSets = new ArrayList<>();
+
+        private final StringWriter stringWriter = new StringWriter();
+
+        private final PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        private final SqlppFormatPrintVisitor printVisitor = new SqlppFormatPrintVisitor(printWriter);
+
+        @Override
+        public Expression visit(GroupbyClause gc, ILangExpression arg) throws CompilationException {
+            groupingSets.addAll(gc.getGbyPairList());
+            return super.visit(gc, arg);
+        }
+
+        public String printGroupingSets() throws CompilationException {
+            StringBuffer sb = stringWriter.getBuffer();
+            sb.delete(0, sb.length());
+
+            for (List<GbyVariableExpressionPair> groupingSet : groupingSets) {
+                printWriter.append('(');
+                String sep = "";
+                for (GbyVariableExpressionPair pair : groupingSet) {
+                    printWriter.append(sep);
+                    sep = ",";
+                    pair.getExpr().accept(printVisitor, 0);
+                    if (pair.getVar() != null) {
+                        String ident = SqlppVariableUtil.toUserDefinedName(pair.getVar().getVar().getValue());
+                        printWriter.append(" as ").append(ident);
+                    }
+                }
+                printWriter.append(')');
+            }
+            printWriter.flush();
+            return stringWriter.toString();
+        }
+    }
+
+    private static String generateSimpleGroupingSet(String prefix, int n) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < n; i++) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append(prefix).append(i);
+        }
+        return sb.toString();
+    }
+}
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
index 51f5acd..3570aff 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
@@ -198,6 +198,8 @@
             FunctionConstants.ASTERIX_NS, "ordered-list-constructor", FunctionIdentifier.VARARGS);
     public static final FunctionIdentifier UNORDERED_LIST_CONSTRUCTOR = new FunctionIdentifier(
             FunctionConstants.ASTERIX_NS, "unordered-list-constructor", FunctionIdentifier.VARARGS);
+    public static final FunctionIdentifier GROUPING =
+            new FunctionIdentifier(FunctionConstants.ASTERIX_NS, "grouping", FunctionIdentifier.VARARGS);
 
     public static final FunctionIdentifier DEEP_EQUAL =
             new FunctionIdentifier(FunctionConstants.ASTERIX_NS, "deep-equal", 2);
@@ -1650,6 +1652,7 @@
         addFunction(BOOLEAN_CONSTRUCTOR, ABooleanTypeComputer.INSTANCE, true);
         addFunction(CIRCLE_CONSTRUCTOR, ACircleTypeComputer.INSTANCE, true);
         addPrivateFunction(CONCAT_NON_NULL, ConcatNonNullTypeComputer.INSTANCE, true);
+        addFunction(GROUPING, AInt64TypeComputer.INSTANCE, true);
 
         addPrivateFunction(COUNTHASHED_GRAM_TOKENS, OrderedListOfAInt32TypeComputer.INSTANCE, true);
         addPrivateFunction(COUNTHASHED_WORD_TOKENS, OrderedListOfAInt32TypeComputer.INSTANCE, true);
diff --git a/asterixdb/pom.xml b/asterixdb/pom.xml
index 1538cbb..7cc4a3e 100644
--- a/asterixdb/pom.xml
+++ b/asterixdb/pom.xml
@@ -1348,7 +1348,17 @@
       <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-csv</artifactId>
-        <version>1.8</version>
+	      <version>1.8</version>
+      </dependency>
+      <dependency>
+        <groupId>org.testcontainers</groupId>
+        <artifactId>postgresql</artifactId>
+        <version>1.13.0</version>
+      </dependency>
+      <dependency>
+        <groupId>org.postgresql</groupId>
+        <artifactId>postgresql</artifactId>
+        <version>42.2.10</version>
       </dependency>
     </dependencies>
   </dependencyManagement>
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnionAllOperator.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnionAllOperator.java
index 9defb4f2..ba5ef5a 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnionAllOperator.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnionAllOperator.java
@@ -77,25 +77,66 @@
 
     @Override
     public void recomputeSchema() {
-        schema = new ArrayList<LogicalVariable>();
-        for (LogicalVariable v1 : inputs.get(0).getValue().getSchema()) {
-            for (Triple<LogicalVariable, LogicalVariable, LogicalVariable> t : varMap) {
-                if (t.first.equals(v1)) {
-                    schema.add(t.third);
-                } else {
-                    schema.add(v1);
-                }
+        // Assume input schemas are
+        // input0 = [a,b,c]
+        // input1 = [d,e,f,g,h]
+        // and
+        // UNION ALL mapping is
+        // [a,d -> X], [c,f -> Y]
+        //
+        // In order to compute the output schema we need to pick a larger input
+        // out of these two and replace variables there using UNION ALL mappings.
+        // Therefore in this example we'll pick input1 and the output schema will be
+        // [X,e,Y,g,h]
+        //
+        // Note that all input variables are out of scope after UNION ALL
+        // therefore it's ok return them in the output schema because
+        // no parent operator will refer to them.
+        // Also note the all UNION ALL operators in the final optimized plan
+        // will have input schemas that exactly match their mappings.
+        // This is guaranteed by InsertProjectBeforeUnionRule.
+        // In this example in the final optimized plan
+        // input0 schema will be [a,c]
+        // input1 schema will be [d,f]
+
+        List<LogicalVariable> inputSchema0 = inputs.get(0).getValue().getSchema();
+        List<LogicalVariable> inputSchema1 = inputs.get(1).getValue().getSchema();
+
+        List<LogicalVariable> inputSchema;
+        int inputSchemaIdx;
+        if (inputSchema0.size() >= inputSchema1.size()) {
+            inputSchema = inputSchema0;
+            inputSchemaIdx = 0;
+        } else {
+            inputSchema = inputSchema1;
+            inputSchemaIdx = 1;
+        }
+
+        schema = new ArrayList<>(inputSchema.size());
+        for (LogicalVariable inVar : inputSchema) {
+            LogicalVariable outVar = findOutputVar(inVar, inputSchemaIdx);
+            schema.add(outVar != null ? outVar : inVar);
+        }
+    }
+
+    private LogicalVariable findOutputVar(LogicalVariable inputVar, int inputIdx) {
+        for (Triple<LogicalVariable, LogicalVariable, LogicalVariable> t : varMap) {
+            LogicalVariable testVar;
+            switch (inputIdx) {
+                case 0:
+                    testVar = t.first;
+                    break;
+                case 1:
+                    testVar = t.second;
+                    break;
+                default:
+                    throw new IllegalArgumentException(String.valueOf(inputIdx));
+            }
+            if (inputVar.equals(testVar)) {
+                return t.third;
             }
         }
-        for (LogicalVariable v2 : inputs.get(1).getValue().getSchema()) {
-            for (Triple<LogicalVariable, LogicalVariable, LogicalVariable> t : varMap) {
-                if (t.second.equals(v2)) {
-                    schema.add(t.third);
-                } else {
-                    schema.add(v2);
-                }
-            }
-        }
+        return null;
     }
 
     @Override