You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by ta...@apache.org on 2020/07/14 03:13:35 UTC

[impala] branch master updated (fc19e70 -> 574fef2)

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

tarmstrong pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git.


    from fc19e70  IMPALA-5534: Fix and enable experimental failure tests
     new 3e1e7da  IMPALA-9898: generate grouping set plans
     new 574fef2  IMPALA-9917: grouping() and grouping_id() support

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 fe/src/main/cup/sql-parser.cup                     |    5 +
 .../main/java/org/apache/impala/analysis/Expr.java |   21 +
 .../apache/impala/analysis/FunctionCallExpr.java   |   39 +-
 .../org/apache/impala/analysis/GroupByClause.java  |   31 +-
 .../apache/impala/analysis/MultiAggregateInfo.java |  222 +-
 .../org/apache/impala/analysis/SelectStmt.java     |   12 +-
 .../apache/impala/catalog/AggregateFunction.java   |   18 +
 .../java/org/apache/impala/catalog/BuiltinsDb.java |   11 +
 .../java/org/apache/impala/common/RuntimeEnv.java  |   11 -
 .../apache/impala/planner/SingleNodePlanner.java   |   32 +-
 .../apache/impala/analysis/AnalyzeStmtsTest.java   |  117 +-
 .../org/apache/impala/analysis/ParserTest.java     |   61 +-
 .../java/org/apache/impala/analysis/ToSqlTest.java |    4 -
 .../org/apache/impala/planner/PlannerTest.java     |    5 +
 .../queries/PlannerTest/grouping-sets.test         | 1331 ++++++++
 .../queries/PlannerTest/subquery-rewrite.test      |  175 ++
 .../queries/PlannerTest/tpcds-all.test             | 3182 ++++++++++++++++++++
 .../queries/QueryTest/grouping-sets.test           |  327 ++
 .../queries/QueryTest/views-compatibility.test     |   29 +
 .../tpcds/queries/tpcds-decimal_v2-q18.test        |  137 +
 .../tpcds/queries/tpcds-decimal_v2-q22.test        |  125 +
 .../tpcds/queries/tpcds-decimal_v2-q27.test        |  126 +
 .../tpcds/queries/tpcds-decimal_v2-q36.test        |  134 +
 .../tpcds/queries/tpcds-decimal_v2-q5.test         |  231 ++
 .../tpcds/queries/tpcds-decimal_v2-q67.test        |  147 +
 .../tpcds/queries/tpcds-decimal_v2-q70.test        |   46 +
 .../tpcds/queries/tpcds-decimal_v2-q77.test        |  155 +
 .../tpcds/queries/tpcds-decimal_v2-q80.test        |  199 ++
 .../tpcds/queries/tpcds-decimal_v2-q86.test        |  131 +
 tests/query_test/test_aggregation.py               |    6 +
 tests/query_test/test_tpcds_queries.py             |   30 +
 tests/util/parse_util.py                           |    2 +-
 32 files changed, 6964 insertions(+), 138 deletions(-)
 create mode 100644 testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test
 create mode 100644 testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q18.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q22.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q27.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q36.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q5.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q67.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q70.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q77.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q80.test
 create mode 100644 testdata/workloads/tpcds/queries/tpcds-decimal_v2-q86.test


[impala] 01/02: IMPALA-9898: generate grouping set plans

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tarmstrong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 3e1e7da229913ac1651ea908e8817d6e41adf8b0
Author: Tim Armstrong <ta...@cloudera.com>
AuthorDate: Tue Jun 30 09:51:05 2020 -0700

    IMPALA-9898: generate grouping set plans
    
    Integrates the parsing and analysis with plan generation.
    
    Testing:
    * Add analysis test to make sure we reject unsupported queries.
    * Added targeted planner tests to ensure we generate the correct
      aggregation classes for a variety of cases.
    * Add targeted end-to-end functional tests.
    
    Added five TPC-DS queries that use ROLLUP, building on some work done
    by Fang-Yu Rao. Some tweaks were required for these tests.
    * Add an extra ORDER BY clause to q77 to make fully deterministic.
    * Add backticks around `returns` to avoid reserved word.
    * Add INTERVAL keyword to date/timestamp arithmetic.
    
    We can run q80, too, but I haven't added or verified results yet -
    that can be done in a follow-up.
    
    Change-Id: Ie454c5bf7aee266321dee615548d7f2b71380197
    Reviewed-on: http://gerrit.cloudera.org:8080/16128
    Reviewed-by: Tim Armstrong <ta...@cloudera.com>
    Tested-by: Tim Armstrong <ta...@cloudera.com>
---
 .../main/java/org/apache/impala/analysis/Expr.java |   21 +
 .../org/apache/impala/analysis/GroupByClause.java  |   35 +-
 .../apache/impala/analysis/MultiAggregateInfo.java |   58 +-
 .../org/apache/impala/analysis/SelectStmt.java     |   12 +-
 .../java/org/apache/impala/common/RuntimeEnv.java  |   11 -
 .../apache/impala/planner/SingleNodePlanner.java   |   32 +-
 .../apache/impala/analysis/AnalyzeStmtsTest.java   |   35 +-
 .../org/apache/impala/analysis/ParserTest.java     |   25 +
 .../java/org/apache/impala/analysis/ToSqlTest.java |    4 -
 .../org/apache/impala/planner/PlannerTest.java     |    5 +
 .../queries/PlannerTest/grouping-sets.test         | 1197 +++++++++++++
 .../queries/PlannerTest/subquery-rewrite.test      |  175 ++
 .../queries/PlannerTest/tpcds-all.test             | 1769 ++++++++++++++++++++
 .../queries/QueryTest/grouping-sets.test           |  155 ++
 .../tpcds/queries/tpcds-decimal_v2-q18.test        |  137 ++
 .../tpcds/queries/tpcds-decimal_v2-q22.test        |  125 ++
 .../tpcds/queries/tpcds-decimal_v2-q5.test         |  231 +++
 .../tpcds/queries/tpcds-decimal_v2-q67.test        |  147 ++
 .../tpcds/queries/tpcds-decimal_v2-q77.test        |  155 ++
 tests/query_test/test_aggregation.py               |    6 +
 tests/query_test/test_tpcds_queries.py             |   15 +
 tests/util/parse_util.py                           |    2 +-
 22 files changed, 4287 insertions(+), 65 deletions(-)

diff --git a/fe/src/main/java/org/apache/impala/analysis/Expr.java b/fe/src/main/java/org/apache/impala/analysis/Expr.java
index a73eeb4..b110515 100644
--- a/fe/src/main/java/org/apache/impala/analysis/Expr.java
+++ b/fe/src/main/java/org/apache/impala/analysis/Expr.java
@@ -1121,6 +1121,27 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
   }
 
   /**
+   * Create a deep copy of 'ls'. The elements of the returned list are of the same
+   * type as the input list.
+   */
+  public static <C extends Expr> List<List<C>> deepCopy(List<List<C>> ls) {
+    Preconditions.checkNotNull(ls);
+    List<List<C>> result = new ArrayList<>(ls.size());
+    for (List<C> l : ls) {
+      if (l == null) {
+        result.add(null);
+        continue;
+      }
+      List<C> l2 = new ArrayList<>(l.size());
+      for (Expr element : l) {
+        l2.add((C) element.clone());
+      }
+      result.add(l2);
+    }
+    return result;
+  }
+
+  /**
    * Removes duplicate exprs (according to equals()).
    */
   public static <C extends Expr> void removeDuplicates(List<C> l) {
diff --git a/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java b/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java
index d0276f3..2a663d8 100644
--- a/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java
+++ b/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java
@@ -51,6 +51,10 @@ public class GroupByClause {
   // clause. Non-null iff groupingSetsType is SETS.
   private final List<List<Integer>> groupingSetsList_;
 
+  /////////////////////////////////////////
+  // BEGIN: Members that need to be reset()
+  boolean isAnalyzed_ = false;
+
   // ID of each of the distinct grouping sets. Each ID is a bitfield with a bit set if
   // that distinct grouping expr is part of that grouping set. Populated during analysis.
   private final List<Long> groupingIDs_;
@@ -59,6 +63,9 @@ public class GroupByClause {
   // analysis.
   private final List<List<Expr>> analyzedGroupingSets_;
 
+  // END: Members that need to be reset()
+  /////////////////////////////////////////
+
   /**
    * Constructor for regular GROUP BY, ROLLUP and CUBE.
    */
@@ -110,15 +117,25 @@ public class GroupByClause {
   }
 
   public List<Expr> getOrigGroupingExprs() { return origGroupingExprs_; }
-  public List<Long> getGroupingIDs() { return groupingIDs_; }
+  public List<Long> getGroupingIDs() {
+    Preconditions.checkState(isAnalyzed_);
+    return groupingIDs_;
+  }
+  public List<List<Expr>> getAnalyzedGroupingSets() {
+    Preconditions.checkState(isAnalyzed_);
+    return analyzedGroupingSets_;
+  }
 
   /**
    * Add a new grouping ID if it is not already present in 'groupingIDs_'. If it is a new
    * grouping ID, expand the list of expressions it references and append them to
    * 'analyzedGroupingSets_'.
    * @param groupingExprs duplicated list of analyzed grouping exprs
+   * @param addtlGroupingExprs additional grouping exprs that will be added to each
+   *    grouping set, e.g. correlated columns from a subquery rewrite.
    */
-  private void addGroupingID(long id, List<Expr> groupingExprs) throws AnalysisException {
+  private void addGroupingID(long id, List<Expr> groupingExprs,
+      List<Expr> addtlGroupingExprs) throws AnalysisException {
     Preconditions.checkState(id >= 0 && id < (1L << groupingExprs.size()),
         "bad id: " + id);
     if (groupingIDs_.contains(id)) return;
@@ -140,6 +157,8 @@ public class GroupByClause {
         groupingSet.add(NullLiteral.create(groupingExpr.getType()));
       }
     }
+    // The additional grouping expressions are a part of each grouping set.
+    groupingSet.addAll(addtlGroupingExprs);
     analyzedGroupingSets_.add(groupingSet);
   }
 
@@ -195,6 +214,10 @@ public class GroupByClause {
       }
     }
 
+    List<Expr> addtlGroupingExprs =
+        groupingExprs.subList(numOrigGroupingExprs, groupingExprs.size());
+    Expr.removeDuplicates(addtlGroupingExprs);
+
     int numGroupingSetExprs = dedupedGroupingSetExprs.size();
     if (numGroupingSetExprs >= (Long.SIZE - 1)) {
       throw new AnalysisException(
@@ -209,7 +232,7 @@ public class GroupByClause {
       // E.g. for CUBE(a, b, c), we enumerate 111, 110, 101, 100, 010, 001, 000,
       // meaning the sets (a, b, c), (b, c), (a, c), (c), (b), (a), ().
       for (long id = (1L << numGroupingSetExprs) - 1; id >= 0; id--) {
-        addGroupingID(id, dedupedGroupingSetExprs);
+        addGroupingID(id, dedupedGroupingSetExprs, addtlGroupingExprs);
       }
     } else if (groupingSetsType_ == GroupingSetsType.ROLLUP) {
       Preconditions.checkState(numGroupingSetExprs > 0);
@@ -220,7 +243,7 @@ public class GroupByClause {
       long bit = (1L << numGroupingSetExprs);
       long id = bit - 1;
       while (bit != 0) {
-        addGroupingID(id, dedupedGroupingSetExprs);
+        addGroupingID(id, dedupedGroupingSetExprs, addtlGroupingExprs);
         bit >>= 1;
         id &= ~bit;
       }
@@ -240,9 +263,10 @@ public class GroupByClause {
               "bad pos" + dedupedPos);
           mask |= (1L << dedupedPos);
         }
-        addGroupingID(mask, dedupedGroupingSetExprs);
+        addGroupingID(mask, dedupedGroupingSetExprs, addtlGroupingExprs);
       }
     }
+    isAnalyzed_ = true;
   }
 
   /**
@@ -253,6 +277,7 @@ public class GroupByClause {
     // groupingIDs_ were generated during analysis, so clear them out.
     groupingIDs_.clear();
     analyzedGroupingSets_.clear();
+    isAnalyzed_ = false;
   }
 
   /**
diff --git a/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java b/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java
index 0483df2..0d998ef 100644
--- a/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java
+++ b/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java
@@ -157,12 +157,22 @@ public class MultiAggregateInfo {
 
   // Indicates if this MultiAggregateInfo is associated with grouping sets.
   private boolean isGroupingSet_;
+
+  // Grouping sets provided in the constructor. Null if 'isGroupingSet_' is false or if
+  // the list of grouping sets will be provided later in analyzeCustomClasses(). Each
+  // list must have the same number of expressions, and the expression types must match.
+  // TODO: could generalise this to allow omitting NULL expressions.
+  private List<List<Expr>> groupingSets_;
+
   // Indicates whether to generate the grouping_id column for grouping sets
   private boolean generateGroupingId_;
 
-  public MultiAggregateInfo(List<Expr> groupingExprs, List<FunctionCallExpr> aggExprs) {
+  public MultiAggregateInfo(List<Expr> groupingExprs, List<FunctionCallExpr> aggExprs,
+      List<List<Expr>> groupingSets) {
     groupingExprs_ = Expr.cloneList(Preconditions.checkNotNull(groupingExprs));
     aggExprs_ = Expr.cloneList(Preconditions.checkNotNull(aggExprs));
+    groupingSets_ = groupingSets == null ? null : Expr.deepCopy(groupingSets);
+    isGroupingSet_ = groupingSets != null;
   }
 
   /**
@@ -211,6 +221,12 @@ public class MultiAggregateInfo {
 
   public void analyze(Analyzer analyzer) throws AnalysisException {
     if (isAnalyzed_) return;
+
+    if (groupingSets_ != null) {
+      analyzeGroupingSets(analyzer);
+      return;
+    }
+
     isAnalyzed_ = true;
 
     // Group the agg exprs by their DISTINCT exprs and move the non-distinct agg exprs
@@ -270,6 +286,7 @@ public class MultiAggregateInfo {
     for (List<FunctionCallExpr> aggClass : aggClasses_) {
       aggInfos_.add(AggregateInfo.create(groupingExprs_, aggClass, analyzer));
     }
+
     if (aggInfos_.size() == 1) {
       // Only a single aggregation class, no transposition step is needed.
       outputSmap_ = aggInfos_.get(0).getResultSmap();
@@ -306,10 +323,36 @@ public class MultiAggregateInfo {
   }
 
   /**
+   * Implementation of analyze() for aggregation with grouping sets.
+   * Does not handle distinct aggregate functions yet.
+   *
+   * @throws AnalysisException if a distinct aggregation function is present or any other
+   *    analysis error occurs.
+   */
+  private void analyzeGroupingSets(Analyzer analyzer) throws AnalysisException {
+    for (FunctionCallExpr aggExpr : aggExprs_) {
+      if (aggExpr.isDistinct()) {
+        // We can't handle this now - it would require enumerating more aggregation
+        // classes.
+        throw new AnalysisException("Distinct aggregate functions and grouping sets " +
+            "are not supported in the same query block.");
+      }
+    }
+
+    List<List<FunctionCallExpr>> aggClasses = new ArrayList<>(groupingSets_.size());
+    List<AggregateInfo> aggInfos = new ArrayList<>(groupingSets_.size());
+    for (List<Expr> groupingSet : groupingSets_) {
+      aggClasses.add(aggExprs_);
+      aggInfos.add(AggregateInfo.create(groupingSet, aggExprs_, analyzer));
+    }
+    // analyzeCustomClasses() will do the rest of the analysis and set 'isAnalyzed_'
+    analyzeCustomClasses(analyzer, aggClasses, aggInfos);
+  }
+
+  /**
    * Version of analyze method that accepts list of aggregation class and aggregation
-   * info that may be created by an external planner. This is needed for supporting
-   * grouping sets/rollup functionality. This is unlike the default analyze method which
-   * internally generates these lists.
+   * info. This is needed for supporting grouping sets/rollup functionality. Does not
+   * handle distinct aggregate functions.
    */
   public void analyzeCustomClasses(Analyzer analyzer,
       List<List<FunctionCallExpr>> aggClasses,
@@ -432,8 +475,8 @@ public class MultiAggregateInfo {
    * Example:
    *  SELECT a1, b1, SUM(c1), MIN(d1) FROM t1 GROUP BY ROLLUP(a1, b1)
    *
-   * Currently, Impala does not support ROLLUP directly but suppose an external planner
-   * converts the above to the following 3 Grouping Sets: {(a1, b1), (a1), ()}
+   * For example, the above statement results in the following 3 Grouping Sets:
+   * {(a1, b1), (a1), ()}
    * We will map these to the following aggregation classes:
    * Class 1:
    *  Aggregate output exprs: SUM(c1), MIN(d1)
@@ -534,7 +577,8 @@ public class MultiAggregateInfo {
         // for a particular aggInfo, we only need to consider the group-by
         // exprs relevant to that aggInfo
         TupleDescriptor aggTuple = aggInfo.getResultTupleDesc();
-        Preconditions.checkState(gbIndex < aggTuple.getSlots().size());
+        Preconditions.checkState(gbIndex < aggTuple.getSlots().size(),
+            groupingExprs.toString() + " " + aggTuple.debugString());
         Expr whenExpr = NumericLiteral.create(aggTuple.getId().asInt());
         if (aggInfo.getGroupingExprs().size() == 0) {
           Type nullType = groupingExprs.get(gbIndex).getType();
diff --git a/fe/src/main/java/org/apache/impala/analysis/SelectStmt.java b/fe/src/main/java/org/apache/impala/analysis/SelectStmt.java
index 12df0d8..2fca33d 100644
--- a/fe/src/main/java/org/apache/impala/analysis/SelectStmt.java
+++ b/fe/src/main/java/org/apache/impala/analysis/SelectStmt.java
@@ -758,10 +758,6 @@ public class SelectStmt extends QueryStmt {
       groupingExprs_ = groupingExprsCopy_;
 
       if (groupByClause_ != null && groupByClause_.hasGroupingSets()) {
-        if (RuntimeEnv.INSTANCE.isGroupingSetsValidationEnabled()) {
-          throw new AnalysisException(groupByClause_.getTypeString() +
-              " not supported in GROUP BY");
-        }
         groupByClause_.analyzeGroupingSets(groupingExprsCopy_);
       }
     }
@@ -837,10 +833,14 @@ public class SelectStmt extends QueryStmt {
         Preconditions.checkState(aggExprs_.isEmpty());
         groupingExprs = Expr.cloneList(resultExprs_);
       }
+      // All expressions passed into the MultiAggregateInfo must be deduplicated.
+      // The analyzed grouping sets were already deduplicated.
       Expr.removeDuplicates(aggExprs_);
       Expr.removeDuplicates(groupingExprs);
-      // TODO: IMPALA-9898: need to pass in grouping set info for MultiAggregateInfo.
-      multiAggInfo_ = new MultiAggregateInfo(groupingExprs, aggExprs_);
+      List<List<Expr>> groupingSets =
+          (groupByClause_ != null && groupByClause_.hasGroupingSets()) ?
+          groupByClause_.getAnalyzedGroupingSets() : null;
+      multiAggInfo_ = new MultiAggregateInfo(groupingExprs, aggExprs_, groupingSets);
       multiAggInfo_.analyze(analyzer_);
     }
 
diff --git a/fe/src/main/java/org/apache/impala/common/RuntimeEnv.java b/fe/src/main/java/org/apache/impala/common/RuntimeEnv.java
index 6c00c82..4342cf0 100644
--- a/fe/src/main/java/org/apache/impala/common/RuntimeEnv.java
+++ b/fe/src/main/java/org/apache/impala/common/RuntimeEnv.java
@@ -34,9 +34,6 @@ public class RuntimeEnv {
   // service.
   private boolean enableMtDopValidation_;
 
-  // Whether we should allow ROLLUP/CUBE/GROUPING SETS queries to pass analysis
-  private boolean enableGroupingSetsValidation_;
-
   public RuntimeEnv() {
     reset();
   }
@@ -48,7 +45,6 @@ public class RuntimeEnv {
     numCores_ = Runtime.getRuntime().availableProcessors();
     isTestEnv_ = false;
     enableMtDopValidation_ = false;
-    enableGroupingSetsValidation_ = true;
   }
 
   public int getNumCores() { return numCores_; }
@@ -57,11 +53,4 @@ public class RuntimeEnv {
   public boolean isTestEnv() { return isTestEnv_; }
   public boolean isMtDopValidationEnabled() { return enableMtDopValidation_; }
   public void setEnableMtDopValidation(boolean v) { enableMtDopValidation_ = v; }
-  public boolean isGroupingSetsValidationEnabled() {
-    return enableGroupingSetsValidation_;
-  }
-  public void setEnableGroupingSetsValidation(boolean v) {
-    enableGroupingSetsValidation_ = v;
-  }
-
 }
diff --git a/fe/src/main/java/org/apache/impala/planner/SingleNodePlanner.java b/fe/src/main/java/org/apache/impala/planner/SingleNodePlanner.java
index 54ca1d1..bbc7140 100644
--- a/fe/src/main/java/org/apache/impala/planner/SingleNodePlanner.java
+++ b/fe/src/main/java/org/apache/impala/planner/SingleNodePlanner.java
@@ -962,23 +962,27 @@ public class SingleNodePlanner {
   private AggregationNode createAggregationPlan(PlanNode root,
       MultiAggregateInfo multiAggInfo, Analyzer analyzer) throws InternalException {
     Preconditions.checkNotNull(multiAggInfo);
-    AggregationNode firstPhaseAgg =
+    AggregationNode agg =
         new AggregationNode(ctx_.getNextNodeId(), root, multiAggInfo, AggPhase.FIRST);
-    firstPhaseAgg.init(analyzer);
-    if (!multiAggInfo.hasSecondPhase()) return firstPhaseAgg;
-
-    firstPhaseAgg.unsetNeedsFinalize();
-    firstPhaseAgg.setIntermediateTuple();
+    agg.init(analyzer);
+    if (!multiAggInfo.hasSecondPhase() && !multiAggInfo.hasTransposePhase()) {
+      return agg;
+    }
 
-    AggregationNode secondPhaseAgg = new AggregationNode(
-        ctx_.getNextNodeId(), firstPhaseAgg, multiAggInfo, AggPhase.SECOND);
-    secondPhaseAgg.init(analyzer);
-    if (!multiAggInfo.hasTransposePhase()) return secondPhaseAgg;
+    agg.setIntermediateTuple();
 
-    AggregationNode transposePhaseAgg = new AggregationNode(
-        ctx_.getNextNodeId(), secondPhaseAgg, multiAggInfo, AggPhase.TRANSPOSE);
-    transposePhaseAgg.init(analyzer);
-    return transposePhaseAgg;
+    if (multiAggInfo.hasSecondPhase()) {
+      agg.unsetNeedsFinalize();
+      agg = new AggregationNode(
+          ctx_.getNextNodeId(), agg, multiAggInfo, AggPhase.SECOND);
+      agg.init(analyzer);
+    }
+    if (multiAggInfo.hasTransposePhase()) {
+      agg = new AggregationNode(
+          ctx_.getNextNodeId(), agg, multiAggInfo, AggPhase.TRANSPOSE);
+      agg.init(analyzer);
+    }
+    return agg;
   }
 
  /**
diff --git a/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java b/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java
index 67703b9..0a86d20 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java
@@ -36,7 +36,6 @@ import org.apache.impala.catalog.Table;
 import org.apache.impala.catalog.Type;
 import org.apache.impala.common.AnalysisException;
 import org.apache.impala.common.ImpalaException;
-import org.apache.impala.common.RuntimeEnv;
 import org.apache.impala.thrift.TFunctionCategory;
 import org.junit.Assert;
 import org.junit.Test;
@@ -2286,7 +2285,6 @@ public class AnalyzeStmtsTest extends AnalyzerTest {
    */
   @Test
   public void TestGroupingSets() throws AnalysisException {
-    RuntimeEnv.INSTANCE.setEnableGroupingSetsValidation(false);
     // Basic examples of each clause.
     AnalyzesOk("select count(*) from functional.alltypes " +
         "group by rollup(int_col, string_col)");
@@ -2476,22 +2474,25 @@ public class AnalyzeStmtsTest extends AnalyzerTest {
         "  group by rollup(t.string_col, t.bool_col)) " +
         "  group by g.int_col",
         "Unsupported correlated subquery with grouping and/or aggregation");
-  }
 
-  /**
-   * Test that ROLLUP, CUBE and GROUPING SETS result in AnalysException for now.
-   */
-  @Test
-  public void TestGroupingSetsValidation() throws AnalysisException {
-    AnalysisError("select count(*) from functional.alltypes " +
-        "group by rollup(int_col, string_col)",
-        "ROLLUP not supported in GROUP BY");
-    AnalysisError("select count(*) from functional.alltypes " +
-        "group by cube(int_col, string_col)",
-        "CUBE not supported in GROUP BY");
-    AnalysisError("select count(*) from functional.alltypes " +
-        "group by GROUPING SETS((int_col), (string_col))",
-        "SETS not supported in GROUP BY");
+    // Combining grouping sets and distinct aggregations is not supported at this point.
+    // See IMPALA-9914.
+    AnalysisError("select count(distinct id) from functional.alltypes " +
+        "group by rollup(int_col, bool_col)",
+        "Distinct aggregate functions and grouping sets are not supported in the same " +
+        "query block.");
+    AnalysisError("select count(distinct id), count(distinct string_col) " +
+        "from functional.alltypes " +
+        "group by rollup(int_col, bool_col)",
+        "Distinct aggregate functions and grouping sets are not supported in the same " +
+        "query block.");
+
+    // Combining DISTINCT and GROUP BY is not supported in general, not just for grouping
+    // sets.
+    AnalysisError("select distinct int_col, bool_col, count(*) " +
+        "from functional.alltypes " +
+        "group by rollup(int_col, bool_col)",
+        "cannot combine SELECT DISTINCT with aggregate functions or GROUP BY");
   }
 
   @Test
diff --git a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
index 02b5822..0eae3c9 100644
--- a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
@@ -3666,6 +3666,9 @@ public class ParserTest extends FrontendTestBase {
     ParsesOk("SELECT a FROM foo GROUP BY a WITH ROLLUP");
     ParsesOk("SELECT a, b FROM foo GROUP BY a, b WITH ROLLUP");
 
+    // Can't combine syntaxes
+    ParserError("SELECT a, b FROM foo GROUP BY ROLLUP(a, b) WITH ROLLUP");
+
     // Nested grouping clauses not supported.
     ParserError("SELECT a, b FROM foo GROUP BY ROLLUP(a, ROLLUP(b, c))");
     ParserError("SELECT a, b FROM foo GROUP BY ROLLUP(a, CUBE(b, c))");
@@ -3675,6 +3678,12 @@ public class ParserTest extends FrontendTestBase {
     ParserError("SELECT a, b FROM foo GROUP BY ROLLUP(a, b), c");
     ParserError("SELECT a, b FROM foo GROUP BY ROLLUP(a, b), ROLLUP(c)");
     ParserError("SELECT a, b FROM foo GROUP BY ROLLUP(a, b), CUBE(c, d)");
+
+    // Empty clause not supported
+    ParserError("SELECT count(*) FROM foo GROUP BY ROLLUP()");
+
+    // Extra parentheses in list elements are supported.
+    ParsesOk("SELECT a, b FROM foo GROUP BY ROLLUP((a), (b))");
   }
 
   @Test
@@ -3686,6 +3695,9 @@ public class ParserTest extends FrontendTestBase {
     ParsesOk("SELECT a FROM foo GROUP BY a WITH CUBE");
     ParsesOk("SELECT a, b FROM foo GROUP BY a, b WITH CUBE");
 
+    // Can't combine syntaxes
+    ParserError("SELECT a, b FROM foo GROUP BY CUBE(a, b) WITH CUBE");
+
     // Nested grouping clauses not supported.
     ParserError("SELECT a, b FROM foo GROUP BY CUBE(a, ROLLUP(b, c))");
     ParserError("SELECT a, b FROM foo GROUP BY CUBE(a, CUBE(b, c))");
@@ -3694,6 +3706,12 @@ public class ParserTest extends FrontendTestBase {
     ParserError("SELECT a, b FROM foo GROUP BY c, CUBE(a, b)");
     ParserError("SELECT a, b FROM foo GROUP BY CUBE(a, b), c");
     ParserError("SELECT a, b FROM foo GROUP BY CUBE(a, b), CUBE(c)");
+
+    // Empty clause not supported
+    ParserError("SELECT count(*) FROM foo GROUP BY CUBE()");
+
+    // Extra parentheses in list elements are supported.
+    ParsesOk("SELECT a, b FROM foo GROUP BY CUBE((a), (b))");
   }
 
   @Test
@@ -3710,6 +3728,13 @@ public class ParserTest extends FrontendTestBase {
     // Multiple clauses not supported with GROUPING SETS - parser does not handle yet.
     ParserError("SELECT a FROM foo GROUP BY a, b, GROUPING SETS(a, b)");
     ParserError("SELECT a FROM foo GROUP BY CUBE(a, b), GROUPING SETS(a, b)");
+
+    // Empty clause not supported, but empty grouping sets are supported.
+    ParsesOk("SELECT a FROM foo GROUP BY GROUPING SETS(())");
+    ParserError("SELECT a FROM foo GROUP BY GROUPING SETS()");
+
+    // Extra parentheses around expressions in list elements are supported.
+    ParserError("SELECT a FROM foo GROUP BY GROUPING SETS((), ((a), (b))");
   }
 
   @Test
diff --git a/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java b/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java
index 4102c7d..950fd3e 100644
--- a/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/ToSqlTest.java
@@ -23,7 +23,6 @@ import static org.junit.Assert.fail;
 import org.apache.impala.authorization.Privilege;
 import org.apache.impala.common.AnalysisException;
 import org.apache.impala.common.FrontendTestBase;
-import org.apache.impala.common.RuntimeEnv;
 import org.apache.impala.testutil.TestUtils;
 import org.junit.Test;
 
@@ -817,9 +816,6 @@ public class ToSqlTest extends FrontendTestBase {
         "HAVING avg(tinyint_col) > 10 AND count(tinyint_col) > 5");
 
     // CUBE, ROLLUP.
-    // Temporarily disable validation of support for CUBE/ROLLUP/GROUPING SETS
-    // so we can run toSql() on these statements.
-    RuntimeEnv.INSTANCE.setEnableGroupingSetsValidation(false);
     testToSql("select int_col, string_col, sum(id) from functional.alltypes " +
         "group by rollup(int_col, string_col)",
         "SELECT int_col, string_col, sum(id) FROM functional.alltypes " +
diff --git a/fe/src/test/java/org/apache/impala/planner/PlannerTest.java b/fe/src/test/java/org/apache/impala/planner/PlannerTest.java
index d2b89eb..7052721 100644
--- a/fe/src/test/java/org/apache/impala/planner/PlannerTest.java
+++ b/fe/src/test/java/org/apache/impala/planner/PlannerTest.java
@@ -180,6 +180,11 @@ public class PlannerTest extends PlannerTestBase {
   }
 
   @Test
+  public void testGroupingSets() {
+    runPlannerTestFile("grouping-sets");
+  }
+
+  @Test
   public void testAnalyticFns() {
     runPlannerTestFile("analytic-fns",
         ImmutableSet.of(PlannerTestOption.VALIDATE_CARDINALITY));
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test b/testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test
new file mode 100644
index 0000000..a81d0af
--- /dev/null
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test
@@ -0,0 +1,1197 @@
+# Basic ROLLUP
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by rollup(int_col, bool_col, string_col)
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=231
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=231
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# WITH ROLLUP results in same plan
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by int_col, bool_col, string_col with rollup
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=231
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Basic CUBE
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by cube(int_col, bool_col, string_col)
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4,5,6,7,8) IN (1, 2, 3, 4, 5, 6, 7, 8), CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) WHEN 5 THEN count(*) WHEN 6 THEN count(*) WHEN 7 THEN count(*) WHEN 8 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN int_col WHEN 2 THEN NULL WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN int_col WHEN 6 THEN NULL WHEN 7 THEN int_col WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN bool_col WHEN 6 THEN bool_col WHEN 7 THEN NULL WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN string_col WHEN 2 THEN string_col WHEN 3 THEN string_col WHEN [...]
+|  row-size=29B cardinality=363
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL, bool_col, string_col
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, string_col
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, string_col
+|  Class 4
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 5
+|    output: count(*)
+|    group by: NULL, bool_col, NULL
+|  Class 6
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 7
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=204B cardinality=363
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4,5,6,7,8) IN (1, 2, 3, 4, 5, 6, 7, 8), CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) WHEN 5 THEN count(*) WHEN 6 THEN count(*) WHEN 7 THEN count(*) WHEN 8 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN int_col WHEN 2 THEN NULL WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN int_col WHEN 6 THEN NULL WHEN 7 THEN int_col WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN bool_col WHEN 6 THEN bool_col WHEN 7 THEN NULL WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN string_col WHEN 2 THEN string_col WHEN 3 THEN string_col WHEN [...]
+|  row-size=29B cardinality=363
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: NULL, bool_col, string_col
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, string_col
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, string_col
+|  Class 4
+|    output: count:merge(*)
+|    group by: int_col, bool_col, NULL
+|  Class 5
+|    output: count:merge(*)
+|    group by: NULL, bool_col, NULL
+|  Class 6
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 7
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=204B cardinality=363
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) WHEN 5 THEN murmur_hash(int_col) WHEN 6 THEN murmur_hash(NULL) WHEN 7 THEN murmur_hash(int_col) WHEN 8 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) WHEN 5 THEN murmur_hash(bo [...]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL, bool_col, string_col
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, string_col
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, string_col
+|  Class 4
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 5
+|    output: count(*)
+|    group by: NULL, bool_col, NULL
+|  Class 6
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 7
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=204B cardinality=363
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# WITH CUBE results in same plan.
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by int_col, bool_col, string_col with cube
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4,5,6,7,8) IN (1, 2, 3, 4, 5, 6, 7, 8), CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) WHEN 5 THEN count(*) WHEN 6 THEN count(*) WHEN 7 THEN count(*) WHEN 8 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN int_col WHEN 2 THEN NULL WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN int_col WHEN 6 THEN NULL WHEN 7 THEN int_col WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN bool_col WHEN 6 THEN bool_col WHEN 7 THEN NULL WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN string_col WHEN 2 THEN string_col WHEN 3 THEN string_col WHEN [...]
+|  row-size=29B cardinality=363
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL, bool_col, string_col
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, string_col
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, string_col
+|  Class 4
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 5
+|    output: count(*)
+|    group by: NULL, bool_col, NULL
+|  Class 6
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 7
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=204B cardinality=363
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Basic GROUPING SETS
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by grouping sets((int_col, bool_col, string_col), (), (bool_col, string_col))
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3) WHEN 1 THEN int_col WHEN 2 THEN NULL WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN bool_col WHEN 2 THEN NULL WHEN 3 THEN bool_col END, CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN string_col END, CASE valid_tid(1,2,3) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 END
+|  row-size=29B cardinality=221
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  Class 2
+|    output: count(*)
+|    group by: NULL, bool_col, string_col
+|  row-size=77B cardinality=221
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3) WHEN 1 THEN int_col WHEN 2 THEN NULL WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN bool_col WHEN 2 THEN NULL WHEN 3 THEN bool_col END, CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN string_col END, CASE valid_tid(1,2,3) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 END
+|  row-size=29B cardinality=221
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: NULL, bool_col, string_col
+|  row-size=77B cardinality=221
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(bool_col) END,CASE valid_tid(1,2,3) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(string_col) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  Class 2
+|    output: count(*)
+|    group by: NULL, bool_col, string_col
+|  row-size=77B cardinality=221
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Test that ordinals are replaced correctly.
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by rollup(int_col, 2, 3)
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=231
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=231
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Test that duplicate column references are deduplicated. and that
+# the order of elements is respected for rollup.
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by rollup(int_col, 3, 2, 3, string_col)
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN string_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=311
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col, bool_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, string_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=102B cardinality=311
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN string_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=311
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, string_col, bool_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col, string_col, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=102B cardinality=311
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(string_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col, bool_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, string_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=102B cardinality=311
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Test that duplicate column references are deduplicated, even if the same expression
+# appears in the select list multiple times.
+select int_col, bool_col, string_col, bool_col, string_col, count(*)
+from functional.alltypes
+group by cube(int_col, 3, 2, 4, string_col, 5)
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4,5,6,7,8) IN (1, 2, 3, 4, 5, 6, 7, 8), CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) WHEN 5 THEN count(*) WHEN 6 THEN count(*) WHEN 7 THEN count(*) WHEN 8 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN int_col WHEN 2 THEN NULL WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN int_col WHEN 6 THEN NULL WHEN 7 THEN int_col WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN string_col WHEN 2 THEN string_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN string_col WHEN 6 THEN string_col WHEN 7 THEN NULL WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN bool_col WH [...]
+|  row-size=29B cardinality=363
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col, bool_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL, string_col, bool_col
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, bool_col
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, bool_col
+|  Class 4
+|    output: count(*)
+|    group by: int_col, string_col, NULL
+|  Class 5
+|    output: count(*)
+|    group by: NULL, string_col, NULL
+|  Class 6
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 7
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=204B cardinality=363
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4,5,6,7,8) IN (1, 2, 3, 4, 5, 6, 7, 8), CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) WHEN 5 THEN count(*) WHEN 6 THEN count(*) WHEN 7 THEN count(*) WHEN 8 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN int_col WHEN 2 THEN NULL WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN int_col WHEN 6 THEN NULL WHEN 7 THEN int_col WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN string_col WHEN 2 THEN string_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN string_col WHEN 6 THEN string_col WHEN 7 THEN NULL WHEN 8 THEN NULL END, CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN bool_col WH [...]
+|  row-size=29B cardinality=363
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, string_col, bool_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: NULL, string_col, bool_col
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, bool_col
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, bool_col
+|  Class 4
+|    output: count:merge(*)
+|    group by: int_col, string_col, NULL
+|  Class 5
+|    output: count:merge(*)
+|    group by: NULL, string_col, NULL
+|  Class 6
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 7
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=204B cardinality=363
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) WHEN 5 THEN murmur_hash(int_col) WHEN 6 THEN murmur_hash(NULL) WHEN 7 THEN murmur_hash(int_col) WHEN 8 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4,5,6,7,8) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(string_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) WHEN 5 THEN murmur_has [...]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col, bool_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL, string_col, bool_col
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, bool_col
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, bool_col
+|  Class 4
+|    output: count(*)
+|    group by: int_col, string_col, NULL
+|  Class 5
+|    output: count(*)
+|    group by: NULL, string_col, NULL
+|  Class 6
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 7
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=204B cardinality=363
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Test that non-trivial expressions can be used
+select int_col * bigint_col, bool_col, string_col || 'foo', count(*)
+from functional.alltypes
+group by rollup(int_col * bigint_col, bool_col, string_col || 'foo')
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col * bigint_col WHEN 2 THEN int_col * bigint_col WHEN 3 THEN int_col * bigint_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN concat(string_col, 'foo') WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=33B cardinality=231
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col * bigint_col, bool_col, concat(string_col, 'foo')
+|  Class 1
+|    output: count(*)
+|    group by: int_col * bigint_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col * bigint_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=116B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=26B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col * bigint_col WHEN 2 THEN int_col * bigint_col WHEN 3 THEN int_col * bigint_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN concat(string_col, 'foo') WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=33B cardinality=231
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col * bigint_col, bool_col, concat(string_col, 'foo')
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col * bigint_col, bool_col, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col * bigint_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=116B cardinality=231
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col * bigint_col) WHEN 2 THEN murmur_hash(int_col * bigint_col) WHEN 3 THEN murmur_hash(int_col * bigint_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(concat(string_col, 'foo')) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmu [...]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col * bigint_col, bool_col, concat(string_col, 'foo')
+|  Class 1
+|    output: count(*)
+|    group by: int_col * bigint_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col * bigint_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=116B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=26B cardinality=7.30K
+====
+# Test that mixed aggregate functions can be used.
+select int_col * bigint_col, bool_col, string_col || 'foo',
+       count(*), sum(tinyint_col), min(date_string_col)
+from functional.alltypes
+group by rollup(int_col * bigint_col, bool_col, string_col || 'foo')
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END), aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN sum(tinyint_col) WHEN 2 THEN sum(tinyint_col) WHEN 3 THEN sum(tinyint_col) WHEN 4 THEN sum(tinyint_col) END), aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN min(date_string_col) WHEN 2 THEN min(date_string_col) WHEN  [...]
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col * bigint_col WHEN 2 THEN int_col * bigint_col WHEN 3 THEN int_col * bigint_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN concat(string_col, 'foo') WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=53B cardinality=231
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: int_col * bigint_col, bool_col, concat(string_col, 'foo')
+|  Class 1
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: int_col * bigint_col, bool_col, NULL
+|  Class 2
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: int_col * bigint_col, NULL, NULL
+|  Class 3
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: NULL, NULL, NULL
+|  row-size=196B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=47B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END), aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN sum(tinyint_col) WHEN 2 THEN sum(tinyint_col) WHEN 3 THEN sum(tinyint_col) WHEN 4 THEN sum(tinyint_col) END), aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN min(date_string_col) WHEN 2 THEN min(date_string_col) WHEN  [...]
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col * bigint_col WHEN 2 THEN int_col * bigint_col WHEN 3 THEN int_col * bigint_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN concat(string_col, 'foo') WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=53B cardinality=231
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*), sum:merge(tinyint_col), min:merge(date_string_col)
+|    group by: int_col * bigint_col, bool_col, concat(string_col, 'foo')
+|  Class 1
+|    output: count:merge(*), sum:merge(tinyint_col), min:merge(date_string_col)
+|    group by: int_col * bigint_col, bool_col, NULL
+|  Class 2
+|    output: count:merge(*), sum:merge(tinyint_col), min:merge(date_string_col)
+|    group by: int_col * bigint_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*), sum:merge(tinyint_col), min:merge(date_string_col)
+|    group by: NULL, NULL, NULL
+|  row-size=196B cardinality=231
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col * bigint_col) WHEN 2 THEN murmur_hash(int_col * bigint_col) WHEN 3 THEN murmur_hash(int_col * bigint_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(concat(string_col, 'foo')) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmu [...]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: int_col * bigint_col, bool_col, concat(string_col, 'foo')
+|  Class 1
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: int_col * bigint_col, bool_col, NULL
+|  Class 2
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: int_col * bigint_col, NULL, NULL
+|  Class 3
+|    output: count(*), sum(tinyint_col), min(date_string_col)
+|    group by: NULL, NULL, NULL
+|  row-size=196B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=47B cardinality=7.30K
+====
+# Test that limit is placed in final aggregate node.
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by rollup(int_col, 2, 3)
+limit 5
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  limit: 5
+|  row-size=29B cardinality=5
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|  limit: 5
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  limit: 5
+|  row-size=29B cardinality=5
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Test that HAVING predicates are placed at final aggregate node.
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by rollup(int_col, 2, 3)
+having count(*) > 1 and min(int_col) > 0
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END), aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN min(int_col) WHEN 2 THEN min(int_col) WHEN 3 THEN min(int_col) WHEN 4 THEN min(int_col) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  having: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END) > 1, aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN min(int_col) WHEN 2 THEN min(int_col) WHEN 3 THEN min(int_col) WHEN 4 THEN min(int_col) END) > 0
+|  row-size=33B cardinality=23
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*), min(int_col)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*), min(int_col)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*), min(int_col)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*), min(int_col)
+|    group by: NULL, NULL, NULL
+|  row-size=117B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END), aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN min(int_col) WHEN 2 THEN min(int_col) WHEN 3 THEN min(int_col) WHEN 4 THEN min(int_col) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  having: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END) > 1, aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN min(int_col) WHEN 2 THEN min(int_col) WHEN 3 THEN min(int_col) WHEN 4 THEN min(int_col) END) > 0
+|  row-size=33B cardinality=23
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*), min:merge(int_col)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count:merge(*), min:merge(int_col)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count:merge(*), min:merge(int_col)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*), min:merge(int_col)
+|    group by: NULL, NULL, NULL
+|  row-size=117B cardinality=231
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*), min(int_col)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*), min(int_col)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*), min(int_col)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*), min(int_col)
+|    group by: NULL, NULL, NULL
+|  row-size=117B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Test that ORDER BY is handled correctly.
+select int_col, bool_col, string_col, count(*) from functional.alltypes
+group by rollup(int_col, bool_col, string_col)
+order by string_col, int_col desc, bool_col
+---- PLAN
+PLAN-ROOT SINK
+|
+03:SORT
+|  order by: CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END ASC, CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END DESC, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END ASC
+|  row-size=25B cardinality=231
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=231
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+06:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END ASC, CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END DESC, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END ASC
+|
+03:SORT
+|  order by: CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END ASC, CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END DESC, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END ASC
+|  row-size=25B cardinality=231
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN bool_col WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=29B cardinality=231
+|
+05:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+04:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(bool_col) WHEN 2 THEN murmur_hash(bool_col) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, bool_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, bool_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=101B cardinality=231
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=18B cardinality=7.30K
+====
+# Grouping sets with constants in group by. We still produce the same grouping sets
+# as if they were variable expressions.
+select int_col, 1234 i, 'test', count(*) from functional.alltypes
+group by rollup(int_col, i, 'test')
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1234 WHEN 2 THEN 1234 WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 'test' WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=30B cardinality=31
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, 1234, 'test'
+|  Class 1
+|    output: count(*)
+|    group by: int_col, 1234, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=104B cardinality=31
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=4B cardinality=7.30K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3,4) IN (1, 2, 3, 4), CASE valid_tid(1,2,3,4) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) WHEN 4 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3,4) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1234 WHEN 2 THEN 1234 WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 'test' WHEN 2 THEN NULL WHEN 3 THEN NULL WHEN 4 THEN NULL END, CASE valid_tid(1,2,3,4) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 END
+|  row-size=30B cardinality=31
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, 1234, 'test'
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col, 1234, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count:merge(*)
+|    group by: NULL, NULL, NULL
+|  row-size=104B cardinality=31
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(int_col) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash(1234) WHEN 2 THEN murmur_hash(1234) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3,4) WHEN 1 THEN murmur_hash('test') WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) WHEN 4 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, 1234, 'test'
+|  Class 1
+|    output: count(*)
+|    group by: int_col, 1234, NULL
+|  Class 2
+|    output: count(*)
+|    group by: int_col, NULL, NULL
+|  Class 3
+|    output: count(*)
+|    group by: NULL, NULL, NULL
+|  row-size=104B cardinality=31
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=4B cardinality=7.30K
+====
+# Empty grouping sets results in non-grouping aggregation.
+select count(*) from functional.alltypes
+group by grouping sets(())
+---- PLAN
+PLAN-ROOT SINK
+|
+01:AGGREGATE [FINALIZE]
+|  output: count(*)
+|  row-size=8B cardinality=1
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=0B cardinality=7.30K
+====
+# Rollup of single column results in multiple aggregation classes.
+select int_col, count(*) from functional.alltypes
+group by rollup(int_col)
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2) IN (1, 2), CASE valid_tid(1,2) WHEN 1 THEN count(*) WHEN 2 THEN count(*) END)
+|  group by: CASE valid_tid(1,2) WHEN 1 THEN int_col WHEN 2 THEN NULL END, CASE valid_tid(1,2) WHEN 1 THEN 1 WHEN 2 THEN 2 END
+|  row-size=16B cardinality=11
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL
+|  row-size=24B cardinality=11
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=4B cardinality=7.30K
+====
+# Cube of single column results in multiple aggregation classes.
+select int_col, count(*) from functional.alltypes
+group by rollup(int_col)
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2) IN (1, 2), CASE valid_tid(1,2) WHEN 1 THEN count(*) WHEN 2 THEN count(*) END)
+|  group by: CASE valid_tid(1,2) WHEN 1 THEN int_col WHEN 2 THEN NULL END, CASE valid_tid(1,2) WHEN 1 THEN 1 WHEN 2 THEN 2 END
+|  row-size=16B cardinality=11
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col
+|  Class 1
+|    output: count(*)
+|    group by: NULL
+|  row-size=24B cardinality=11
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=4B cardinality=7.30K
+====
+# Grouping sets referencing single column results in simple grouping aggregation.
+select int_col, count(*) from functional.alltypes
+group by grouping sets((int_col), (1))
+---- PLAN
+PLAN-ROOT SINK
+|
+01:AGGREGATE [FINALIZE]
+|  output: count(*)
+|  group by: int_col
+|  row-size=12B cardinality=10
+|
+00:SCAN HDFS [functional.alltypes]
+   HDFS partitions=24/24 files=24 size=478.45KB
+   row-size=4B cardinality=7.30K
+====
+# Having predicates must be evaluated after aggregation because the aggregation can
+# introduce NULLs.
+select int_col, string_col, count(*)
+from functional.alltypesagg
+group by rollup(int_col, string_col)
+having int_col is NULL and string_col > 'abc'
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 END
+|  having: CASE valid_tid(1,2,3) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN NULL END IS NULL, CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL END > 'abc'
+|  row-size=28B cardinality=7
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: NULL, NULL
+|  row-size=75B cardinality=11.96K
+|
+00:SCAN HDFS [functional.alltypesagg]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   row-size=19B cardinality=11.00K
+====
+# Missing optimization: the HAVING predicate could eliminate 2/3 of the agg classes.
+select int_col, string_col, count(*)
+from functional.alltypesagg
+group by rollup(int_col, string_col)
+having string_col is not NULL
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 END
+|  having: CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL END IS NOT NULL
+|  row-size=28B cardinality=11.96K
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: NULL, NULL
+|  row-size=75B cardinality=11.96K
+|
+00:SCAN HDFS [functional.alltypesagg]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   row-size=19B cardinality=11.00K
+====
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/subquery-rewrite.test b/testdata/workloads/functional-planner/queries/PlannerTest/subquery-rewrite.test
index b562e9e..f7326ea 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/subquery-rewrite.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/subquery-rewrite.test
@@ -4541,3 +4541,178 @@ PLAN-ROOT SINK
    HDFS partitions=4/4 files=4 size=460B
    row-size=4B cardinality=8
 ====
+# Uncorrelated EXISTS with a group by clause with rollup.
+select 1
+from functional.alltypesagg a
+where exists
+  (select id
+   from functional.alltypestiny b
+   group by rollup(id, int_col, bool_col))
+and tinyint_col < 10
+---- PLAN
+PLAN-ROOT SINK
+|
+04:NESTED LOOP JOIN [LEFT SEMI JOIN]
+|  row-size=1B cardinality=1.10K
+|
+|--03:AGGREGATE [FINALIZE]
+|  |  group by: CASE valid_tid(2,3,4,5) WHEN 2 THEN id WHEN 3 THEN id WHEN 4 THEN id WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 4 WHEN 5 THEN 5 END
+|  |  limit: 1
+|  |  row-size=13B cardinality=1
+|  |
+|  02:AGGREGATE [FINALIZE]
+|  |  Class 0
+|  |    group by: id, int_col, bool_col
+|  |  Class 1
+|  |    group by: id, int_col, NULL
+|  |  Class 2
+|  |    group by: id, NULL, NULL
+|  |  Class 3
+|  |    group by: NULL, NULL, NULL
+|  |  row-size=36B cardinality=25
+|  |
+|  01:SCAN HDFS [functional.alltypestiny b]
+|     HDFS partitions=4/4 files=4 size=460B
+|     row-size=9B cardinality=8
+|
+00:SCAN HDFS [functional.alltypesagg a]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   predicates: tinyint_col < 10
+   row-size=1B cardinality=1.10K
+====
+# Correlated EXISTS with a group by clause with rollup.
+# The correlated columns are pushed into the GROUP BY.
+# TODO: This plan could be improved by removing the ROLLUP in the subquery or by
+# reordering the JOIN and AGGREGATE.
+select 1
+from functional.alltypesagg a
+where exists
+  (select id
+   from functional.alltypestiny b
+   where a.tinyint_col = b.tinyint_col and a.string_col = b.string_col
+   group by rollup(id, int_col, bool_col))
+and tinyint_col < 10
+---- PLAN
+PLAN-ROOT SINK
+|
+04:HASH JOIN [LEFT SEMI JOIN]
+|  hash predicates: a.tinyint_col = CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END, a.string_col = CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col WHEN 3 THEN b.string_col WHEN 4 THEN b.string_col WHEN 5 THEN b.string_col END
+|  runtime filters: RF000 <- CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END, RF001 <- CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col WHEN 3 THEN b.string_col WHEN 4 THEN b.string_col WHEN 5 THEN b.string_col END
+|  row-size=16B cardinality=3
+|
+|--03:AGGREGATE [FINALIZE]
+|  |  group by: CASE valid_tid(2,3,4,5) WHEN 2 THEN id WHEN 3 THEN id WHEN 4 THEN id WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END, CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col [...]
+|  |  row-size=26B cardinality=28
+|  |
+|  02:AGGREGATE [FINALIZE]
+|  |  Class 0
+|  |    group by: id, int_col, bool_col, b.tinyint_col, b.string_col
+|  |  Class 1
+|  |    group by: id, int_col, NULL, b.tinyint_col, b.string_col
+|  |  Class 2
+|  |    group by: id, NULL, NULL, b.tinyint_col, b.string_col
+|  |  Class 3
+|  |    group by: NULL, NULL, NULL, b.tinyint_col, b.string_col
+|  |  row-size=92B cardinality=28
+|  |
+|  01:SCAN HDFS [functional.alltypestiny b]
+|     HDFS partitions=4/4 files=4 size=460B
+|     row-size=23B cardinality=8
+|
+00:SCAN HDFS [functional.alltypesagg a]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   predicates: tinyint_col < 10
+   runtime filters: RF000 -> a.tinyint_col, RF001 -> a.string_col
+   row-size=16B cardinality=1.10K
+====
+# Correlated EXISTS with a group by clause with rollup and having.
+# The correlated columns are pushed into the GROUP BY.
+# This plan could be improved by reordering the JOIN and AGGREGATE
+# or by transforming the ROLLUP (the HAVING predicate interacts with
+# the ROLLUP since it is always true for some aggregation classes).
+select 1
+from functional.alltypesagg a
+where exists
+  (select id
+   from functional.alltypestiny b
+   where a.tinyint_col = b.tinyint_col and a.string_col = b.string_col
+   group by rollup(id, int_col, bool_col)
+   having int_col is null)
+and tinyint_col < 10
+---- PLAN
+PLAN-ROOT SINK
+|
+04:HASH JOIN [LEFT SEMI JOIN]
+|  hash predicates: a.tinyint_col = CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END, a.string_col = CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col WHEN 3 THEN b.string_col WHEN 4 THEN b.string_col WHEN 5 THEN b.string_col END
+|  runtime filters: RF000 <- CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END, RF001 <- CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col WHEN 3 THEN b.string_col WHEN 4 THEN b.string_col WHEN 5 THEN b.string_col END
+|  row-size=16B cardinality=1
+|
+|--03:AGGREGATE [FINALIZE]
+|  |  group by: CASE valid_tid(2,3,4,5) WHEN 2 THEN id WHEN 3 THEN id WHEN 4 THEN id WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END, CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col [...]
+|  |  having: CASE valid_tid(2,3,4,5) WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN NULL END IS NULL
+|  |  row-size=26B cardinality=1
+|  |
+|  02:AGGREGATE [FINALIZE]
+|  |  Class 0
+|  |    group by: id, int_col, bool_col, b.tinyint_col, b.string_col
+|  |  Class 1
+|  |    group by: id, int_col, NULL, b.tinyint_col, b.string_col
+|  |  Class 2
+|  |    group by: id, NULL, NULL, b.tinyint_col, b.string_col
+|  |  Class 3
+|  |    group by: NULL, NULL, NULL, b.tinyint_col, b.string_col
+|  |  row-size=92B cardinality=28
+|  |
+|  01:SCAN HDFS [functional.alltypestiny b]
+|     HDFS partitions=4/4 files=4 size=460B
+|     row-size=23B cardinality=8
+|
+00:SCAN HDFS [functional.alltypesagg a]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   predicates: tinyint_col < 10
+   runtime filters: RF000 -> a.tinyint_col, RF001 -> a.string_col
+   row-size=16B cardinality=1.10K
+====
+# Correlated EXISTS with a group by clause with rollup.
+# Same query as above but with tables flipped so that the join is transformed into a
+# RIGHT OUTER JOIN and the aggregation ends up on the left side of of the join. Runtime
+# filters cannot be pushed to the scan because the rollup aggregation introduces NULLs.
+select 1
+from functional.alltypestiny a
+where exists
+  (select id
+   from functional.alltypesagg b
+   where a.tinyint_col = b.tinyint_col and a.string_col = b.string_col
+   group by rollup(id, int_col, bool_col))
+and tinyint_col < 10
+---- PLAN
+PLAN-ROOT SINK
+|
+04:HASH JOIN [RIGHT SEMI JOIN]
+|  hash predicates: CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END = a.tinyint_col, CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col WHEN 3 THEN b.string_col WHEN 4 THEN b.string_col WHEN 5 THEN b.string_col END = a.string_col
+|  row-size=14B cardinality=1
+|
+|--00:SCAN HDFS [functional.alltypestiny a]
+|     HDFS partitions=4/4 files=4 size=460B
+|     predicates: tinyint_col < 10
+|     row-size=14B cardinality=1
+|
+03:AGGREGATE [FINALIZE]
+|  group by: CASE valid_tid(2,3,4,5) WHEN 2 THEN id WHEN 3 THEN id WHEN 4 THEN id WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN int_col WHEN 3 THEN int_col WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN bool_col WHEN 3 THEN NULL WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(2,3,4,5) WHEN 2 THEN b.tinyint_col WHEN 3 THEN b.tinyint_col WHEN 4 THEN b.tinyint_col WHEN 5 THEN b.tinyint_col END, CASE valid_tid(2,3,4,5) WHEN 2 THEN b.string_col WH [...]
+|  row-size=26B cardinality=41.67K
+|
+02:AGGREGATE [FINALIZE]
+|  Class 0
+|    group by: id, int_col, bool_col, b.tinyint_col, b.string_col
+|  Class 1
+|    group by: id, int_col, NULL, b.tinyint_col, b.string_col
+|  Class 2
+|    group by: id, NULL, NULL, b.tinyint_col, b.string_col
+|  Class 3
+|    group by: NULL, NULL, NULL, b.tinyint_col, b.string_col
+|  row-size=100B cardinality=41.67K
+|
+01:SCAN HDFS [functional.alltypesagg b]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   row-size=25B cardinality=11.00K
+====
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test b/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test
index c95a47d..f60fa9b 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test
@@ -10443,3 +10443,1772 @@ PLAN-ROOT SINK
    runtime filters: RF000 -> ws_item_sk, RF004 -> ws_bill_customer_sk, RF006 -> ws_sold_date_sk
    row-size=20B cardinality=719.38K
 ====
+# TPCDS-Q18
+select  i_item_id,
+        ca_country,
+        ca_state,
+        ca_county,
+        avg( cast(cs_quantity as decimal(12,2))) agg1,
+        avg( cast(cs_list_price as decimal(12,2))) agg2,
+        avg( cast(cs_coupon_amt as decimal(12,2))) agg3,
+        avg( cast(cs_sales_price as decimal(12,2))) agg4,
+        avg( cast(cs_net_profit as decimal(12,2))) agg5,
+        avg( cast(c_birth_year as decimal(12,2))) agg6,
+        avg( cast(cd1.cd_dep_count as decimal(12,2))) agg7
+ from catalog_sales, customer_demographics cd1,
+      customer_demographics cd2, customer, customer_address, date_dim, item
+ where cs_sold_date_sk = d_date_sk and
+       cs_item_sk = i_item_sk and
+       cs_bill_cdemo_sk = cd1.cd_demo_sk and
+       cs_bill_customer_sk = c_customer_sk and
+       cd1.cd_gender = 'M' and
+       cd1.cd_education_status = 'College' and
+       c_current_cdemo_sk = cd2.cd_demo_sk and
+       c_current_addr_sk = ca_address_sk and
+       c_birth_month in (9,5,12,4,1,10) and
+       d_year = 2001 and
+       ca_state in ('ND','WI','AL'
+                   ,'NC','OK','MS','TN')
+ group by rollup (i_item_id, ca_country, ca_state, ca_county)
+ order by ca_country,
+        ca_state,
+        ca_county,
+        i_item_id
+ limit 100;
+---- PLAN
+Max Per-Host Resource Reservation: Memory=111.38MB Threads=8
+Per-Host Resource Estimates: Memory=702MB
+PLAN-ROOT SINK
+|
+15:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_country WHEN 10 THEN ca_country WHEN 12 THEN ca_country WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_state WHEN 10 THEN ca_state WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_county WHEN 10 THEN NULL WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN i_item_id WHEN 10 THEN [...]
+|  row-size=104B cardinality=100
+|
+14:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(16,8,10,12,14) IN (8, 10, 12, 14, 16), CASE valid_tid(16,8,10,12,14) WHEN 8 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 10 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 12 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 14 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 16 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) END), aggif(valid_tid(16,8,10,12,14) IN (8, 10, 12, 14, 16), CASE valid_tid(16,8,10,12,14) WHEN 8 THEN avg(CAST(cs_list_price AS DEC [...]
+|  group by: CASE valid_tid(16,8,10,12,14) WHEN 8 THEN i_item_id WHEN 10 THEN i_item_id WHEN 12 THEN i_item_id WHEN 14 THEN i_item_id WHEN 16 THEN NULL END, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_country WHEN 10 THEN ca_country WHEN 12 THEN ca_country WHEN 14 THEN NULL WHEN 16 THEN NULL END, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_state WHEN 10 THEN ca_state WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_county WHEN 10 T [...]
+|  row-size=108B cardinality=75.61K
+|
+13:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, ca_state, ca_county
+|  Class 1
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, ca_state, NULL
+|  Class 2
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, NULL, NULL
+|  Class 3
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, NULL, NULL, NULL
+|  Class 4
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: NULL, NULL, NULL, NULL
+|  row-size=641B cardinality=75.61K
+|
+12:HASH JOIN [INNER JOIN]
+|  hash predicates: cs_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=220B cardinality=28.95K
+|
+|--06:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=36B cardinality=18.00K
+|
+11:HASH JOIN [INNER JOIN]
+|  hash predicates: cd2.cd_demo_sk = c_current_cdemo_sk
+|  runtime filters: RF002 <- c_current_cdemo_sk
+|  row-size=184B cardinality=28.95K
+|
+|--10:HASH JOIN [INNER JOIN]
+|  |  hash predicates: cd1.cd_demo_sk = cs_bill_cdemo_sk
+|  |  runtime filters: RF004 <- cs_bill_cdemo_sk
+|  |  row-size=180B cardinality=28.95K
+|  |
+|  |--09:HASH JOIN [INNER JOIN]
+|  |  |  hash predicates: c_current_addr_sk = ca_address_sk
+|  |  |  runtime filters: RF006 <- ca_address_sk
+|  |  |  row-size=137B cardinality=28.95K
+|  |  |
+|  |  |--04:SCAN HDFS [tpcds.customer_address]
+|  |  |     HDFS partitions=1/1 files=1 size=5.25MB
+|  |  |     predicates: ca_state IN ('ND', 'WI', 'AL', 'NC', 'OK', 'MS', 'TN')
+|  |  |     row-size=69B cardinality=6.86K
+|  |  |
+|  |  08:HASH JOIN [INNER JOIN]
+|  |  |  hash predicates: cs_bill_customer_sk = c_customer_sk
+|  |  |  runtime filters: RF008 <- c_customer_sk
+|  |  |  row-size=68B cardinality=181.77K
+|  |  |
+|  |  |--03:SCAN HDFS [tpcds.customer]
+|  |  |     HDFS partitions=1/1 files=1 size=12.60MB
+|  |  |     predicates: c_birth_month IN (9, 5, 12, 4, 1, 10)
+|  |  |     runtime filters: RF006 -> c_current_addr_sk
+|  |  |     row-size=20B cardinality=50.00K
+|  |  |
+|  |  07:HASH JOIN [INNER JOIN]
+|  |  |  hash predicates: cs_sold_date_sk = d_date_sk
+|  |  |  runtime filters: RF010 <- d_date_sk
+|  |  |  row-size=48B cardinality=294.63K
+|  |  |
+|  |  |--05:SCAN HDFS [tpcds.date_dim]
+|  |  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |  |     predicates: d_year = 2001
+|  |  |     row-size=8B cardinality=373
+|  |  |
+|  |  00:SCAN HDFS [tpcds.catalog_sales]
+|  |     HDFS partitions=1/1 files=1 size=282.20MB
+|  |     runtime filters: RF000 -> cs_item_sk, RF008 -> cs_bill_customer_sk, RF010 -> cs_sold_date_sk
+|  |     row-size=40B cardinality=1.44M
+|  |
+|  01:SCAN HDFS [tpcds.customer_demographics cd1]
+|     HDFS partitions=1/1 files=1 size=76.92MB
+|     predicates: cd1.cd_gender = 'M', cd1.cd_education_status = 'College'
+|     runtime filters: RF004 -> cd1.cd_demo_sk
+|     row-size=43B cardinality=194.03K
+|
+02:SCAN HDFS [tpcds.customer_demographics cd2]
+   HDFS partitions=1/1 files=1 size=76.92MB
+   runtime filters: RF002 -> cd2.cd_demo_sk
+   row-size=4B cardinality=1.92M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=228.94MB Threads=18
+Per-Host Resource Estimates: Memory=911MB
+PLAN-ROOT SINK
+|
+26:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_country WHEN 10 THEN ca_country WHEN 12 THEN ca_country WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_state WHEN 10 THEN ca_state WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_county WHEN 10 THEN NULL WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN i_item_id WHEN 10 THEN [...]
+|  limit: 100
+|
+15:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_country WHEN 10 THEN ca_country WHEN 12 THEN ca_country WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_state WHEN 10 THEN ca_state WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_county WHEN 10 THEN NULL WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END ASC, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN i_item_id WHEN 10 THEN [...]
+|  row-size=104B cardinality=100
+|
+14:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(16,8,10,12,14) IN (8, 10, 12, 14, 16), CASE valid_tid(16,8,10,12,14) WHEN 8 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 10 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 12 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 14 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) WHEN 16 THEN avg(CAST(cs_quantity AS DECIMAL(12,2))) END), aggif(valid_tid(16,8,10,12,14) IN (8, 10, 12, 14, 16), CASE valid_tid(16,8,10,12,14) WHEN 8 THEN avg(CAST(cs_list_price AS DEC [...]
+|  group by: CASE valid_tid(16,8,10,12,14) WHEN 8 THEN i_item_id WHEN 10 THEN i_item_id WHEN 12 THEN i_item_id WHEN 14 THEN i_item_id WHEN 16 THEN NULL END, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_country WHEN 10 THEN ca_country WHEN 12 THEN ca_country WHEN 14 THEN NULL WHEN 16 THEN NULL END, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_state WHEN 10 THEN ca_state WHEN 12 THEN NULL WHEN 14 THEN NULL WHEN 16 THEN NULL END, CASE valid_tid(16,8,10,12,14) WHEN 8 THEN ca_county WHEN 10 T [...]
+|  row-size=108B cardinality=75.61K
+|
+25:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: avg:merge(CAST(cs_quantity AS DECIMAL(12,2))), avg:merge(CAST(cs_list_price AS DECIMAL(12,2))), avg:merge(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg:merge(CAST(cs_sales_price AS DECIMAL(12,2))), avg:merge(CAST(cs_net_profit AS DECIMAL(12,2))), avg:merge(CAST(c_birth_year AS DECIMAL(12,2))), avg:merge(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, ca_state, ca_county
+|  Class 1
+|    output: avg:merge(CAST(cs_quantity AS DECIMAL(12,2))), avg:merge(CAST(cs_list_price AS DECIMAL(12,2))), avg:merge(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg:merge(CAST(cs_sales_price AS DECIMAL(12,2))), avg:merge(CAST(cs_net_profit AS DECIMAL(12,2))), avg:merge(CAST(c_birth_year AS DECIMAL(12,2))), avg:merge(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, ca_state, NULL
+|  Class 2
+|    output: avg:merge(CAST(cs_quantity AS DECIMAL(12,2))), avg:merge(CAST(cs_list_price AS DECIMAL(12,2))), avg:merge(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg:merge(CAST(cs_sales_price AS DECIMAL(12,2))), avg:merge(CAST(cs_net_profit AS DECIMAL(12,2))), avg:merge(CAST(c_birth_year AS DECIMAL(12,2))), avg:merge(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, NULL, NULL
+|  Class 3
+|    output: avg:merge(CAST(cs_quantity AS DECIMAL(12,2))), avg:merge(CAST(cs_list_price AS DECIMAL(12,2))), avg:merge(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg:merge(CAST(cs_sales_price AS DECIMAL(12,2))), avg:merge(CAST(cs_net_profit AS DECIMAL(12,2))), avg:merge(CAST(c_birth_year AS DECIMAL(12,2))), avg:merge(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, NULL, NULL, NULL
+|  Class 4
+|    output: avg:merge(CAST(cs_quantity AS DECIMAL(12,2))), avg:merge(CAST(cs_list_price AS DECIMAL(12,2))), avg:merge(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg:merge(CAST(cs_sales_price AS DECIMAL(12,2))), avg:merge(CAST(cs_net_profit AS DECIMAL(12,2))), avg:merge(CAST(c_birth_year AS DECIMAL(12,2))), avg:merge(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: NULL, NULL, NULL, NULL
+|  row-size=641B cardinality=75.61K
+|
+24:EXCHANGE [HASH(CASE valid_tid(7,9,11,13,15) WHEN 7 THEN murmur_hash(i_item_id) WHEN 9 THEN murmur_hash(i_item_id) WHEN 11 THEN murmur_hash(i_item_id) WHEN 13 THEN murmur_hash(i_item_id) WHEN 15 THEN murmur_hash(NULL) END,CASE valid_tid(7,9,11,13,15) WHEN 7 THEN murmur_hash(ca_country) WHEN 9 THEN murmur_hash(ca_country) WHEN 11 THEN murmur_hash(ca_country) WHEN 13 THEN murmur_hash(NULL) WHEN 15 THEN murmur_hash(NULL) END,CASE valid_tid(7,9,11,13,15) WHEN 7 THEN murmur_hash(ca_state) W [...]
+|
+13:AGGREGATE [STREAMING]
+|  Class 0
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, ca_state, ca_county
+|  Class 1
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, ca_state, NULL
+|  Class 2
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, ca_country, NULL, NULL
+|  Class 3
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: i_item_id, NULL, NULL, NULL
+|  Class 4
+|    output: avg(CAST(cs_quantity AS DECIMAL(12,2))), avg(CAST(cs_list_price AS DECIMAL(12,2))), avg(CAST(cs_coupon_amt AS DECIMAL(12,2))), avg(CAST(cs_sales_price AS DECIMAL(12,2))), avg(CAST(cs_net_profit AS DECIMAL(12,2))), avg(CAST(c_birth_year AS DECIMAL(12,2))), avg(CAST(cd1.cd_dep_count AS DECIMAL(12,2)))
+|    group by: NULL, NULL, NULL, NULL
+|  row-size=641B cardinality=75.61K
+|
+12:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: cs_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=220B cardinality=28.95K
+|
+|--23:EXCHANGE [BROADCAST]
+|  |
+|  06:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=36B cardinality=18.00K
+|
+11:HASH JOIN [INNER JOIN, PARTITIONED]
+|  hash predicates: c_current_cdemo_sk = cd2.cd_demo_sk
+|  runtime filters: RF002 <- cd2.cd_demo_sk
+|  row-size=184B cardinality=28.95K
+|
+|--22:EXCHANGE [HASH(cd2.cd_demo_sk)]
+|  |
+|  02:SCAN HDFS [tpcds.customer_demographics cd2]
+|     HDFS partitions=1/1 files=1 size=76.92MB
+|     row-size=4B cardinality=1.92M
+|
+21:EXCHANGE [HASH(c_current_cdemo_sk)]
+|
+10:HASH JOIN [INNER JOIN, PARTITIONED]
+|  hash predicates: cs_bill_cdemo_sk = cd1.cd_demo_sk
+|  runtime filters: RF004 <- cd1.cd_demo_sk
+|  row-size=180B cardinality=28.95K
+|
+|--20:EXCHANGE [HASH(cd1.cd_demo_sk)]
+|  |
+|  01:SCAN HDFS [tpcds.customer_demographics cd1]
+|     HDFS partitions=1/1 files=1 size=76.92MB
+|     predicates: cd1.cd_gender = 'M', cd1.cd_education_status = 'College'
+|     row-size=43B cardinality=194.03K
+|
+19:EXCHANGE [HASH(cs_bill_cdemo_sk)]
+|
+09:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: c_current_addr_sk = ca_address_sk
+|  runtime filters: RF006 <- ca_address_sk
+|  row-size=137B cardinality=28.95K
+|
+|--18:EXCHANGE [BROADCAST]
+|  |
+|  04:SCAN HDFS [tpcds.customer_address]
+|     HDFS partitions=1/1 files=1 size=5.25MB
+|     predicates: ca_state IN ('ND', 'WI', 'AL', 'NC', 'OK', 'MS', 'TN')
+|     row-size=69B cardinality=6.86K
+|
+08:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: cs_bill_customer_sk = c_customer_sk
+|  runtime filters: RF008 <- c_customer_sk
+|  row-size=68B cardinality=181.77K
+|
+|--17:EXCHANGE [BROADCAST]
+|  |
+|  03:SCAN HDFS [tpcds.customer]
+|     HDFS partitions=1/1 files=1 size=12.60MB
+|     predicates: c_birth_month IN (9, 5, 12, 4, 1, 10)
+|     runtime filters: RF002 -> c_current_cdemo_sk, RF006 -> c_current_addr_sk
+|     row-size=20B cardinality=50.00K
+|
+07:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: cs_sold_date_sk = d_date_sk
+|  runtime filters: RF010 <- d_date_sk
+|  row-size=48B cardinality=294.63K
+|
+|--16:EXCHANGE [BROADCAST]
+|  |
+|  05:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_year = 2001
+|     row-size=8B cardinality=373
+|
+00:SCAN HDFS [tpcds.catalog_sales]
+   HDFS partitions=1/1 files=1 size=282.20MB
+   runtime filters: RF000 -> cs_item_sk, RF004 -> cs_bill_cdemo_sk, RF008 -> cs_bill_customer_sk, RF010 -> cs_sold_date_sk
+   row-size=40B cardinality=1.44M
+====
+# TPCDS-Q22
+select  i_product_name
+             ,i_brand
+             ,i_class
+             ,i_category
+             ,avg(inv_quantity_on_hand) qoh
+       from inventory
+           ,date_dim
+           ,item
+           ,warehouse
+       where inv_date_sk=d_date_sk
+              and inv_item_sk=i_item_sk
+              and inv_warehouse_sk = w_warehouse_sk
+              and d_month_seq between 1212 and 1212 + 11
+       group by rollup(i_product_name
+                       ,i_brand
+                       ,i_class
+                       ,i_category)
+order by qoh, i_product_name, i_brand, i_class, i_category
+limit 100;
+---- PLAN
+Max Per-Host Resource Reservation: Memory=149.44MB Threads=5
+Per-Host Resource Estimates: Memory=17.43GB
+PLAN-ROOT SINK
+|
+09:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(5,7,9,11,13) IN (5, 7, 9, 11, 13), CASE valid_tid(5,7,9,11,13) WHEN 5 THEN avg(inv_quantity_on_hand) WHEN 7 THEN avg(inv_quantity_on_hand) WHEN 9 THEN avg(inv_quantity_on_hand) WHEN 11 THEN avg(inv_quantity_on_hand) WHEN 13 THEN avg(inv_quantity_on_hand) END) ASC, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_product_name WHEN 7 THEN i_product_name WHEN 9 THEN i_product_name WHEN 11 THEN i_product_name WHEN 13 THEN NULL END ASC, CASE valid_tid(5,7,9,11,13) WHEN 5 [...]
+|  row-size=56B cardinality=100
+|
+08:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(5,7,9,11,13) IN (5, 7, 9, 11, 13), CASE valid_tid(5,7,9,11,13) WHEN 5 THEN avg(inv_quantity_on_hand) WHEN 7 THEN avg(inv_quantity_on_hand) WHEN 9 THEN avg(inv_quantity_on_hand) WHEN 11 THEN avg(inv_quantity_on_hand) WHEN 13 THEN avg(inv_quantity_on_hand) END)
+|  group by: CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_product_name WHEN 7 THEN i_product_name WHEN 9 THEN i_product_name WHEN 11 THEN i_product_name WHEN 13 THEN NULL END, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_brand WHEN 7 THEN i_brand WHEN 9 THEN i_brand WHEN 11 THEN NULL WHEN 13 THEN NULL END, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_class WHEN 7 THEN i_class WHEN 9 THEN NULL WHEN 11 THEN NULL WHEN 13 THEN NULL END, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_category WHEN 7 THEN N [...]
+|  row-size=60B cardinality=35.25M
+|
+07:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, i_class, i_category
+|  Class 1
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, i_class, NULL
+|  Class 2
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, NULL, NULL
+|  Class 3
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, NULL, NULL, NULL
+|  Class 4
+|    output: avg(inv_quantity_on_hand)
+|    group by: NULL, NULL, NULL, NULL
+|  row-size=422B cardinality=35.25M
+|
+06:HASH JOIN [INNER JOIN]
+|  hash predicates: inv_warehouse_sk = w_warehouse_sk
+|  runtime filters: RF000 <- w_warehouse_sk
+|  row-size=136B cardinality=11.74M
+|
+|--03:SCAN HDFS [tpcds.warehouse]
+|     HDFS partitions=1/1 files=1 size=585B
+|     row-size=4B cardinality=5
+|
+05:HASH JOIN [INNER JOIN]
+|  hash predicates: inv_item_sk = i_item_sk
+|  runtime filters: RF002 <- i_item_sk
+|  row-size=132B cardinality=11.74M
+|
+|--02:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=104B cardinality=18.00K
+|
+04:HASH JOIN [INNER JOIN]
+|  hash predicates: inv_date_sk = d_date_sk
+|  runtime filters: RF004 <- d_date_sk
+|  row-size=28B cardinality=11.74M
+|
+|--01:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_month_seq <= 1223, d_month_seq >= 1212
+|     row-size=8B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.inventory]
+   HDFS partitions=1/1 files=1 size=225.47MB
+   runtime filters: RF000 -> inv_warehouse_sk, RF002 -> inv_item_sk, RF004 -> inv_date_sk
+   row-size=20B cardinality=11.74M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=293.20MB Threads=10
+Per-Host Resource Estimates: Memory=41.64GB
+PLAN-ROOT SINK
+|
+15:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: aggif(valid_tid(5,7,9,11,13) IN (5, 7, 9, 11, 13), CASE valid_tid(5,7,9,11,13) WHEN 5 THEN avg(inv_quantity_on_hand) WHEN 7 THEN avg(inv_quantity_on_hand) WHEN 9 THEN avg(inv_quantity_on_hand) WHEN 11 THEN avg(inv_quantity_on_hand) WHEN 13 THEN avg(inv_quantity_on_hand) END) ASC, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_product_name WHEN 7 THEN i_product_name WHEN 9 THEN i_product_name WHEN 11 THEN i_product_name WHEN 13 THEN NULL END ASC, CASE valid_tid(5,7,9,11,13) WHEN 5 [...]
+|  limit: 100
+|
+09:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(5,7,9,11,13) IN (5, 7, 9, 11, 13), CASE valid_tid(5,7,9,11,13) WHEN 5 THEN avg(inv_quantity_on_hand) WHEN 7 THEN avg(inv_quantity_on_hand) WHEN 9 THEN avg(inv_quantity_on_hand) WHEN 11 THEN avg(inv_quantity_on_hand) WHEN 13 THEN avg(inv_quantity_on_hand) END) ASC, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_product_name WHEN 7 THEN i_product_name WHEN 9 THEN i_product_name WHEN 11 THEN i_product_name WHEN 13 THEN NULL END ASC, CASE valid_tid(5,7,9,11,13) WHEN 5 [...]
+|  row-size=56B cardinality=100
+|
+08:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(5,7,9,11,13) IN (5, 7, 9, 11, 13), CASE valid_tid(5,7,9,11,13) WHEN 5 THEN avg(inv_quantity_on_hand) WHEN 7 THEN avg(inv_quantity_on_hand) WHEN 9 THEN avg(inv_quantity_on_hand) WHEN 11 THEN avg(inv_quantity_on_hand) WHEN 13 THEN avg(inv_quantity_on_hand) END)
+|  group by: CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_product_name WHEN 7 THEN i_product_name WHEN 9 THEN i_product_name WHEN 11 THEN i_product_name WHEN 13 THEN NULL END, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_brand WHEN 7 THEN i_brand WHEN 9 THEN i_brand WHEN 11 THEN NULL WHEN 13 THEN NULL END, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_class WHEN 7 THEN i_class WHEN 9 THEN NULL WHEN 11 THEN NULL WHEN 13 THEN NULL END, CASE valid_tid(5,7,9,11,13) WHEN 5 THEN i_category WHEN 7 THEN N [...]
+|  row-size=60B cardinality=35.25M
+|
+14:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: avg:merge(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, i_class, i_category
+|  Class 1
+|    output: avg:merge(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, i_class, NULL
+|  Class 2
+|    output: avg:merge(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, NULL, NULL
+|  Class 3
+|    output: avg:merge(inv_quantity_on_hand)
+|    group by: i_product_name, NULL, NULL, NULL
+|  Class 4
+|    output: avg:merge(inv_quantity_on_hand)
+|    group by: NULL, NULL, NULL, NULL
+|  row-size=422B cardinality=35.25M
+|
+13:EXCHANGE [HASH(CASE valid_tid(4,6,8,10,12) WHEN 4 THEN murmur_hash(i_product_name) WHEN 6 THEN murmur_hash(i_product_name) WHEN 8 THEN murmur_hash(i_product_name) WHEN 10 THEN murmur_hash(i_product_name) WHEN 12 THEN murmur_hash(NULL) END,CASE valid_tid(4,6,8,10,12) WHEN 4 THEN murmur_hash(i_brand) WHEN 6 THEN murmur_hash(i_brand) WHEN 8 THEN murmur_hash(i_brand) WHEN 10 THEN murmur_hash(NULL) WHEN 12 THEN murmur_hash(NULL) END,CASE valid_tid(4,6,8,10,12) WHEN 4 THEN murmur_hash(i_cla [...]
+|
+07:AGGREGATE [STREAMING]
+|  Class 0
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, i_class, i_category
+|  Class 1
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, i_class, NULL
+|  Class 2
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, i_brand, NULL, NULL
+|  Class 3
+|    output: avg(inv_quantity_on_hand)
+|    group by: i_product_name, NULL, NULL, NULL
+|  Class 4
+|    output: avg(inv_quantity_on_hand)
+|    group by: NULL, NULL, NULL, NULL
+|  row-size=422B cardinality=35.25M
+|
+06:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: inv_warehouse_sk = w_warehouse_sk
+|  runtime filters: RF000 <- w_warehouse_sk
+|  row-size=136B cardinality=11.74M
+|
+|--12:EXCHANGE [BROADCAST]
+|  |
+|  03:SCAN HDFS [tpcds.warehouse]
+|     HDFS partitions=1/1 files=1 size=585B
+|     row-size=4B cardinality=5
+|
+05:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: inv_item_sk = i_item_sk
+|  runtime filters: RF002 <- i_item_sk
+|  row-size=132B cardinality=11.74M
+|
+|--11:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=104B cardinality=18.00K
+|
+04:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: inv_date_sk = d_date_sk
+|  runtime filters: RF004 <- d_date_sk
+|  row-size=28B cardinality=11.74M
+|
+|--10:EXCHANGE [BROADCAST]
+|  |
+|  01:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_month_seq <= 1223, d_month_seq >= 1212
+|     row-size=8B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.inventory]
+   HDFS partitions=1/1 files=1 size=225.47MB
+   runtime filters: RF000 -> inv_warehouse_sk, RF002 -> inv_item_sk, RF004 -> inv_date_sk
+   row-size=20B cardinality=11.74M
+====
+# TPCDS-Q67
+select  *
+from (select i_category
+            ,i_class
+            ,i_brand
+            ,i_product_name
+            ,d_year
+            ,d_qoy
+            ,d_moy
+            ,s_store_id
+            ,sumsales
+            ,rank() over (partition by i_category order by sumsales desc) rk
+      from (select i_category
+                  ,i_class
+                  ,i_brand
+                  ,i_product_name
+                  ,d_year
+                  ,d_qoy
+                  ,d_moy
+                  ,s_store_id
+                  ,sum(coalesce(ss_sales_price*ss_quantity,0)) sumsales
+            from store_sales
+                ,date_dim
+                ,store
+                ,item
+       where  ss_sold_date_sk=d_date_sk
+          and ss_item_sk=i_item_sk
+          and ss_store_sk = s_store_sk
+          and d_month_seq between 1212 and 1212+11
+       group by  rollup(i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy,s_store_id))dw1) dw2
+where rk <= 100
+order by i_category
+        ,i_class
+        ,i_brand
+        ,i_product_name
+        ,d_year
+        ,d_qoy
+        ,d_moy
+        ,s_store_id
+        ,sumsales
+        ,rk
+limit 100;
+---- PLAN
+Max Per-Host Resource Reservation: Memory=251.75MB Threads=5
+Per-Host Resource Estimates: Memory=18.41GB
+PLAN-ROOT SINK
+|
+12:TOP-N [LIMIT=100]
+|  order by: i_category ASC, i_class ASC, i_brand ASC, i_product_name ASC, d_year ASC, d_qoy ASC, d_moy ASC, s_store_id ASC, sumsales ASC, rk ASC
+|  row-size=96B cardinality=100
+|
+11:SELECT
+|  predicates: rank() <= 100
+|  row-size=100B cardinality=1.51M
+|
+10:ANALYTIC
+|  functions: rank()
+|  partition by: i_category
+|  order by: aggif(valid_tid(4,5,6,7,8,9,10,11,12) IN (4, 5, 6, 7, 8, 9, 10, 11, 12), CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 5 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 6 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 7 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 8 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 9 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 10 THEN su [...]
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=100B cardinality=15.09M
+|
+09:SORT
+|  order by: CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN i_category WHEN 7 THEN i_category WHEN 8 THEN i_category WHEN 9 THEN i_category WHEN 10 THEN i_category WHEN 11 THEN i_category WHEN 12 THEN NULL END ASC NULLS FIRST, aggif(valid_tid(4,5,6,7,8,9,10,11,12) IN (4, 5, 6, 7, 8, 9, 10, 11, 12), CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 5 THEN sum(coalesce(ss_sales_price * ss_qua [...]
+|  row-size=92B cardinality=15.09M
+|
+08:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(4,5,6,7,8,9,10,11,12) IN (4, 5, 6, 7, 8, 9, 10, 11, 12), CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 5 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 6 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 7 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 8 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 9 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 10 THEN sum( [...]
+|  group by: CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN i_category WHEN 7 THEN i_category WHEN 8 THEN i_category WHEN 9 THEN i_category WHEN 10 THEN i_category WHEN 11 THEN i_category WHEN 12 THEN NULL END, CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN i_class WHEN 5 THEN i_class WHEN 6 THEN i_class WHEN 7 THEN i_class WHEN 8 THEN i_class WHEN 9 THEN i_class WHEN 10 THEN i_class WHEN 11 THEN NULL WHEN 12 THEN NULL END, CASE valid_tid [...]
+|  row-size=92B cardinality=15.09M
+|
+07:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy, s_store_id
+|  Class 1
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy, NULL
+|  Class 2
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, NULL, NULL
+|  Class 3
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, NULL, NULL, NULL
+|  Class 4
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, NULL, NULL, NULL, NULL
+|  Class 5
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, NULL, NULL, NULL, NULL, NULL
+|  Class 6
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, NULL, NULL, NULL, NULL, NULL, NULL
+|  Class 7
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+|  Class 8
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+|  row-size=1.07KB cardinality=15.09M
+|
+06:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=180B cardinality=2.88M
+|
+|--03:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=104B cardinality=18.00K
+|
+05:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF002 <- s_store_sk
+|  row-size=76B cardinality=2.88M
+|
+|--02:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=32B cardinality=12
+|
+04:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF004 <- d_date_sk
+|  row-size=44B cardinality=2.88M
+|
+|--01:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_month_seq <= 1223, d_month_seq >= 1212
+|     row-size=20B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_item_sk, RF002 -> ss_store_sk, RF004 -> ss_sold_date_sk
+   row-size=24B cardinality=2.88M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=505.51MB Threads=11
+Per-Host Resource Estimates: Memory=53.08GB
+PLAN-ROOT SINK
+|
+19:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: i_category ASC, i_class ASC, i_brand ASC, i_product_name ASC, d_year ASC, d_qoy ASC, d_moy ASC, s_store_id ASC, sumsales ASC, rk ASC
+|  limit: 100
+|
+12:TOP-N [LIMIT=100]
+|  order by: i_category ASC, i_class ASC, i_brand ASC, i_product_name ASC, d_year ASC, d_qoy ASC, d_moy ASC, s_store_id ASC, sumsales ASC, rk ASC
+|  row-size=96B cardinality=100
+|
+11:SELECT
+|  predicates: rank() <= 100
+|  row-size=100B cardinality=1.51M
+|
+10:ANALYTIC
+|  functions: rank()
+|  partition by: i_category
+|  order by: aggif(valid_tid(4,5,6,7,8,9,10,11,12) IN (4, 5, 6, 7, 8, 9, 10, 11, 12), CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 5 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 6 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 7 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 8 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 9 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 10 THEN su [...]
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=100B cardinality=15.09M
+|
+09:SORT
+|  order by: CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN i_category WHEN 7 THEN i_category WHEN 8 THEN i_category WHEN 9 THEN i_category WHEN 10 THEN i_category WHEN 11 THEN i_category WHEN 12 THEN NULL END ASC NULLS FIRST, aggif(valid_tid(4,5,6,7,8,9,10,11,12) IN (4, 5, 6, 7, 8, 9, 10, 11, 12), CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 5 THEN sum(coalesce(ss_sales_price * ss_qua [...]
+|  row-size=92B cardinality=15.09M
+|
+18:EXCHANGE [HASH(CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN i_category WHEN 7 THEN i_category WHEN 8 THEN i_category WHEN 9 THEN i_category WHEN 10 THEN i_category WHEN 11 THEN i_category WHEN 12 THEN NULL END)]
+|
+08:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(4,5,6,7,8,9,10,11,12) IN (4, 5, 6, 7, 8, 9, 10, 11, 12), CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 5 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 6 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 7 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 8 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 9 THEN sum(coalesce(ss_sales_price * ss_quantity, 0)) WHEN 10 THEN sum( [...]
+|  group by: CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN i_category WHEN 7 THEN i_category WHEN 8 THEN i_category WHEN 9 THEN i_category WHEN 10 THEN i_category WHEN 11 THEN i_category WHEN 12 THEN NULL END, CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN i_class WHEN 5 THEN i_class WHEN 6 THEN i_class WHEN 7 THEN i_class WHEN 8 THEN i_class WHEN 9 THEN i_class WHEN 10 THEN i_class WHEN 11 THEN NULL WHEN 12 THEN NULL END, CASE valid_tid [...]
+|  row-size=92B cardinality=15.09M
+|
+17:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy, s_store_id
+|  Class 1
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy, NULL
+|  Class 2
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, NULL, NULL
+|  Class 3
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, NULL, NULL, NULL
+|  Class 4
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, NULL, NULL, NULL, NULL
+|  Class 5
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, NULL, NULL, NULL, NULL, NULL
+|  Class 6
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, NULL, NULL, NULL, NULL, NULL, NULL
+|  Class 7
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+|  Class 8
+|    output: sum:merge(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+|  row-size=1.07KB cardinality=15.09M
+|
+16:EXCHANGE [HASH(CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN murmur_hash(i_category) WHEN 5 THEN murmur_hash(i_category) WHEN 6 THEN murmur_hash(i_category) WHEN 7 THEN murmur_hash(i_category) WHEN 8 THEN murmur_hash(i_category) WHEN 9 THEN murmur_hash(i_category) WHEN 10 THEN murmur_hash(i_category) WHEN 11 THEN murmur_hash(i_category) WHEN 12 THEN murmur_hash(NULL) END,CASE valid_tid(4,5,6,7,8,9,10,11,12) WHEN 4 THEN murmur_hash(i_class) WHEN 5 THEN murmur_hash(i_class) WHEN 6 TH [...]
+|
+07:AGGREGATE [STREAMING]
+|  Class 0
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy, s_store_id
+|  Class 1
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy, NULL
+|  Class 2
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, d_qoy, NULL, NULL
+|  Class 3
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, d_year, NULL, NULL, NULL
+|  Class 4
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, i_product_name, NULL, NULL, NULL, NULL
+|  Class 5
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, i_brand, NULL, NULL, NULL, NULL, NULL
+|  Class 6
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, i_class, NULL, NULL, NULL, NULL, NULL, NULL
+|  Class 7
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: i_category, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+|  Class 8
+|    output: sum(coalesce(ss_sales_price * ss_quantity, 0))
+|    group by: NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+|  row-size=1.07KB cardinality=15.09M
+|
+06:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=180B cardinality=2.88M
+|
+|--15:EXCHANGE [BROADCAST]
+|  |
+|  03:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=104B cardinality=18.00K
+|
+05:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF002 <- s_store_sk
+|  row-size=76B cardinality=2.88M
+|
+|--14:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=32B cardinality=12
+|
+04:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF004 <- d_date_sk
+|  row-size=44B cardinality=2.88M
+|
+|--13:EXCHANGE [BROADCAST]
+|  |
+|  01:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_month_seq <= 1223, d_month_seq >= 1212
+|     row-size=20B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_item_sk, RF002 -> ss_store_sk, RF004 -> ss_sold_date_sk
+   row-size=24B cardinality=2.88M
+====
+# TPCDS-Q5
+with ssr as
+ (select s_store_id,
+        sum(sales_price) as sales,
+        sum(profit) as profit,
+        sum(return_amt) as `returns`,
+        sum(net_loss) as profit_loss
+ from
+  ( select  ss_store_sk as store_sk,
+            ss_sold_date_sk  as date_sk,
+            ss_ext_sales_price as sales_price,
+            ss_net_profit as profit,
+            cast(0 as decimal(7,2)) as return_amt,
+            cast(0 as decimal(7,2)) as net_loss
+    from store_sales
+    union all
+    select sr_store_sk as store_sk,
+           sr_returned_date_sk as date_sk,
+           cast(0 as decimal(7,2)) as sales_price,
+           cast(0 as decimal(7,2)) as profit,
+           sr_return_amt as return_amt,
+           sr_net_loss as net_loss
+    from store_returns
+   ) salesreturns,
+     date_dim,
+     store
+ where date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  INTERVAL 14 days)
+       and store_sk = s_store_sk
+ group by s_store_id)
+ ,
+ csr as
+ (select cp_catalog_page_id,
+        sum(sales_price) as sales,
+        sum(profit) as profit,
+        sum(return_amt) as `returns`,
+        sum(net_loss) as profit_loss
+ from
+  ( select  cs_catalog_page_sk as page_sk,
+            cs_sold_date_sk  as date_sk,
+            cs_ext_sales_price as sales_price,
+            cs_net_profit as profit,
+            cast(0 as decimal(7,2)) as return_amt,
+            cast(0 as decimal(7,2)) as net_loss
+    from catalog_sales
+    union all
+    select cr_catalog_page_sk as page_sk,
+           cr_returned_date_sk as date_sk,
+           cast(0 as decimal(7,2)) as sales_price,
+           cast(0 as decimal(7,2)) as profit,
+           cr_return_amount as return_amt,
+           cr_net_loss as net_loss
+    from catalog_returns
+   ) salesreturns,
+     date_dim,
+     catalog_page
+ where date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  INTERVAL 14 days)
+       and page_sk = cp_catalog_page_sk
+ group by cp_catalog_page_id)
+ ,
+ wsr as
+ (select web_site_id,
+        sum(sales_price) as sales,
+        sum(profit) as profit,
+        sum(return_amt) as `returns`,
+        sum(net_loss) as profit_loss
+ from
+  ( select  ws_web_site_sk as wsr_web_site_sk,
+            ws_sold_date_sk  as date_sk,
+            ws_ext_sales_price as sales_price,
+            ws_net_profit as profit,
+            cast(0 as decimal(7,2)) as return_amt,
+            cast(0 as decimal(7,2)) as net_loss
+    from web_sales
+    union all
+    select ws_web_site_sk as wsr_web_site_sk,
+           wr_returned_date_sk as date_sk,
+           cast(0 as decimal(7,2)) as sales_price,
+           cast(0 as decimal(7,2)) as profit,
+           wr_return_amt as return_amt,
+           wr_net_loss as net_loss
+    from web_returns left outer join web_sales on
+         ( wr_item_sk = ws_item_sk
+           and wr_order_number = ws_order_number)
+   ) salesreturns,
+     date_dim,
+     web_site
+ where date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  INTERVAL 14 days)
+       and wsr_web_site_sk = web_site_sk
+ group by web_site_id)
+  select  channel
+        , id
+        , sum(sales) as sales
+        , sum(`returns`) as `returns`
+        , sum(profit) as profit
+ from
+ (select 'store channel' as channel
+        , 'store' || s_store_id as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from   ssr
+ union all
+ select 'catalog channel' as channel
+        , 'catalog_page' || cp_catalog_page_id as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from  csr
+ union all
+ select 'web channel' as channel
+        , 'web_site' || web_site_id as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from   wsr
+ ) x
+ group by rollup (channel, id)
+ order by channel
+         ,id
+ limit 100;
+---- PLAN
+Max Per-Host Resource Reservation: Memory=47.39MB Threads=5
+Per-Host Resource Estimates: Memory=333MB
+PLAN-ROOT SINK
+|
+29:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(27,28,29) WHEN 27 THEN channel WHEN 28 THEN channel WHEN 29 THEN NULL END ASC, CASE valid_tid(27,28,29) WHEN 27 THEN id WHEN 28 THEN NULL WHEN 29 THEN NULL END ASC
+|  row-size=72B cardinality=100
+|
+28:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(27,28,29) IN (27, 28, 29), CASE valid_tid(27,28,29) WHEN 27 THEN sum(sales) WHEN 28 THEN sum(sales) WHEN 29 THEN sum(sales) END), aggif(valid_tid(27,28,29) IN (27, 28, 29), CASE valid_tid(27,28,29) WHEN 27 THEN sum(`returns`) WHEN 28 THEN sum(`returns`) WHEN 29 THEN sum(`returns`) END), aggif(valid_tid(27,28,29) IN (27, 28, 29), CASE valid_tid(27,28,29) WHEN 27 THEN sum(profit) WHEN 28 THEN sum(profit) WHEN 29 THEN sum(profit) END)
+|  group by: CASE valid_tid(27,28,29) WHEN 27 THEN channel WHEN 28 THEN channel WHEN 29 THEN NULL END, CASE valid_tid(27,28,29) WHEN 27 THEN id WHEN 28 THEN NULL WHEN 29 THEN NULL END, CASE valid_tid(27,28,29) WHEN 27 THEN 27 WHEN 28 THEN 28 WHEN 29 THEN 29 END
+|  row-size=76B cardinality=11.56K
+|
+27:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: NULL, NULL
+|  row-size=216B cardinality=11.56K
+|
+00:UNION
+|  row-size=72B cardinality=11.56K
+|
+|--26:AGGREGATE [FINALIZE]
+|  |  output: sum(sales_price), sum(profit), sum(return_amt), sum(net_loss)
+|  |  group by: web_site_id
+|  |  row-size=92B cardinality=15
+|  |
+|  25:HASH JOIN [INNER JOIN]
+|  |  hash predicates: wsr_web_site_sk = web_site_sk
+|  |  runtime filters: RF008 <- web_site_sk
+|  |  row-size=82B cardinality=791.15K
+|  |
+|  |--23:SCAN HDFS [tpcds.web_site]
+|  |     HDFS partitions=1/1 files=1 size=8.57KB
+|  |     row-size=32B cardinality=30
+|  |
+|  24:HASH JOIN [INNER JOIN]
+|  |  hash predicates: date_sk = d_date_sk
+|  |  runtime filters: RF010 <- d_date_sk
+|  |  row-size=50B cardinality=791.15K
+|  |
+|  |--22:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-08-18', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  17:UNION
+|  |  row-size=24B cardinality=791.15K
+|  |
+|  |--21:HASH JOIN [RIGHT OUTER JOIN]
+|  |  |  hash predicates: ws_item_sk = wr_item_sk, ws_order_number = wr_order_number
+|  |  |  runtime filters: RF012 <- wr_item_sk, RF013 <- wr_order_number
+|  |  |  row-size=48B cardinality=71.76K
+|  |  |
+|  |  |--19:SCAN HDFS [tpcds.web_returns]
+|  |  |     HDFS partitions=1/1 files=1 size=9.35MB
+|  |  |     runtime filters: RF010 -> tpcds.web_returns.wr_returned_date_sk
+|  |  |     row-size=28B cardinality=71.76K
+|  |  |
+|  |  20:SCAN HDFS [tpcds.web_sales]
+|  |     HDFS partitions=1/1 files=1 size=140.07MB
+|  |     runtime filters: RF008 -> tpcds.web_sales.ws_web_site_sk, RF012 -> ws_item_sk, RF013 -> ws_order_number
+|  |     row-size=20B cardinality=719.38K
+|  |
+|  18:SCAN HDFS [tpcds.web_sales]
+|     HDFS partitions=1/1 files=1 size=140.07MB
+|     runtime filters: RF008 -> tpcds.web_sales.ws_web_site_sk, RF010 -> tpcds.web_sales.ws_sold_date_sk
+|     row-size=16B cardinality=719.38K
+|
+|--16:AGGREGATE [FINALIZE]
+|  |  output: sum(sales_price), sum(profit), sum(return_amt), sum(net_loss)
+|  |  group by: cp_catalog_page_id
+|  |  row-size=92B cardinality=11.54K
+|  |
+|  15:HASH JOIN [INNER JOIN]
+|  |  hash predicates: page_sk = cp_catalog_page_sk
+|  |  runtime filters: RF004 <- cp_catalog_page_sk
+|  |  row-size=82B cardinality=1.59M
+|  |
+|  |--13:SCAN HDFS [tpcds.catalog_page]
+|  |     HDFS partitions=1/1 files=1 size=1.56MB
+|  |     row-size=32B cardinality=11.72K
+|  |
+|  14:HASH JOIN [INNER JOIN]
+|  |  hash predicates: date_sk = d_date_sk
+|  |  runtime filters: RF006 <- d_date_sk
+|  |  row-size=50B cardinality=1.59M
+|  |
+|  |--12:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-08-18', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  09:UNION
+|  |  row-size=24B cardinality=1.59M
+|  |
+|  |--11:SCAN HDFS [tpcds.catalog_returns]
+|  |     HDFS partitions=1/1 files=1 size=20.39MB
+|  |     runtime filters: RF004 -> tpcds.catalog_returns.cr_catalog_page_sk, RF006 -> tpcds.catalog_returns.cr_returned_date_sk
+|  |     row-size=16B cardinality=144.07K
+|  |
+|  10:SCAN HDFS [tpcds.catalog_sales]
+|     HDFS partitions=1/1 files=1 size=282.20MB
+|     runtime filters: RF004 -> tpcds.catalog_sales.cs_catalog_page_sk, RF006 -> tpcds.catalog_sales.cs_sold_date_sk
+|     row-size=16B cardinality=1.44M
+|
+08:AGGREGATE [FINALIZE]
+|  output: sum(sales_price), sum(profit), sum(return_amt), sum(net_loss)
+|  group by: s_store_id
+|  row-size=92B cardinality=6
+|
+07:HASH JOIN [INNER JOIN]
+|  hash predicates: store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=82B cardinality=3.17M
+|
+|--05:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=32B cardinality=12
+|
+06:HASH JOIN [INNER JOIN]
+|  hash predicates: date_sk = d_date_sk
+|  runtime filters: RF002 <- d_date_sk
+|  row-size=50B cardinality=3.17M
+|
+|--04:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_date <= DATE '1998-08-18', d_date >= DATE '1998-08-04'
+|     row-size=26B cardinality=7.30K
+|
+01:UNION
+|  row-size=24B cardinality=3.17M
+|
+|--03:SCAN HDFS [tpcds.store_returns]
+|     HDFS partitions=1/1 files=1 size=31.19MB
+|     runtime filters: RF000 -> tpcds.store_returns.sr_store_sk, RF002 -> tpcds.store_returns.sr_returned_date_sk
+|     row-size=16B cardinality=287.51K
+|
+02:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> tpcds.store_sales.ss_store_sk, RF002 -> tpcds.store_sales.ss_sold_date_sk
+   row-size=16B cardinality=2.88M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=114.27MB Threads=25
+Per-Host Resource Estimates: Memory=1.02GB
+PLAN-ROOT SINK
+|
+46:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: CASE valid_tid(27,28,29) WHEN 27 THEN channel WHEN 28 THEN channel WHEN 29 THEN NULL END ASC, CASE valid_tid(27,28,29) WHEN 27 THEN id WHEN 28 THEN NULL WHEN 29 THEN NULL END ASC
+|  limit: 100
+|
+29:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(27,28,29) WHEN 27 THEN channel WHEN 28 THEN channel WHEN 29 THEN NULL END ASC, CASE valid_tid(27,28,29) WHEN 27 THEN id WHEN 28 THEN NULL WHEN 29 THEN NULL END ASC
+|  row-size=72B cardinality=100
+|
+28:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(27,28,29) IN (27, 28, 29), CASE valid_tid(27,28,29) WHEN 27 THEN sum(sales) WHEN 28 THEN sum(sales) WHEN 29 THEN sum(sales) END), aggif(valid_tid(27,28,29) IN (27, 28, 29), CASE valid_tid(27,28,29) WHEN 27 THEN sum(`returns`) WHEN 28 THEN sum(`returns`) WHEN 29 THEN sum(`returns`) END), aggif(valid_tid(27,28,29) IN (27, 28, 29), CASE valid_tid(27,28,29) WHEN 27 THEN sum(profit) WHEN 28 THEN sum(profit) WHEN 29 THEN sum(profit) END)
+|  group by: CASE valid_tid(27,28,29) WHEN 27 THEN channel WHEN 28 THEN channel WHEN 29 THEN NULL END, CASE valid_tid(27,28,29) WHEN 27 THEN id WHEN 28 THEN NULL WHEN 29 THEN NULL END, CASE valid_tid(27,28,29) WHEN 27 THEN 27 WHEN 28 THEN 28 WHEN 29 THEN 29 END
+|  row-size=76B cardinality=11.56K
+|
+45:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: NULL, NULL
+|  row-size=216B cardinality=11.56K
+|
+44:EXCHANGE [HASH(CASE valid_tid(27,28,29) WHEN 27 THEN murmur_hash(channel) WHEN 28 THEN murmur_hash(channel) WHEN 29 THEN murmur_hash(NULL) END,CASE valid_tid(27,28,29) WHEN 27 THEN murmur_hash(id) WHEN 28 THEN murmur_hash(NULL) WHEN 29 THEN murmur_hash(NULL) END)]
+|
+27:AGGREGATE [STREAMING]
+|  Class 0
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: NULL, NULL
+|  row-size=216B cardinality=11.56K
+|
+00:UNION
+|  row-size=72B cardinality=11.56K
+|
+|--43:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(sales_price), sum:merge(profit), sum:merge(return_amt), sum:merge(net_loss)
+|  |  group by: web_site_id
+|  |  row-size=92B cardinality=15
+|  |
+|  42:EXCHANGE [HASH(web_site_id)]
+|  |
+|  26:AGGREGATE [STREAMING]
+|  |  output: sum(sales_price), sum(profit), sum(return_amt), sum(net_loss)
+|  |  group by: web_site_id
+|  |  row-size=92B cardinality=15
+|  |
+|  25:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: wsr_web_site_sk = web_site_sk
+|  |  runtime filters: RF008 <- web_site_sk
+|  |  row-size=82B cardinality=791.15K
+|  |
+|  |--41:EXCHANGE [BROADCAST]
+|  |  |
+|  |  23:SCAN HDFS [tpcds.web_site]
+|  |     HDFS partitions=1/1 files=1 size=8.57KB
+|  |     row-size=32B cardinality=30
+|  |
+|  24:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: date_sk = d_date_sk
+|  |  runtime filters: RF010 <- d_date_sk
+|  |  row-size=50B cardinality=791.15K
+|  |
+|  |--40:EXCHANGE [BROADCAST]
+|  |  |
+|  |  22:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-08-18', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  17:UNION
+|  |  row-size=24B cardinality=791.15K
+|  |
+|  |--21:HASH JOIN [RIGHT OUTER JOIN, PARTITIONED]
+|  |  |  hash predicates: ws_item_sk = wr_item_sk, ws_order_number = wr_order_number
+|  |  |  runtime filters: RF012 <- wr_item_sk, RF013 <- wr_order_number
+|  |  |  row-size=48B cardinality=71.76K
+|  |  |
+|  |  |--39:EXCHANGE [HASH(wr_item_sk,wr_order_number)]
+|  |  |  |
+|  |  |  19:SCAN HDFS [tpcds.web_returns]
+|  |  |     HDFS partitions=1/1 files=1 size=9.35MB
+|  |  |     runtime filters: RF010 -> tpcds.web_returns.wr_returned_date_sk
+|  |  |     row-size=28B cardinality=71.76K
+|  |  |
+|  |  38:EXCHANGE [HASH(ws_item_sk,ws_order_number)]
+|  |  |
+|  |  20:SCAN HDFS [tpcds.web_sales]
+|  |     HDFS partitions=1/1 files=1 size=140.07MB
+|  |     runtime filters: RF008 -> tpcds.web_sales.ws_web_site_sk, RF012 -> ws_item_sk, RF013 -> ws_order_number
+|  |     row-size=20B cardinality=719.38K
+|  |
+|  18:SCAN HDFS [tpcds.web_sales]
+|     HDFS partitions=1/1 files=1 size=140.07MB
+|     runtime filters: RF008 -> tpcds.web_sales.ws_web_site_sk, RF010 -> tpcds.web_sales.ws_sold_date_sk
+|     row-size=16B cardinality=719.38K
+|
+|--37:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(sales_price), sum:merge(profit), sum:merge(return_amt), sum:merge(net_loss)
+|  |  group by: cp_catalog_page_id
+|  |  row-size=92B cardinality=11.54K
+|  |
+|  36:EXCHANGE [HASH(cp_catalog_page_id)]
+|  |
+|  16:AGGREGATE [STREAMING]
+|  |  output: sum(sales_price), sum(profit), sum(return_amt), sum(net_loss)
+|  |  group by: cp_catalog_page_id
+|  |  row-size=92B cardinality=11.54K
+|  |
+|  15:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: page_sk = cp_catalog_page_sk
+|  |  runtime filters: RF004 <- cp_catalog_page_sk
+|  |  row-size=82B cardinality=1.59M
+|  |
+|  |--35:EXCHANGE [BROADCAST]
+|  |  |
+|  |  13:SCAN HDFS [tpcds.catalog_page]
+|  |     HDFS partitions=1/1 files=1 size=1.56MB
+|  |     row-size=32B cardinality=11.72K
+|  |
+|  14:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: date_sk = d_date_sk
+|  |  runtime filters: RF006 <- d_date_sk
+|  |  row-size=50B cardinality=1.59M
+|  |
+|  |--34:EXCHANGE [BROADCAST]
+|  |  |
+|  |  12:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-08-18', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  09:UNION
+|  |  row-size=24B cardinality=1.59M
+|  |
+|  |--11:SCAN HDFS [tpcds.catalog_returns]
+|  |     HDFS partitions=1/1 files=1 size=20.39MB
+|  |     runtime filters: RF004 -> tpcds.catalog_returns.cr_catalog_page_sk, RF006 -> tpcds.catalog_returns.cr_returned_date_sk
+|  |     row-size=16B cardinality=144.07K
+|  |
+|  10:SCAN HDFS [tpcds.catalog_sales]
+|     HDFS partitions=1/1 files=1 size=282.20MB
+|     runtime filters: RF004 -> tpcds.catalog_sales.cs_catalog_page_sk, RF006 -> tpcds.catalog_sales.cs_sold_date_sk
+|     row-size=16B cardinality=1.44M
+|
+33:AGGREGATE [FINALIZE]
+|  output: sum:merge(sales_price), sum:merge(profit), sum:merge(return_amt), sum:merge(net_loss)
+|  group by: s_store_id
+|  row-size=92B cardinality=6
+|
+32:EXCHANGE [HASH(s_store_id)]
+|
+08:AGGREGATE [STREAMING]
+|  output: sum(sales_price), sum(profit), sum(return_amt), sum(net_loss)
+|  group by: s_store_id
+|  row-size=92B cardinality=6
+|
+07:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=82B cardinality=3.17M
+|
+|--31:EXCHANGE [BROADCAST]
+|  |
+|  05:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=32B cardinality=12
+|
+06:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: date_sk = d_date_sk
+|  runtime filters: RF002 <- d_date_sk
+|  row-size=50B cardinality=3.17M
+|
+|--30:EXCHANGE [BROADCAST]
+|  |
+|  04:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_date <= DATE '1998-08-18', d_date >= DATE '1998-08-04'
+|     row-size=26B cardinality=7.30K
+|
+01:UNION
+|  row-size=24B cardinality=3.17M
+|
+|--03:SCAN HDFS [tpcds.store_returns]
+|     HDFS partitions=1/1 files=1 size=31.19MB
+|     runtime filters: RF000 -> tpcds.store_returns.sr_store_sk, RF002 -> tpcds.store_returns.sr_returned_date_sk
+|     row-size=16B cardinality=287.51K
+|
+02:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> tpcds.store_sales.ss_store_sk, RF002 -> tpcds.store_sales.ss_sold_date_sk
+   row-size=16B cardinality=2.88M
+====
+# TPCDS-Q77
+with ss as
+ (select s_store_sk,
+         sum(ss_ext_sales_price) as sales,
+         sum(ss_net_profit) as profit
+ from store_sales,
+      date_dim,
+      store
+ where ss_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and ss_store_sk = s_store_sk
+ group by s_store_sk)
+ ,
+ sr as
+ (select s_store_sk,
+         sum(sr_return_amt) as `returns`,
+         sum(sr_net_loss) as profit_loss
+ from store_returns,
+      date_dim,
+      store
+ where sr_returned_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and sr_store_sk = s_store_sk
+ group by s_store_sk),
+ cs as
+ (select cs_call_center_sk,
+        sum(cs_ext_sales_price) as sales,
+        sum(cs_net_profit) as profit
+ from catalog_sales,
+      date_dim
+ where cs_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+ group by cs_call_center_sk
+ ),
+ cr as
+ (select
+        sum(cr_return_amount) as `returns`,
+        sum(cr_net_loss) as profit_loss
+ from catalog_returns,
+      date_dim
+ where cr_returned_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+ ),
+ ws as
+ ( select wp_web_page_sk,
+        sum(ws_ext_sales_price) as sales,
+        sum(ws_net_profit) as profit
+ from web_sales,
+      date_dim,
+      web_page
+ where ws_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and ws_web_page_sk = wp_web_page_sk
+ group by wp_web_page_sk),
+ wr as
+ (select wp_web_page_sk,
+        sum(wr_return_amt) as `returns`,
+        sum(wr_net_loss) as profit_loss
+ from web_returns,
+      date_dim,
+      web_page
+ where wr_returned_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and wr_web_page_sk = wp_web_page_sk
+ group by wp_web_page_sk)
+  select  channel
+        , id
+        , sum(sales) as sales
+        , sum(`returns`) as `returns`
+        , sum(profit) as profit
+ from
+ (select 'store channel' as channel
+        , ss.s_store_sk as id
+        , sales
+        , coalesce(`returns`, 0) as `returns`
+        , (profit - coalesce(profit_loss,0)) as profit
+ from   ss left join sr
+        on  ss.s_store_sk = sr.s_store_sk
+ union all
+ select 'catalog channel' as channel
+        , cs_call_center_sk as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from  cs
+       , cr
+ union all
+ select 'web channel' as channel
+        , ws.wp_web_page_sk as id
+        , sales
+        , coalesce(`returns`, 0) `returns`
+        , (profit - coalesce(profit_loss,0)) as profit
+ from   ws left join wr
+        on  ws.wp_web_page_sk = wr.wp_web_page_sk
+ ) x
+ group by rollup (channel, id)
+ order by channel
+         ,id
+         ,sales desc
+ limit 100;
+---- PLAN
+Max Per-Host Resource Reservation: Memory=55.58MB Threads=7
+Per-Host Resource Estimates: Memory=416MB
+PLAN-ROOT SINK
+|
+38:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(32,30,31) WHEN 30 THEN channel WHEN 31 THEN channel WHEN 32 THEN NULL END ASC, CASE valid_tid(32,30,31) WHEN 30 THEN id WHEN 31 THEN NULL WHEN 32 THEN NULL END ASC, aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(sales) WHEN 31 THEN sum(sales) WHEN 32 THEN sum(sales) END) DESC
+|  row-size=64B cardinality=82
+|
+37:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(sales) WHEN 31 THEN sum(sales) WHEN 32 THEN sum(sales) END), aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(`returns`) WHEN 31 THEN sum(`returns`) WHEN 32 THEN sum(`returns`) END), aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(profit) WHEN 31 THEN sum(profit) WHEN 32 THEN sum(profit) END)
+|  group by: CASE valid_tid(32,30,31) WHEN 30 THEN channel WHEN 31 THEN channel WHEN 32 THEN NULL END, CASE valid_tid(32,30,31) WHEN 30 THEN id WHEN 31 THEN NULL WHEN 32 THEN NULL END, CASE valid_tid(32,30,31) WHEN 30 THEN 30 WHEN 31 THEN 31 WHEN 32 THEN 32 END
+|  row-size=68B cardinality=82
+|
+36:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: NULL, NULL
+|  row-size=192B cardinality=82
+|
+00:UNION
+|  row-size=64B cardinality=78
+|
+|--35:HASH JOIN [LEFT OUTER JOIN]
+|  |  hash predicates: wp_web_page_sk = wp_web_page_sk
+|  |  row-size=72B cardinality=60
+|  |
+|  |--34:AGGREGATE [FINALIZE]
+|  |  |  output: sum(wr_return_amt), sum(wr_net_loss)
+|  |  |  group by: wp_web_page_sk
+|  |  |  row-size=36B cardinality=60
+|  |  |
+|  |  33:HASH JOIN [INNER JOIN]
+|  |  |  hash predicates: wr_web_page_sk = wp_web_page_sk
+|  |  |  runtime filters: RF016 <- wp_web_page_sk
+|  |  |  row-size=46B cardinality=71.76K
+|  |  |
+|  |  |--31:SCAN HDFS [tpcds.web_page]
+|  |  |     HDFS partitions=1/1 files=1 size=5.64KB
+|  |  |     row-size=4B cardinality=60
+|  |  |
+|  |  32:HASH JOIN [INNER JOIN]
+|  |  |  hash predicates: wr_returned_date_sk = d_date_sk
+|  |  |  runtime filters: RF018 <- d_date_sk
+|  |  |  row-size=42B cardinality=71.76K
+|  |  |
+|  |  |--30:SCAN HDFS [tpcds.date_dim]
+|  |  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |  |     row-size=26B cardinality=7.30K
+|  |  |
+|  |  29:SCAN HDFS [tpcds.web_returns]
+|  |     HDFS partitions=1/1 files=1 size=9.35MB
+|  |     runtime filters: RF016 -> wr_web_page_sk, RF018 -> wr_returned_date_sk
+|  |     row-size=16B cardinality=71.76K
+|  |
+|  28:AGGREGATE [FINALIZE]
+|  |  output: sum(ws_ext_sales_price), sum(ws_net_profit)
+|  |  group by: wp_web_page_sk
+|  |  row-size=36B cardinality=60
+|  |
+|  27:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ws_web_page_sk = wp_web_page_sk
+|  |  runtime filters: RF012 <- wp_web_page_sk
+|  |  row-size=46B cardinality=719.38K
+|  |
+|  |--25:SCAN HDFS [tpcds.web_page]
+|  |     HDFS partitions=1/1 files=1 size=5.64KB
+|  |     row-size=4B cardinality=60
+|  |
+|  26:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ws_sold_date_sk = d_date_sk
+|  |  runtime filters: RF014 <- d_date_sk
+|  |  row-size=42B cardinality=719.38K
+|  |
+|  |--24:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  23:SCAN HDFS [tpcds.web_sales]
+|     HDFS partitions=1/1 files=1 size=140.07MB
+|     runtime filters: RF012 -> ws_web_page_sk, RF014 -> ws_sold_date_sk
+|     row-size=16B cardinality=719.38K
+|
+|--22:NESTED LOOP JOIN [CROSS JOIN]
+|  |  row-size=68B cardinality=6
+|  |
+|  |--21:AGGREGATE [FINALIZE]
+|  |  |  output: sum(cr_return_amount), sum(cr_net_loss)
+|  |  |  row-size=32B cardinality=1
+|  |  |
+|  |  20:HASH JOIN [INNER JOIN]
+|  |  |  hash predicates: cr_returned_date_sk = d_date_sk
+|  |  |  runtime filters: RF010 <- d_date_sk
+|  |  |  row-size=38B cardinality=144.07K
+|  |  |
+|  |  |--19:SCAN HDFS [tpcds.date_dim]
+|  |  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |  |     row-size=26B cardinality=7.30K
+|  |  |
+|  |  18:SCAN HDFS [tpcds.catalog_returns]
+|  |     HDFS partitions=1/1 files=1 size=20.39MB
+|  |     runtime filters: RF010 -> cr_returned_date_sk
+|  |     row-size=12B cardinality=144.07K
+|  |
+|  17:AGGREGATE [FINALIZE]
+|  |  output: sum(cs_ext_sales_price), sum(cs_net_profit)
+|  |  group by: cs_call_center_sk
+|  |  row-size=36B cardinality=6
+|  |
+|  16:HASH JOIN [INNER JOIN]
+|  |  hash predicates: cs_sold_date_sk = d_date_sk
+|  |  runtime filters: RF008 <- d_date_sk
+|  |  row-size=42B cardinality=1.44M
+|  |
+|  |--15:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  14:SCAN HDFS [tpcds.catalog_sales]
+|     HDFS partitions=1/1 files=1 size=282.20MB
+|     runtime filters: RF008 -> cs_sold_date_sk
+|     row-size=16B cardinality=1.44M
+|
+13:HASH JOIN [LEFT OUTER JOIN]
+|  hash predicates: s_store_sk = s_store_sk
+|  row-size=72B cardinality=12
+|
+|--12:AGGREGATE [FINALIZE]
+|  |  output: sum(sr_return_amt), sum(sr_net_loss)
+|  |  group by: s_store_sk
+|  |  row-size=36B cardinality=12
+|  |
+|  11:HASH JOIN [INNER JOIN]
+|  |  hash predicates: sr_store_sk = s_store_sk
+|  |  runtime filters: RF004 <- s_store_sk
+|  |  row-size=46B cardinality=287.51K
+|  |
+|  |--09:SCAN HDFS [tpcds.store]
+|  |     HDFS partitions=1/1 files=1 size=3.08KB
+|  |     row-size=4B cardinality=12
+|  |
+|  10:HASH JOIN [INNER JOIN]
+|  |  hash predicates: sr_returned_date_sk = d_date_sk
+|  |  runtime filters: RF006 <- d_date_sk
+|  |  row-size=42B cardinality=287.51K
+|  |
+|  |--08:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  07:SCAN HDFS [tpcds.store_returns]
+|     HDFS partitions=1/1 files=1 size=31.19MB
+|     runtime filters: RF004 -> sr_store_sk, RF006 -> sr_returned_date_sk
+|     row-size=16B cardinality=287.51K
+|
+06:AGGREGATE [FINALIZE]
+|  output: sum(ss_ext_sales_price), sum(ss_net_profit)
+|  group by: s_store_sk
+|  row-size=36B cardinality=12
+|
+05:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=46B cardinality=2.88M
+|
+|--03:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=4B cardinality=12
+|
+04:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF002 <- d_date_sk
+|  row-size=42B cardinality=2.88M
+|
+|--02:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|     row-size=26B cardinality=7.30K
+|
+01:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_store_sk, RF002 -> ss_sold_date_sk
+   row-size=16B cardinality=2.88M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=154.97MB Threads=36
+Per-Host Resource Estimates: Memory=1.23GB
+PLAN-ROOT SINK
+|
+64:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: CASE valid_tid(32,30,31) WHEN 30 THEN channel WHEN 31 THEN channel WHEN 32 THEN NULL END ASC, CASE valid_tid(32,30,31) WHEN 30 THEN id WHEN 31 THEN NULL WHEN 32 THEN NULL END ASC, aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(sales) WHEN 31 THEN sum(sales) WHEN 32 THEN sum(sales) END) DESC
+|  limit: 100
+|
+38:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(32,30,31) WHEN 30 THEN channel WHEN 31 THEN channel WHEN 32 THEN NULL END ASC, CASE valid_tid(32,30,31) WHEN 30 THEN id WHEN 31 THEN NULL WHEN 32 THEN NULL END ASC, aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(sales) WHEN 31 THEN sum(sales) WHEN 32 THEN sum(sales) END) DESC
+|  row-size=64B cardinality=82
+|
+37:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(sales) WHEN 31 THEN sum(sales) WHEN 32 THEN sum(sales) END), aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(`returns`) WHEN 31 THEN sum(`returns`) WHEN 32 THEN sum(`returns`) END), aggif(valid_tid(32,30,31) IN (30, 31, 32), CASE valid_tid(32,30,31) WHEN 30 THEN sum(profit) WHEN 31 THEN sum(profit) WHEN 32 THEN sum(profit) END)
+|  group by: CASE valid_tid(32,30,31) WHEN 30 THEN channel WHEN 31 THEN channel WHEN 32 THEN NULL END, CASE valid_tid(32,30,31) WHEN 30 THEN id WHEN 31 THEN NULL WHEN 32 THEN NULL END, CASE valid_tid(32,30,31) WHEN 30 THEN 30 WHEN 31 THEN 31 WHEN 32 THEN 32 END
+|  row-size=68B cardinality=82
+|
+63:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: NULL, NULL
+|  row-size=192B cardinality=82
+|
+62:EXCHANGE [HASH(CASE valid_tid(32,30,31) WHEN 30 THEN murmur_hash(channel) WHEN 31 THEN murmur_hash(channel) WHEN 32 THEN murmur_hash(NULL) END,CASE valid_tid(32,30,31) WHEN 30 THEN murmur_hash(id) WHEN 31 THEN murmur_hash(NULL) WHEN 32 THEN murmur_hash(NULL) END)]
+|
+36:AGGREGATE [STREAMING]
+|  Class 0
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: NULL, NULL
+|  row-size=192B cardinality=82
+|
+00:UNION
+|  row-size=64B cardinality=78
+|
+|--35:HASH JOIN [LEFT OUTER JOIN, PARTITIONED]
+|  |  hash predicates: wp_web_page_sk = wp_web_page_sk
+|  |  row-size=72B cardinality=60
+|  |
+|  |--61:AGGREGATE [FINALIZE]
+|  |  |  output: sum:merge(wr_return_amt), sum:merge(wr_net_loss)
+|  |  |  group by: wp_web_page_sk
+|  |  |  row-size=36B cardinality=60
+|  |  |
+|  |  60:EXCHANGE [HASH(wp_web_page_sk)]
+|  |  |
+|  |  34:AGGREGATE [STREAMING]
+|  |  |  output: sum(wr_return_amt), sum(wr_net_loss)
+|  |  |  group by: wp_web_page_sk
+|  |  |  row-size=36B cardinality=60
+|  |  |
+|  |  33:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  |  hash predicates: wr_web_page_sk = wp_web_page_sk
+|  |  |  runtime filters: RF016 <- wp_web_page_sk
+|  |  |  row-size=46B cardinality=71.76K
+|  |  |
+|  |  |--59:EXCHANGE [BROADCAST]
+|  |  |  |
+|  |  |  31:SCAN HDFS [tpcds.web_page]
+|  |  |     HDFS partitions=1/1 files=1 size=5.64KB
+|  |  |     row-size=4B cardinality=60
+|  |  |
+|  |  32:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  |  hash predicates: wr_returned_date_sk = d_date_sk
+|  |  |  runtime filters: RF018 <- d_date_sk
+|  |  |  row-size=42B cardinality=71.76K
+|  |  |
+|  |  |--58:EXCHANGE [BROADCAST]
+|  |  |  |
+|  |  |  30:SCAN HDFS [tpcds.date_dim]
+|  |  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |  |     row-size=26B cardinality=7.30K
+|  |  |
+|  |  29:SCAN HDFS [tpcds.web_returns]
+|  |     HDFS partitions=1/1 files=1 size=9.35MB
+|  |     runtime filters: RF016 -> wr_web_page_sk, RF018 -> wr_returned_date_sk
+|  |     row-size=16B cardinality=71.76K
+|  |
+|  57:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(ws_ext_sales_price), sum:merge(ws_net_profit)
+|  |  group by: wp_web_page_sk
+|  |  row-size=36B cardinality=60
+|  |
+|  56:EXCHANGE [HASH(wp_web_page_sk)]
+|  |
+|  28:AGGREGATE [STREAMING]
+|  |  output: sum(ws_ext_sales_price), sum(ws_net_profit)
+|  |  group by: wp_web_page_sk
+|  |  row-size=36B cardinality=60
+|  |
+|  27:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ws_web_page_sk = wp_web_page_sk
+|  |  runtime filters: RF012 <- wp_web_page_sk
+|  |  row-size=46B cardinality=719.38K
+|  |
+|  |--55:EXCHANGE [BROADCAST]
+|  |  |
+|  |  25:SCAN HDFS [tpcds.web_page]
+|  |     HDFS partitions=1/1 files=1 size=5.64KB
+|  |     row-size=4B cardinality=60
+|  |
+|  26:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ws_sold_date_sk = d_date_sk
+|  |  runtime filters: RF014 <- d_date_sk
+|  |  row-size=42B cardinality=719.38K
+|  |
+|  |--54:EXCHANGE [BROADCAST]
+|  |  |
+|  |  24:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  23:SCAN HDFS [tpcds.web_sales]
+|     HDFS partitions=1/1 files=1 size=140.07MB
+|     runtime filters: RF012 -> ws_web_page_sk, RF014 -> ws_sold_date_sk
+|     row-size=16B cardinality=719.38K
+|
+|--22:NESTED LOOP JOIN [CROSS JOIN, BROADCAST]
+|  |  row-size=68B cardinality=6
+|  |
+|  |--53:EXCHANGE [BROADCAST]
+|  |  |
+|  |  52:AGGREGATE [FINALIZE]
+|  |  |  output: sum:merge(cr_return_amount), sum:merge(cr_net_loss)
+|  |  |  row-size=32B cardinality=1
+|  |  |
+|  |  51:EXCHANGE [UNPARTITIONED]
+|  |  |
+|  |  21:AGGREGATE
+|  |  |  output: sum(cr_return_amount), sum(cr_net_loss)
+|  |  |  row-size=32B cardinality=1
+|  |  |
+|  |  20:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  |  hash predicates: cr_returned_date_sk = d_date_sk
+|  |  |  runtime filters: RF010 <- d_date_sk
+|  |  |  row-size=38B cardinality=144.07K
+|  |  |
+|  |  |--50:EXCHANGE [BROADCAST]
+|  |  |  |
+|  |  |  19:SCAN HDFS [tpcds.date_dim]
+|  |  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |  |     row-size=26B cardinality=7.30K
+|  |  |
+|  |  18:SCAN HDFS [tpcds.catalog_returns]
+|  |     HDFS partitions=1/1 files=1 size=20.39MB
+|  |     runtime filters: RF010 -> cr_returned_date_sk
+|  |     row-size=12B cardinality=144.07K
+|  |
+|  49:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(cs_ext_sales_price), sum:merge(cs_net_profit)
+|  |  group by: cs_call_center_sk
+|  |  row-size=36B cardinality=6
+|  |
+|  48:EXCHANGE [HASH(cs_call_center_sk)]
+|  |
+|  17:AGGREGATE [STREAMING]
+|  |  output: sum(cs_ext_sales_price), sum(cs_net_profit)
+|  |  group by: cs_call_center_sk
+|  |  row-size=36B cardinality=6
+|  |
+|  16:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: cs_sold_date_sk = d_date_sk
+|  |  runtime filters: RF008 <- d_date_sk
+|  |  row-size=42B cardinality=1.44M
+|  |
+|  |--47:EXCHANGE [BROADCAST]
+|  |  |
+|  |  15:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  14:SCAN HDFS [tpcds.catalog_sales]
+|     HDFS partitions=1/1 files=1 size=282.20MB
+|     runtime filters: RF008 -> cs_sold_date_sk
+|     row-size=16B cardinality=1.44M
+|
+13:HASH JOIN [LEFT OUTER JOIN, PARTITIONED]
+|  hash predicates: s_store_sk = s_store_sk
+|  row-size=72B cardinality=12
+|
+|--46:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(sr_return_amt), sum:merge(sr_net_loss)
+|  |  group by: s_store_sk
+|  |  row-size=36B cardinality=12
+|  |
+|  45:EXCHANGE [HASH(s_store_sk)]
+|  |
+|  12:AGGREGATE [STREAMING]
+|  |  output: sum(sr_return_amt), sum(sr_net_loss)
+|  |  group by: s_store_sk
+|  |  row-size=36B cardinality=12
+|  |
+|  11:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: sr_store_sk = s_store_sk
+|  |  runtime filters: RF004 <- s_store_sk
+|  |  row-size=46B cardinality=287.51K
+|  |
+|  |--44:EXCHANGE [BROADCAST]
+|  |  |
+|  |  09:SCAN HDFS [tpcds.store]
+|  |     HDFS partitions=1/1 files=1 size=3.08KB
+|  |     row-size=4B cardinality=12
+|  |
+|  10:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: sr_returned_date_sk = d_date_sk
+|  |  runtime filters: RF006 <- d_date_sk
+|  |  row-size=42B cardinality=287.51K
+|  |
+|  |--43:EXCHANGE [BROADCAST]
+|  |  |
+|  |  08:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  07:SCAN HDFS [tpcds.store_returns]
+|     HDFS partitions=1/1 files=1 size=31.19MB
+|     runtime filters: RF004 -> sr_store_sk, RF006 -> sr_returned_date_sk
+|     row-size=16B cardinality=287.51K
+|
+42:AGGREGATE [FINALIZE]
+|  output: sum:merge(ss_ext_sales_price), sum:merge(ss_net_profit)
+|  group by: s_store_sk
+|  row-size=36B cardinality=12
+|
+41:EXCHANGE [HASH(s_store_sk)]
+|
+06:AGGREGATE [STREAMING]
+|  output: sum(ss_ext_sales_price), sum(ss_net_profit)
+|  group by: s_store_sk
+|  row-size=36B cardinality=12
+|
+05:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=46B cardinality=2.88M
+|
+|--40:EXCHANGE [BROADCAST]
+|  |
+|  03:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=4B cardinality=12
+|
+04:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF002 <- d_date_sk
+|  row-size=42B cardinality=2.88M
+|
+|--39:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|     row-size=26B cardinality=7.30K
+|
+01:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_store_sk, RF002 -> ss_sold_date_sk
+   row-size=16B cardinality=2.88M
+====
diff --git a/testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test b/testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test
new file mode 100644
index 0000000..aba927d
--- /dev/null
+++ b/testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test
@@ -0,0 +1,155 @@
+====
+---- QUERY
+# Basic ROLLUP.
+select int_col, string_col, bool_col, count(*)
+from alltypesagg
+group by rollup(1, 2, 3)
+order by count(*) desc, 1, 2, 3
+limit 20
+---- RESULTS
+NULL,'NULL',NULL,11000
+10,'10',true,20
+10,'10',NULL,20
+10,'NULL',NULL,20
+20,'20',true,20
+20,'20',NULL,20
+20,'NULL',NULL,20
+30,'30',true,20
+30,'30',NULL,20
+30,'NULL',NULL,20
+40,'40',true,20
+40,'40',NULL,20
+40,'NULL',NULL,20
+50,'50',true,20
+50,'50',NULL,20
+50,'NULL',NULL,20
+60,'60',true,20
+60,'60',NULL,20
+60,'NULL',NULL,20
+70,'70',true,20
+---- TYPES
+INT,STRING,BOOLEAN,BIGINT
+====
+---- QUERY
+# Basic CUBE
+select int_col, string_col, bool_col, count(*)
+from alltypesagg
+group by cube(1, 2, 3)
+order by count(*) desc, 1, 2, 3
+limit 20
+---- RESULTS
+NULL,'NULL',NULL,11000
+NULL,'NULL',true,6000
+NULL,'NULL',false,5000
+10,'10',true,20
+10,'10',NULL,20
+10,'NULL',true,20
+10,'NULL',NULL,20
+20,'20',true,20
+20,'20',NULL,20
+20,'NULL',true,20
+20,'NULL',NULL,20
+30,'30',true,20
+30,'30',NULL,20
+30,'NULL',true,20
+30,'NULL',NULL,20
+40,'40',true,20
+40,'40',NULL,20
+40,'NULL',true,20
+40,'NULL',NULL,20
+50,'50',true,20
+---- TYPES
+INT,STRING,BOOLEAN,BIGINT
+====
+---- QUERY
+# Basic GROUPING SETS
+select int_col, string_col, bool_col, count(*)
+from alltypesagg
+group by grouping sets((int_col), (string_col), (int_col, bool_col))
+order by count(*) desc, 1, 2, 3
+limit 20
+---- RESULTS
+10,'NULL',true,20
+10,'NULL',NULL,20
+20,'NULL',true,20
+20,'NULL',NULL,20
+30,'NULL',true,20
+30,'NULL',NULL,20
+40,'NULL',true,20
+40,'NULL',NULL,20
+50,'NULL',true,20
+50,'NULL',NULL,20
+60,'NULL',true,20
+60,'NULL',NULL,20
+70,'NULL',true,20
+70,'NULL',NULL,20
+80,'NULL',true,20
+80,'NULL',NULL,20
+90,'NULL',true,20
+90,'NULL',NULL,20
+100,'NULL',true,20
+100,'NULL',NULL,20
+---- TYPES
+INT,STRING,BOOLEAN,BIGINT
+====
+---- QUERY
+# Output should contain a row for both the NULL value in
+# int_col and the rollup of all values in int_col.
+select year, int_col, count(*)
+from alltypesagg
+group by rollup(year, int_col)
+order by 2 desc, 1, 3
+limit 10
+---- RESULTS
+2010,NULL,20
+2010,NULL,11000
+NULL,NULL,11000
+2010,999,10
+2010,998,10
+2010,997,10
+2010,996,10
+2010,995,10
+2010,994,10
+2010,993,10
+---- TYPES
+INT, INT, BIGINT
+====
+---- QUERY
+# Output should contain a row for both the NULL value in
+# int_col and the rollup of all values in int_col.
+select year, int_col, count(*)
+from alltypesagg
+group by rollup(year, int_col)
+having int_col is NULL
+---- RESULTS
+NULL,NULL,11000
+2010,NULL,20
+2010,NULL,11000
+---- TYPES
+INT, INT, BIGINT
+====
+---- QUERY
+# Correlated subquery with rollup. Results are the same as
+# if rollup was not present.
+select id
+from functional.alltypesagg a
+where exists
+  (select id
+   from functional.alltypestiny b
+   where a.tinyint_col = b.tinyint_col and a.string_col = b.string_col
+   group by rollup(id, int_col, bool_col))
+  and tinyint_col < 10
+---- RESULTS
+1
+1001
+2001
+3001
+4001
+5001
+6001
+7001
+8001
+9001
+---- TYPES
+INT
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q18.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q18.test
new file mode 100644
index 0000000..3e828ab
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q18.test
@@ -0,0 +1,137 @@
+====
+---- QUERY: TPCDS-Q18
+select  i_item_id,
+        ca_country,
+        ca_state,
+        ca_county,
+        avg( cast(cs_quantity as decimal(12,2))) agg1,
+        avg( cast(cs_list_price as decimal(12,2))) agg2,
+        avg( cast(cs_coupon_amt as decimal(12,2))) agg3,
+        avg( cast(cs_sales_price as decimal(12,2))) agg4,
+        avg( cast(cs_net_profit as decimal(12,2))) agg5,
+        avg( cast(c_birth_year as decimal(12,2))) agg6,
+        avg( cast(cd1.cd_dep_count as decimal(12,2))) agg7
+ from catalog_sales, customer_demographics cd1,
+      customer_demographics cd2, customer, customer_address, date_dim, item
+ where cs_sold_date_sk = d_date_sk and
+       cs_item_sk = i_item_sk and
+       cs_bill_cdemo_sk = cd1.cd_demo_sk and
+       cs_bill_customer_sk = c_customer_sk and
+       cd1.cd_gender = 'M' and
+       cd1.cd_education_status = 'College' and
+       c_current_cdemo_sk = cd2.cd_demo_sk and
+       c_current_addr_sk = ca_address_sk and
+       c_birth_month in (9,5,12,4,1,10) and
+       d_year = 2001 and
+       ca_state in ('ND','WI','AL'
+                   ,'NC','OK','MS','TN')
+ group by rollup (i_item_id, ca_country, ca_state, ca_county)
+ order by ca_country,
+        ca_state,
+        ca_county,
+        i_item_id
+ limit 100;
+---- RESULTS
+'AAAAAAAAAMJBAAAA','United States','AL','Bullock County',20.000000,192.920000,0.000000,167.840000,1978.800000,1930.000000,6.000000
+'AAAAAAAABHACAAAA','United States','AL','Bullock County',20.000000,117.530000,0.000000,62.290000,-665.400000,1930.000000,6.000000
+'AAAAAAAAGMBDAAAA','United States','AL','Bullock County',87.000000,281.890000,0.000000,253.700000,13405.830000,1930.000000,6.000000
+'AAAAAAAAMFNBAAAA','United States','AL','Bullock County',58.000000,12.070000,0.000000,6.510000,-71.340000,1930.000000,6.000000
+'AAAAAAAANGBCAAAA','United States','AL','Bullock County',62.000000,63.470000,0.000000,0.000000,-1847.600000,1930.000000,6.000000
+'AAAAAAAANKGCAAAA','United States','AL','Bullock County',50.000000,196.520000,0.000000,51.090000,-1557.000000,1930.000000,6.000000
+'AAAAAAAAOJBCAAAA','United States','AL','Bullock County',81.000000,28.570000,0.000000,15.990000,499.770000,1930.000000,6.000000
+'AAAAAAAAPAKDAAAA','United States','AL','Bullock County',48.000000,169.210000,0.000000,69.370000,-345.600000,1930.000000,6.000000
+'AAAAAAAAPNMAAAAA','United States','AL','Bullock County',80.000000,228.040000,0.000000,54.720000,-3068.800000,1930.000000,6.000000
+'AAAAAAAAABLCAAAA','United States','AL','Dale County',97.000000,14.950000,0.000000,13.600000,445.230000,1944.000000,0.000000
+'AAAAAAAAACGCAAAA','United States','AL','Dale County',88.000000,152.900000,451.890000,18.340000,-3954.290000,1944.000000,0.000000
+'AAAAAAAAAHHDAAAA','United States','AL','Dale County',21.000000,62.600000,0.000000,5.000000,-820.890000,1944.000000,0.000000
+'AAAAAAAAAPHBAAAA','United States','AL','Dale County',14.000000,168.640000,0.000000,57.330000,-554.260000,1944.000000,0.000000
+'AAAAAAAACHOAAAAA','United States','AL','Dale County',54.000000,115.260000,0.000000,101.420000,2300.940000,1944.000000,0.000000
+'AAAAAAAADLHBAAAA','United States','AL','Dale County',42.000000,3.450000,0.000000,0.000000,-54.180000,1944.000000,0.000000
+'AAAAAAAAELDBAAAA','United States','AL','Dale County',22.000000,9.120000,0.000000,2.370000,-84.480000,1944.000000,0.000000
+'AAAAAAAAHHACAAAA','United States','AL','Dale County',11.000000,56.970000,0.000000,21.640000,-158.620000,1944.000000,0.000000
+'AAAAAAAAIKAEAAAA','United States','AL','Dale County',41.000000,86.200000,38.160000,31.030000,-1308.750000,1944.000000,0.000000
+'AAAAAAAAIPACAAAA','United States','AL','Dale County',94.000000,45.130000,144.530000,4.960000,-2102.550000,1944.000000,0.000000
+'AAAAAAAALHPBAAAA','United States','AL','Dale County',64.000000,88.570000,0.000000,56.680000,-871.680000,1944.000000,0.000000
+'AAAAAAAAOHMCAAAA','United States','AL','Dale County',78.000000,132.430000,0.000000,11.910000,-3074.760000,1944.000000,0.000000
+'AAAAAAAAOMCEAAAA','United States','AL','Dale County',24.000000,36.350000,0.000000,32.710000,133.920000,1944.000000,0.000000
+'AAAAAAAAPFLDAAAA','United States','AL','Dale County',93.000000,13.250000,0.000000,7.420000,233.430000,1944.000000,0.000000
+'AAAAAAAAAFDDAAAA','United States','AL','Jefferson County',75.000000,88.760000,0.000000,50.590000,-192.000000,1933.000000,0.000000
+'AAAAAAAACIFBAAAA','United States','AL','Jefferson County',64.000000,182.430000,3992.830000,138.640000,423.810000,1933.000000,0.000000
+'AAAAAAAACPMAAAAA','United States','AL','Jefferson County',63.000000,57.100000,0.000000,53.670000,2023.560000,1933.000000,0.000000
+'AAAAAAAAEJOCAAAA','United States','AL','Jefferson County',83.000000,100.530000,0.000000,15.070000,-2457.630000,1933.000000,0.000000
+'AAAAAAAAFLKCAAAA','United States','AL','Jefferson County',88.000000,74.080000,0.000000,40.740000,566.720000,1933.000000,0.000000
+'AAAAAAAAKBDCAAAA','United States','AL','Jefferson County',30.000000,48.600000,0.000000,45.680000,481.200000,1933.000000,0.000000
+'AAAAAAAAMIPCAAAA','United States','AL','Jefferson County',24.000000,212.110000,0.000000,16.960000,-1896.480000,1933.000000,0.000000
+'AAAAAAAAAFPCAAAA','United States','AL','Lamar County',56.000000,121.720000,0.000000,26.770000,-2927.120000,1972.000000,6.000000
+'AAAAAAAAAHIBAAAA','United States','AL','Lamar County',68.000000,281.940000,0.000000,8.450000,-5946.600000,1972.000000,6.000000
+'AAAAAAAAAIFAAAAA','United States','AL','Lamar County',52.000000,205.590000,0.000000,98.680000,271.960000,1972.000000,6.000000
+'AAAAAAAABIDBAAAA','United States','AL','Lamar County',29.000000,94.220000,0.000000,91.390000,887.400000,1972.000000,6.000000
+'AAAAAAAABJADAAAA','United States','AL','Lamar County',62.000000,31.210000,142.730000,12.790000,-407.470000,1972.000000,6.000000
+'AAAAAAAACCNDAAAA','United States','AL','Lamar County',82.000000,63.880000,0.000000,49.180000,1373.500000,1972.000000,6.000000
+'AAAAAAAACJAAAAAA','United States','AL','Lamar County',57.000000,133.260000,0.000000,63.960000,239.400000,1972.000000,6.000000
+'AAAAAAAACJFAAAAA','United States','AL','Lamar County',4.000000,106.250000,0.000000,97.750000,101.880000,1972.000000,6.000000
+'AAAAAAAAGBADAAAA','United States','AL','Lamar County',69.000000,93.170000,0.000000,0.000000,-3716.340000,1972.000000,6.000000
+'AAAAAAAAGDFDAAAA','United States','AL','Lamar County',58.000000,160.040000,0.000000,108.820000,976.720000,1972.000000,6.000000
+'AAAAAAAAIBHDAAAA','United States','AL','Lamar County',89.000000,141.980000,0.000000,53.950000,-377.360000,1974.000000,6.000000
+'AAAAAAAAIGECAAAA','United States','AL','Lamar County',23.000000,36.680000,0.000000,2.200000,-454.710000,1974.000000,6.000000
+'AAAAAAAAJHJAAAAA','United States','AL','Lamar County',11.000000,124.840000,0.000000,92.380000,383.350000,1972.000000,6.000000
+'AAAAAAAAJLDCAAAA','United States','AL','Lamar County',87.000000,7.040000,154.870000,3.870000,-370.630000,1972.000000,6.000000
+'AAAAAAAAMKBDAAAA','United States','AL','Lamar County',7.000000,55.570000,0.000000,32.780000,92.960000,1974.000000,6.000000
+'AAAAAAAAOFHDAAAA','United States','AL','Lamar County',77.000000,85.960000,0.000000,85.100000,3354.890000,1974.000000,6.000000
+'AAAAAAAAOHKAAAAA','United States','AL','Lamar County',87.000000,140.150000,0.000000,9.810000,-5530.590000,1972.000000,6.000000
+'AAAAAAAABEFDAAAA','United States','AL','Lauderdale County',50.000000,16.470000,0.000000,15.480000,-49.500000,1935.000000,5.000000
+'AAAAAAAACMDDAAAA','United States','AL','Lauderdale County',37.000000,197.130000,0.000000,80.820000,-167.240000,1935.000000,5.000000
+'AAAAAAAADEFBAAAA','United States','AL','Lauderdale County',94.000000,40.190000,0.000000,17.680000,-509.480000,1935.000000,5.000000
+'AAAAAAAAEEODAAAA','United States','AL','Lauderdale County',19.000000,82.240000,0.000000,37.000000,2.280000,1935.000000,5.000000
+'AAAAAAAAGFEEAAAA','United States','AL','Lauderdale County',70.000000,89.240000,0.000000,33.910000,-1176.000000,1935.000000,5.000000
+'AAAAAAAAGMKBAAAA','United States','AL','Lauderdale County',69.000000,34.440000,76.870000,6.190000,-701.320000,1935.000000,5.000000
+'AAAAAAAAJGKDAAAA','United States','AL','Lauderdale County',47.000000,6.750000,0.000000,4.520000,30.080000,1935.000000,5.000000
+'AAAAAAAAKLHBAAAA','United States','AL','Lauderdale County',45.000000,62.560000,0.000000,45.040000,717.300000,1935.000000,5.000000
+'AAAAAAAALEKDAAAA','United States','AL','Lauderdale County',2.000000,68.050000,0.000000,19.730000,-41.560000,1935.000000,5.000000
+'AAAAAAAAMLLBAAAA','United States','AL','Lauderdale County',79.000000,14.010000,0.000000,10.640000,417.910000,1935.000000,5.000000
+'AAAAAAAAOIDBAAAA','United States','AL','Lauderdale County',49.000000,117.910000,0.000000,68.380000,-172.480000,1935.000000,5.000000
+'AAAAAAAAOKIDAAAA','United States','AL','Lauderdale County',58.000000,243.790000,0.000000,138.960000,2470.800000,1935.000000,5.000000
+'AAAAAAAAAHFCAAAA','United States','AL','Lee County',90.000000,147.450000,0.000000,106.160000,2341.800000,1939.000000,1.000000
+'AAAAAAAABPKCAAAA','United States','AL','Lee County',96.000000,167.270000,0.000000,70.250000,-2592.000000,1939.000000,1.000000
+'AAAAAAAACBKBAAAA','United States','AL','Lee County',64.000000,258.600000,910.270000,142.230000,2638.530000,1939.000000,1.000000
+'AAAAAAAACIICAAAA','United States','AL','Lee County',9.000000,11.290000,5.920000,5.980000,6.050000,1939.000000,1.000000
+'AAAAAAAAEDJAAAAA','United States','AL','Lee County',19.000000,121.960000,0.000000,47.560000,-26.980000,1939.000000,1.000000
+'AAAAAAAAJJDEAAAA','United States','AL','Lee County',32.000000,107.320000,0.000000,10.730000,-910.080000,1939.000000,1.000000
+'AAAAAAAAMOACAAAA','United States','AL','Lee County',51.000000,16.480000,0.000000,1.640000,-375.870000,1939.000000,1.000000
+'AAAAAAAAOEEDAAAA','United States','AL','Lee County',17.000000,74.280000,336.640000,63.880000,51.640000,1939.000000,1.000000
+'AAAAAAAAOOKDAAAA','United States','AL','Lee County',95.000000,156.530000,0.000000,95.480000,1989.300000,1939.000000,1.000000
+'AAAAAAAAPIFDAAAA','United States','AL','Lee County',42.000000,58.660000,118.980000,40.470000,-811.560000,1939.000000,1.000000
+'AAAAAAAADEGDAAAA','United States','AL','Marengo County',19.000000,36.900000,0.000000,1.470000,-551.570000,1960.000000,2.000000
+'AAAAAAAADPMBAAAA','United States','AL','Marengo County',12.000000,86.800000,0.000000,49.470000,-139.920000,1960.000000,2.000000
+'AAAAAAAAFBFCAAAA','United States','AL','Marengo County',12.000000,22.330000,48.330000,4.240000,-218.970000,1960.000000,2.000000
+'AAAAAAAAPDCCAAAA','United States','AL','Marengo County',2.000000,183.040000,0.000000,91.520000,38.340000,1960.000000,2.000000
+'AAAAAAAAAAHBAAAA','United States','AL','Marshall County',55.000000,40.870000,0.000000,20.020000,-261.250000,1978.000000,3.000000
+'AAAAAAAAAALDAAAA','United States','AL','Marshall County',80.000000,100.760000,59.200000,1.000000,-6228.000000,1978.000000,3.000000
+'AAAAAAAAADHAAAAA','United States','AL','Marshall County',7.000000,260.420000,0.000000,190.100000,710.640000,1978.000000,3.000000
+'AAAAAAAACAGCAAAA','United States','AL','Marshall County',92.000000,183.760000,0.000000,121.280000,3438.040000,1978.000000,3.000000
+'AAAAAAAADAOCAAAA','United States','AL','Marshall County',68.000000,199.290000,3428.280000,109.600000,-2717.680000,1978.000000,3.000000
+'AAAAAAAAGGPCAAAA','United States','AL','Marshall County',64.000000,263.920000,0.000000,160.990000,4313.600000,1978.000000,3.000000
+'AAAAAAAAHBEEAAAA','United States','AL','Marshall County',25.000000,46.470000,0.000000,10.220000,-209.250000,1978.000000,3.000000
+'AAAAAAAAFJODAAAA','United States','AL','Morgan County',39.000000,45.160000,0.000000,8.120000,-1377.090000,1985.000000,4.000000
+'AAAAAAAAGDCDAAAA','United States','AL','Morgan County',99.000000,8.690000,0.000000,0.000000,-354.420000,1985.000000,4.000000
+'AAAAAAAAGKDAAAAA','United States','AL','Morgan County',68.000000,135.270000,0.000000,132.560000,2840.360000,1985.000000,4.000000
+'AAAAAAAAIOFBAAAA','United States','AL','Morgan County',37.000000,64.090000,0.000000,1.920000,-827.320000,1985.000000,4.000000
+'AAAAAAAALBJBAAAA','United States','AL','Morgan County',73.000000,132.770000,0.000000,132.770000,5891.100000,1985.000000,4.000000
+'AAAAAAAAPJICAAAA','United States','AL','Morgan County',85.000000,86.390000,0.000000,60.470000,2237.200000,1985.000000,4.000000
+'AAAAAAAADOCAAAAA','United States','AL','Perry County',44.000000,132.900000,0.000000,53.160000,-584.760000,1957.000000,6.000000
+'AAAAAAAADPAEAAAA','United States','AL','Perry County',95.000000,180.400000,0.000000,151.530000,5279.150000,1957.000000,6.000000
+'AAAAAAAAEFNDAAAA','United States','AL','Perry County',25.000000,3.800000,0.000000,2.200000,6.000000,1957.000000,6.000000
+'AAAAAAAAGHGDAAAA','United States','AL','Perry County',43.000000,82.190000,0.000000,32.870000,-2051.530000,1957.000000,6.000000
+'AAAAAAAAHACEAAAA','United States','AL','Perry County',65.000000,191.050000,0.000000,32.470000,-3173.950000,1957.000000,6.000000
+'AAAAAAAAHHHBAAAA','United States','AL','Perry County',6.000000,36.220000,0.000000,7.960000,-36.480000,1957.000000,6.000000
+'AAAAAAAAIHGCAAAA','United States','AL','Perry County',12.000000,48.660000,0.000000,36.980000,-51.120000,1957.000000,6.000000
+'AAAAAAAAKAHAAAAA','United States','AL','Perry County',99.000000,7.510000,0.000000,0.450000,-292.050000,1957.000000,6.000000
+'AAAAAAAALNBAAAAA','United States','AL','Perry County',49.000000,150.130000,0.000000,123.100000,1285.760000,1957.000000,6.000000
+'AAAAAAAAMFDDAAAA','United States','AL','Perry County',87.000000,121.490000,0.000000,51.020000,-1244.100000,1957.000000,6.000000
+'AAAAAAAAOMFBAAAA','United States','AL','Perry County',55.000000,79.970000,0.000000,38.380000,-461.450000,1957.000000,6.000000
+'AAAAAAAAADFDAAAA','United States','AL','Shelby County',75.000000,231.300000,0.000000,83.260000,-1106.250000,1948.000000,3.000000
+'AAAAAAAAADKAAAAA','United States','AL','Shelby County',61.000000,67.390000,616.250000,13.470000,-3432.620000,1973.000000,1.000000
+'AAAAAAAAAOCEAAAA','United States','AL','Shelby County',67.000000,51.710000,0.000000,11.890000,-1732.620000,1948.000000,3.000000
+---- TYPES
+STRING, STRING, STRING, STRING, DECIMAL, DECIMAL, DECIMAL, DECIMAL, DECIMAL, DECIMAL, DECIMAL
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q22.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q22.test
new file mode 100644
index 0000000..737180e
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q22.test
@@ -0,0 +1,125 @@
+====
+---- QUERY: TPCDS-Q22
+select  i_product_name
+             ,i_brand
+             ,i_class
+             ,i_category
+             ,avg(inv_quantity_on_hand) qoh
+       from inventory
+           ,date_dim
+           ,item
+           ,warehouse
+       where inv_date_sk=d_date_sk
+              and inv_item_sk=i_item_sk
+              and inv_warehouse_sk = w_warehouse_sk
+              and d_month_seq between 1212 and 1212 + 11
+       group by rollup(i_product_name
+                       ,i_brand
+                       ,i_class
+                       ,i_category)
+order by qoh, i_product_name, i_brand, i_class, i_category
+limit 100;
+---- RESULTS
+'n stbarcallyationought','amalgscholar #2','rock','Music',432.056680162
+'n stbarcallyationought','amalgscholar #2','rock','NULL',432.056680162
+'n stbarcallyationought','amalgscholar #2','NULL','NULL',432.056680162
+'n stbarcallyationought','NULL','NULL','NULL',432.056680162
+'antiablen stcally','univbrand #6','jewelry boxes','Jewelry',436.257028112
+'antiablen stcally','univbrand #6','jewelry boxes','NULL',436.257028112
+'antiablen stcally','univbrand #6','NULL','NULL',436.257028112
+'antiablen stcally','NULL','NULL','NULL',436.257028112
+'antiantiableeseought','exportiexporti #2','toddlers','Children',436.558704453
+'antiantiableeseought','exportiexporti #2','toddlers','NULL',436.558704453
+'antiantiableeseought','exportiexporti #2','NULL','NULL',436.558704453
+'antiantiableeseought','NULL','NULL','NULL',436.558704453
+'n stcallyn stationought','exportischolar #2','pop','Music',436.852589641
+'n stcallyn stationought','exportischolar #2','pop','NULL',436.852589641
+'n stcallyn stationought','exportischolar #2','NULL','NULL',436.852589641
+'n stcallyn stationought','NULL','NULL','NULL',436.852589641
+'ationantiableationought','amalgscholar #2','rock','Music',438.133064516
+'ationantiableationought','amalgscholar #2','rock','NULL',438.133064516
+'ationantiableationought','amalgscholar #2','NULL','NULL',438.133064516
+'ationantiableationought','NULL','NULL','NULL',438.133064516
+'n stcallyesepriought','edu packscholar #2','classical','Music',438.56097561
+'n stcallyesepriought','edu packscholar #2','classical','NULL',438.56097561
+'n stcallyesepriought','edu packscholar #2','NULL','NULL',438.56097561
+'n stcallyesepriought','NULL','NULL','NULL',438.56097561
+'oughteingoughtn st','importobrand #6','costume','Jewelry',441.268595041
+'oughteingoughtn st','importobrand #6','costume','NULL',441.268595041
+'oughteingoughtn st','importobrand #6','NULL','NULL',441.268595041
+'oughteingoughtn st','NULL','NULL','NULL',441.268595041
+'prieingeseantiought','amalgunivamalg #4','cooking','Books',441.76446281
+'prieingeseantiought','amalgunivamalg #4','cooking','NULL',441.76446281
+'prieingeseantiought','amalgunivamalg #4','NULL','NULL',441.76446281
+'prieingeseantiought','NULL','NULL','NULL',441.76446281
+'ationationpribarought','scholarmaxi #10','history','Books',442.041322314
+'ationationpribarought','scholarmaxi #10','history','NULL',442.041322314
+'ationationpribarought','scholarmaxi #10','NULL','NULL',442.041322314
+'ationationpribarought','NULL','NULL','NULL',442.041322314
+'n stpribarought','edu packexporti #2','school-uniforms','Children',442.215447154
+'n stpribarought','edu packexporti #2','school-uniforms','NULL',442.215447154
+'n stpribarought','edu packexporti #2','NULL','NULL',442.215447154
+'n stpribarought','NULL','NULL','NULL',442.215447154
+'pricallyantiese','amalgunivamalg #8','cameras','Electronics',442.740890688
+'pricallyantiese','amalgunivamalg #8','cameras','NULL',442.740890688
+'pricallyantiese','amalgunivamalg #8','NULL','NULL',442.740890688
+'pricallyantiese','NULL','NULL','NULL',442.740890688
+'n stoughtoughtbarought','amalgscholar #2','rock','Music',443.9625
+'n stoughtoughtbarought','amalgscholar #2','rock','NULL',443.9625
+'n stoughtoughtbarought','amalgscholar #2','NULL','NULL',443.9625
+'n stoughtoughtbarought','NULL','NULL','NULL',443.9625
+'antiableableought','importoamalgamalg #2','monitors','Electronics',444.646090535
+'antiableableought','importoamalgamalg #2','monitors','NULL',444.646090535
+'antiableableought','importoamalgamalg #2','NULL','NULL',444.646090535
+'antiableableought','NULL','NULL','NULL',444.646090535
+'prieseoughtable','exportischolar #2','pop','Music',445.270491803
+'prieseoughtable','exportischolar #2','pop','NULL',445.270491803
+'prieseoughtable','exportischolar #2','NULL','NULL',445.270491803
+'prieseoughtable','NULL','NULL','NULL',445.270491803
+'ationpribarought','amalgunivamalg #1','cameras','Electronics',445.530120482
+'ationpribarought','amalgunivamalg #1','cameras','NULL',445.530120482
+'ationpribarought','amalgunivamalg #1','NULL','NULL',445.530120482
+'ationpribarought','NULL','NULL','NULL',445.530120482
+'n stn stesebarought','importoexporti #2','infants','Children',445.907630522
+'n stn stesebarought','importoexporti #2','infants','NULL',445.907630522
+'n stn stesebarought','importoexporti #2','NULL','NULL',445.907630522
+'n stn stesebarought','NULL','NULL','NULL',445.907630522
+'prioughtanticallyought','importoexporti #2','infants','Children',445.959677419
+'prioughtanticallyought','importoexporti #2','infants','NULL',445.959677419
+'prioughtanticallyought','importoexporti #2','NULL','NULL',445.959677419
+'prioughtanticallyought','NULL','NULL','NULL',445.959677419
+'oughtationpriantiought','exportiedu pack #2','kids','Shoes',445.963855422
+'oughtationpriantiought','exportiedu pack #2','kids','NULL',445.963855422
+'oughtationpriantiought','exportiedu pack #2','NULL','NULL',445.963855422
+'oughtationpriantiought','NULL','NULL','NULL',445.963855422
+'priantioughtantiought','importomaxi #10','business','Books',446.179591837
+'priantioughtantiought','importomaxi #10','business','NULL',446.179591837
+'priantioughtantiought','importomaxi #10','NULL','NULL',446.179591837
+'priantioughtantiought','NULL','NULL','NULL',446.179591837
+'n stpribarbarought','edu packbrand #8','estate','Jewelry',446.316
+'n stpribarbarought','edu packbrand #8','estate','NULL',446.316
+'n stpribarbarought','edu packbrand #8','NULL','NULL',446.316
+'n stpribarbarought','NULL','NULL','NULL',446.316
+'ationn steingantiought','edu packcorp #2','bracelets','Jewelry',446.586065574
+'ationn steingantiought','edu packcorp #2','bracelets','NULL',446.586065574
+'ationn steingantiought','edu packcorp #2','NULL','NULL',446.586065574
+'ationn steingantiought','NULL','NULL','NULL',446.586065574
+'antioughtcallyantiought','edu packamalgamalg #10','automotive','Electronics',446.714859438
+'antioughtcallyantiought','edu packamalgamalg #10','automotive','NULL',446.714859438
+'antioughtcallyantiought','edu packamalgamalg #10','NULL','NULL',446.714859438
+'antioughtcallyantiought','NULL','NULL','NULL',446.714859438
+'antiableprin st','edu packimporto #2','sports-apparel','Men',446.796680498
+'antiableprin st','edu packimporto #2','sports-apparel','NULL',446.796680498
+'antiableprin st','edu packimporto #2','NULL','NULL',446.796680498
+'antiableprin st','NULL','NULL','NULL',446.796680498
+'antioughtantically','corpnameless #6','football','Sports',446.963562753
+'antioughtantically','corpnameless #6','football','NULL',446.963562753
+'antioughtantically','corpnameless #6','NULL','NULL',446.963562753
+'antioughtantically','NULL','NULL','NULL',446.963562753
+'antin stn stanti','amalgamalg #2','dresses','Women',447.380165289
+'antin stn stanti','amalgamalg #2','dresses','NULL',447.380165289
+'antin stn stanti','amalgamalg #2','NULL','NULL',447.380165289
+'antin stn stanti','NULL','NULL','NULL',447.380165289
+---- TYPES
+STRING, STRING, STRING, STRING, DOUBLE
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q5.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q5.test
new file mode 100644
index 0000000..fdb26fd
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q5.test
@@ -0,0 +1,231 @@
+====
+---- QUERY: TPCDS-Q5
+with ssr as
+ (select s_store_id,
+        sum(sales_price) as sales,
+        sum(profit) as profit,
+        sum(return_amt) as `returns`,
+        sum(net_loss) as profit_loss
+ from
+  ( select  ss_store_sk as store_sk,
+            ss_sold_date_sk  as date_sk,
+            ss_ext_sales_price as sales_price,
+            ss_net_profit as profit,
+            cast(0 as decimal(7,2)) as return_amt,
+            cast(0 as decimal(7,2)) as net_loss
+    from store_sales
+    union all
+    select sr_store_sk as store_sk,
+           sr_returned_date_sk as date_sk,
+           cast(0 as decimal(7,2)) as sales_price,
+           cast(0 as decimal(7,2)) as profit,
+           sr_return_amt as return_amt,
+           sr_net_loss as net_loss
+    from store_returns
+   ) salesreturns,
+     date_dim,
+     store
+ where date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  INTERVAL 14 days)
+       and store_sk = s_store_sk
+ group by s_store_id)
+ ,
+ csr as
+ (select cp_catalog_page_id,
+        sum(sales_price) as sales,
+        sum(profit) as profit,
+        sum(return_amt) as `returns`,
+        sum(net_loss) as profit_loss
+ from
+  ( select  cs_catalog_page_sk as page_sk,
+            cs_sold_date_sk  as date_sk,
+            cs_ext_sales_price as sales_price,
+            cs_net_profit as profit,
+            cast(0 as decimal(7,2)) as return_amt,
+            cast(0 as decimal(7,2)) as net_loss
+    from catalog_sales
+    union all
+    select cr_catalog_page_sk as page_sk,
+           cr_returned_date_sk as date_sk,
+           cast(0 as decimal(7,2)) as sales_price,
+           cast(0 as decimal(7,2)) as profit,
+           cr_return_amount as return_amt,
+           cr_net_loss as net_loss
+    from catalog_returns
+   ) salesreturns,
+     date_dim,
+     catalog_page
+ where date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  INTERVAL 14 days)
+       and page_sk = cp_catalog_page_sk
+ group by cp_catalog_page_id)
+ ,
+ wsr as
+ (select web_site_id,
+        sum(sales_price) as sales,
+        sum(profit) as profit,
+        sum(return_amt) as `returns`,
+        sum(net_loss) as profit_loss
+ from
+  ( select  ws_web_site_sk as wsr_web_site_sk,
+            ws_sold_date_sk  as date_sk,
+            ws_ext_sales_price as sales_price,
+            ws_net_profit as profit,
+            cast(0 as decimal(7,2)) as return_amt,
+            cast(0 as decimal(7,2)) as net_loss
+    from web_sales
+    union all
+    select ws_web_site_sk as wsr_web_site_sk,
+           wr_returned_date_sk as date_sk,
+           cast(0 as decimal(7,2)) as sales_price,
+           cast(0 as decimal(7,2)) as profit,
+           wr_return_amt as return_amt,
+           wr_net_loss as net_loss
+    from web_returns left outer join web_sales on
+         ( wr_item_sk = ws_item_sk
+           and wr_order_number = ws_order_number)
+   ) salesreturns,
+     date_dim,
+     web_site
+ where date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  INTERVAL 14 days)
+       and wsr_web_site_sk = web_site_sk
+ group by web_site_id)
+  select  channel
+        , id
+        , sum(sales) as sales
+        , sum(`returns`) as `returns`
+        , sum(profit) as profit
+ from
+ (select 'store channel' as channel
+        , 'store' || s_store_id as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from   ssr
+ union all
+ select 'catalog channel' as channel
+        , 'catalog_page' || cp_catalog_page_id as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from  csr
+ union all
+ select 'web channel' as channel
+        , 'web_site' || web_site_id as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from   wsr
+ ) x
+ group by rollup (channel, id)
+ order by channel
+         ,id
+ limit 100;
+---- RESULTS
+'catalog channel','catalog_pageAAAAAAAAAABAAAAA',73724.99,0.00,-34506.26
+'catalog channel','catalog_pageAAAAAAAAAADAAAAA',73182.60,0.00,-17828.06
+'catalog channel','catalog_pageAAAAAAAAABAAAAAA',0.00,4543.46,-2785.79
+'catalog channel','catalog_pageAAAAAAAAABBAAAAA',72671.60,0.00,6590.95
+'catalog channel','catalog_pageAAAAAAAAABCAAAAA',0.00,3646.32,-514.18
+'catalog channel','catalog_pageAAAAAAAAABDAAAAA',69548.37,0.00,-6749.40
+'catalog channel','catalog_pageAAAAAAAAACAAAAAA',0.00,10499.54,-5521.37
+'catalog channel','catalog_pageAAAAAAAAACBAAAAA',153295.93,5351.63,4632.81
+'catalog channel','catalog_pageAAAAAAAAACDAAAAA',54336.42,0.00,-18212.06
+'catalog channel','catalog_pageAAAAAAAAADAAAAAA',0.00,9992.13,-6117.85
+'catalog channel','catalog_pageAAAAAAAAADBAAAAA',131954.65,50.05,5527.80
+'catalog channel','catalog_pageAAAAAAAAADDAAAAA',76814.28,0.00,-3457.30
+'catalog channel','catalog_pageAAAAAAAAAEAAAAAA',0.00,3197.25,-478.68
+'catalog channel','catalog_pageAAAAAAAAAEBAAAAA',92714.17,0.00,10984.25
+'catalog channel','catalog_pageAAAAAAAAAECAAAAA',0.00,2249.75,-1759.52
+'catalog channel','catalog_pageAAAAAAAAAEDAAAAA',74886.06,0.00,12438.42
+'catalog channel','catalog_pageAAAAAAAAAFAAAAAA',0.00,7425.07,-4953.73
+'catalog channel','catalog_pageAAAAAAAAAFDAAAAA',68271.10,0.00,-31349.03
+'catalog channel','catalog_pageAAAAAAAAAGAAAAAA',0.00,30.69,-114.52
+'catalog channel','catalog_pageAAAAAAAAAGBAAAAA',0.00,152.10,-232.54
+'catalog channel','catalog_pageAAAAAAAAAGCAAAAA',0.00,907.94,-259.69
+'catalog channel','catalog_pageAAAAAAAAAGDAAAAA',80839.26,0.00,-17596.72
+'catalog channel','catalog_pageAAAAAAAAAHAAAAAA',183580.58,3667.05,-30239.38
+'catalog channel','catalog_pageAAAAAAAAAIAAAAAA',176232.57,130.41,-14554.63
+'catalog channel','catalog_pageAAAAAAAAAIBAAAAA',0.00,267.00,-180.11
+'catalog channel','catalog_pageAAAAAAAAAJAAAAAA',200218.67,1441.64,41.78
+'catalog channel','catalog_pageAAAAAAAAAJCAAAAA',21707.46,0.00,-2111.35
+'catalog channel','catalog_pageAAAAAAAAAKAAAAAA',142031.14,0.00,-26852.79
+'catalog channel','catalog_pageAAAAAAAAAKCAAAAA',15995.65,0.00,-1934.05
+'catalog channel','catalog_pageAAAAAAAAALAAAAAA',126857.88,9.78,-44236.00
+'catalog channel','catalog_pageAAAAAAAAALBAAAAA',0.00,NULL,-508.20
+'catalog channel','catalog_pageAAAAAAAAALCAAAAA',21479.91,0.00,-8594.75
+'catalog channel','catalog_pageAAAAAAAAAMAAAAAA',169425.57,2291.31,-37705.18
+'catalog channel','catalog_pageAAAAAAAAAMCAAAAA',14310.85,0.00,-21009.49
+'catalog channel','catalog_pageAAAAAAAAANAAAAAA',198016.35,273.78,-14742.98
+'catalog channel','catalog_pageAAAAAAAAANBAAAAA',0.00,490.00,-184.86
+'catalog channel','catalog_pageAAAAAAAAANCAAAAA',29918.97,0.00,-4040.58
+'catalog channel','catalog_pageAAAAAAAAAOAAAAAA',105854.60,0.00,-8874.12
+'catalog channel','catalog_pageAAAAAAAAAOCAAAAA',14091.48,0.00,-1309.14
+'catalog channel','catalog_pageAAAAAAAAAPAAAAAA',83006.94,0.00,-5381.07
+'catalog channel','catalog_pageAAAAAAAAAPCAAAAA',37508.18,0.00,-7290.39
+'catalog channel','catalog_pageAAAAAAAABAAAAAAA',0.00,1107.33,-960.08
+'catalog channel','catalog_pageAAAAAAAABABAAAAA',95905.45,0.00,-13710.06
+'catalog channel','catalog_pageAAAAAAAABADAAAAA',47636.96,0.00,-28573.24
+'catalog channel','catalog_pageAAAAAAAABBAAAAAA',0.00,2623.04,-1015.94
+'catalog channel','catalog_pageAAAAAAAABBBAAAAA',58354.38,0.00,-10624.68
+'catalog channel','catalog_pageAAAAAAAABBDAAAAA',49780.88,0.00,-11981.01
+'catalog channel','catalog_pageAAAAAAAABCAAAAAA',0.00,4125.66,-1523.65
+'catalog channel','catalog_pageAAAAAAAABCBAAAAA',98660.80,0.00,-16289.71
+'catalog channel','catalog_pageAAAAAAAABCDAAAAA',113374.11,0.00,-19319.37
+'catalog channel','catalog_pageAAAAAAAABDAAAAAA',0.00,180.96,-303.56
+'catalog channel','catalog_pageAAAAAAAABDBAAAAA',58175.73,380.96,555.42
+'catalog channel','catalog_pageAAAAAAAABDDAAAAA',49788.84,0.00,-35531.15
+'catalog channel','catalog_pageAAAAAAAABEAAAAAA',0.00,4989.69,-1347.21
+'catalog channel','catalog_pageAAAAAAAABEBAAAAA',77142.64,0.00,2181.34
+'catalog channel','catalog_pageAAAAAAAABEDAAAAA',37722.95,0.00,-3862.61
+'catalog channel','catalog_pageAAAAAAAABFAAAAAA',0.00,1134.57,-2249.74
+'catalog channel','catalog_pageAAAAAAAABFDAAAAA',90245.96,0.00,8274.06
+'catalog channel','catalog_pageAAAAAAAABGAAAAAA',0.00,9836.43,-6219.61
+'catalog channel','catalog_pageAAAAAAAABGBAAAAA',0.00,60.03,-337.81
+'catalog channel','catalog_pageAAAAAAAABHAAAAAA',211801.76,0.00,-18883.07
+'catalog channel','catalog_pageAAAAAAAABHCAAAAA',0.00,2809.62,-969.29
+'catalog channel','catalog_pageAAAAAAAABIAAAAAA',175304.26,0.00,-5063.68
+'catalog channel','catalog_pageAAAAAAAABJAAAAAA',192257.13,2641.12,4494.09
+'catalog channel','catalog_pageAAAAAAAABJCAAAAA',21066.11,0.00,-9792.90
+'catalog channel','catalog_pageAAAAAAAABKAAAAAA',117310.09,0.00,-38423.34
+'catalog channel','catalog_pageAAAAAAAABKCAAAAA',16351.27,0.00,970.92
+'catalog channel','catalog_pageAAAAAAAABLAAAAAA',189060.69,0.00,-27541.44
+'catalog channel','catalog_pageAAAAAAAABLCAAAAA',11111.58,0.00,2786.73
+'catalog channel','catalog_pageAAAAAAAABMAAAAAA',148581.54,1047.69,-39546.35
+'catalog channel','catalog_pageAAAAAAAABMCAAAAA',5756.80,0.00,961.53
+'catalog channel','catalog_pageAAAAAAAABNAAAAAA',210427.91,404.72,-17396.64
+'catalog channel','catalog_pageAAAAAAAABNCAAAAA',11474.87,0.00,-6101.47
+'catalog channel','catalog_pageAAAAAAAABOAAAAAA',85328.52,0.00,-13566.80
+'catalog channel','catalog_pageAAAAAAAABOCAAAAA',8991.73,0.00,-9799.23
+'catalog channel','catalog_pageAAAAAAAABPAAAAAA',67031.90,228.48,-17054.88
+'catalog channel','catalog_pageAAAAAAAABPCAAAAA',3184.57,0.00,-1553.07
+'catalog channel','catalog_pageAAAAAAAACAAAAAAA',0.00,594.69,-910.16
+'catalog channel','catalog_pageAAAAAAAACABAAAAA',68609.89,0.00,-2362.92
+'catalog channel','catalog_pageAAAAAAAACADAAAAA',97819.48,0.00,3615.65
+'catalog channel','catalog_pageAAAAAAAACBAAAAAA',0.00,4531.07,-864.84
+'catalog channel','catalog_pageAAAAAAAACBBAAAAA',87577.40,5163.40,-23331.95
+'catalog channel','catalog_pageAAAAAAAACBDAAAAA',65509.70,0.00,-26490.01
+'catalog channel','catalog_pageAAAAAAAACCAAAAAA',0.00,579.28,-263.33
+'catalog channel','catalog_pageAAAAAAAACCBAAAAA',79097.22,0.00,-14175.57
+'catalog channel','catalog_pageAAAAAAAACCCAAAAA',0.00,107.64,-110.70
+'catalog channel','catalog_pageAAAAAAAACCDAAAAA',94846.27,0.00,2494.45
+'catalog channel','catalog_pageAAAAAAAACDAAAAAA',0.00,3583.50,-5705.10
+'catalog channel','catalog_pageAAAAAAAACDBAAAAA',78115.95,0.00,-10896.38
+'catalog channel','catalog_pageAAAAAAAACDCAAAAA',0.00,793.44,-78.49
+'catalog channel','catalog_pageAAAAAAAACDDAAAAA',102043.84,0.00,-3411.73
+'catalog channel','catalog_pageAAAAAAAACEBAAAAA',80630.55,0.00,-37508.78
+'catalog channel','catalog_pageAAAAAAAACECAAAAA',0.00,505.73,-398.47
+'catalog channel','catalog_pageAAAAAAAACEDAAAAA',144185.84,0.00,23612.84
+'catalog channel','catalog_pageAAAAAAAACFAAAAAA',0.00,2046.36,-1765.63
+'catalog channel','catalog_pageAAAAAAAACFDAAAAA',87318.92,0.00,22201.70
+'catalog channel','catalog_pageAAAAAAAACGAAAAAA',0.00,1401.84,-382.86
+'catalog channel','catalog_pageAAAAAAAACGCAAAAA',0.00,498.96,-311.43
+'catalog channel','catalog_pageAAAAAAAACHAAAAAA',198097.84,0.00,-171.05
+'catalog channel','catalog_pageAAAAAAAACIAAAAAA',170669.13,0.00,-28180.55
+---- TYPES
+STRING, STRING, DECIMAL, DECIMAL, DECIMAL
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q67.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q67.test
new file mode 100644
index 0000000..77e5b26
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q67.test
@@ -0,0 +1,147 @@
+====
+---- QUERY: TPCDS-Q67
+select  *
+from (select i_category
+            ,i_class
+            ,i_brand
+            ,i_product_name
+            ,d_year
+            ,d_qoy
+            ,d_moy
+            ,s_store_id
+            ,sumsales
+            ,rank() over (partition by i_category order by sumsales desc) rk
+      from (select i_category
+                  ,i_class
+                  ,i_brand
+                  ,i_product_name
+                  ,d_year
+                  ,d_qoy
+                  ,d_moy
+                  ,s_store_id
+                  ,sum(coalesce(ss_sales_price*ss_quantity,0)) sumsales
+            from store_sales
+                ,date_dim
+                ,store
+                ,item
+       where  ss_sold_date_sk=d_date_sk
+          and ss_item_sk=i_item_sk
+          and ss_store_sk = s_store_sk
+          and d_month_seq between 1212 and 1212+11
+       group by  rollup(i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy,s_store_id))dw1) dw2
+where rk <= 100
+order by i_category
+        ,i_class
+        ,i_brand
+        ,i_product_name
+        ,d_year
+        ,d_qoy
+        ,d_moy
+        ,s_store_id
+        ,sumsales
+        ,rk
+limit 100;
+---- RESULTS
+'Books','arts','amalgmaxi #10','NULL',NULL,NULL,NULL,'NULL',771942.81,94
+'Books','arts','amalgmaxi #12','NULL',NULL,NULL,NULL,'NULL',1404915.39,28
+'Books','arts','amalgmaxi #2','NULL',NULL,NULL,NULL,'NULL',843070.19,86
+'Books','arts','NULL','NULL',NULL,NULL,NULL,'NULL',4506213.38,17
+'Books','business','importomaxi #12','NULL',NULL,NULL,NULL,'NULL',976588.70,69
+'Books','business','importomaxi #2','NULL',NULL,NULL,NULL,'NULL',1070176.27,59
+'Books','business','importomaxi #4','NULL',NULL,NULL,NULL,'NULL',985988.99,66
+'Books','business','importomaxi #6','NULL',NULL,NULL,NULL,'NULL',759660.32,95
+'Books','business','importomaxi #8','NULL',NULL,NULL,NULL,'NULL',998679.05,63
+'Books','business','NULL','NULL',NULL,NULL,NULL,'NULL',5357940.76,15
+'Books','computers','exportimaxi #10','NULL',NULL,NULL,NULL,'NULL',892700.18,77
+'Books','computers','exportimaxi #12','NULL',NULL,NULL,NULL,'NULL',1334760.61,34
+'Books','computers','exportimaxi #2','NULL',NULL,NULL,NULL,'NULL',1491893.77,26
+'Books','computers','exportimaxi #4','NULL',NULL,NULL,NULL,'NULL',1375974.23,31
+'Books','computers','exportimaxi #6','NULL',NULL,NULL,NULL,'NULL',864545.72,80
+'Books','computers','exportimaxi #8','NULL',NULL,NULL,NULL,'NULL',1227706.86,43
+'Books','computers','NULL','NULL',NULL,NULL,NULL,'NULL',7187581.37,3
+'Books','cooking','amalgunivamalg #10','NULL',NULL,NULL,NULL,'NULL',828936.66,89
+'Books','cooking','amalgunivamalg #12','NULL',NULL,NULL,NULL,'NULL',1199051.96,49
+'Books','cooking','amalgunivamalg #4','NULL',NULL,NULL,NULL,'NULL',1215546.70,46
+'Books','cooking','amalgunivamalg #6','NULL',NULL,NULL,NULL,'NULL',978464.05,68
+'Books','cooking','amalgunivamalg #8','NULL',NULL,NULL,NULL,'NULL',893562.91,76
+'Books','cooking','NULL','NULL',NULL,NULL,NULL,'NULL',5648185.86,13
+'Books','entertainments','edu packmaxi #10','NULL',NULL,NULL,NULL,'NULL',1063523.23,60
+'Books','entertainments','edu packmaxi #12','NULL',NULL,NULL,NULL,'NULL',812807.75,91
+'Books','entertainments','edu packmaxi #2','NULL',NULL,NULL,NULL,'NULL',850707.47,83
+'Books','entertainments','edu packmaxi #4','NULL',NULL,NULL,NULL,'NULL',1132602.81,56
+'Books','entertainments','edu packmaxi #6','NULL',NULL,NULL,NULL,'NULL',871965.57,79
+'Books','entertainments','edu packmaxi #8','NULL',NULL,NULL,NULL,'NULL',894401.25,75
+'Books','entertainments','NULL','NULL',NULL,NULL,NULL,'NULL',5705551.13,11
+'Books','fiction','scholarunivamalg #10','NULL',NULL,NULL,NULL,'NULL',1080330.14,58
+'Books','fiction','scholarunivamalg #12','NULL',NULL,NULL,NULL,'NULL',1063243.41,61
+'Books','fiction','scholarunivamalg #2','NULL',NULL,NULL,NULL,'NULL',1931852.77,18
+'Books','fiction','scholarunivamalg #6','NULL',NULL,NULL,NULL,'NULL',990232.80,65
+'Books','fiction','scholarunivamalg #8','NULL',NULL,NULL,NULL,'NULL',1357801.34,32
+'Books','fiction','NULL','NULL',NULL,NULL,NULL,'NULL',6758618.52,7
+'Books','history','scholarmaxi #10','NULL',NULL,NULL,NULL,'NULL',1159896.65,51
+'Books','history','scholarmaxi #12','NULL',NULL,NULL,NULL,'NULL',1377383.04,30
+'Books','history','scholarmaxi #2','NULL',NULL,NULL,NULL,'NULL',1880968.59,19
+'Books','history','scholarmaxi #6','NULL',NULL,NULL,NULL,'NULL',1321161.65,37
+'Books','history','scholarmaxi #8','NULL',NULL,NULL,NULL,'NULL',951861.54,72
+'Books','history','NULL','NULL',NULL,NULL,NULL,'NULL',7175822.76,4
+'Books','home repair','importounivamalg #10','NULL',NULL,NULL,NULL,'NULL',975176.77,71
+'Books','home repair','importounivamalg #12','NULL',NULL,NULL,NULL,'NULL',851531.36,82
+'Books','home repair','importounivamalg #2','NULL',NULL,NULL,NULL,'NULL',1553230.30,22
+'Books','home repair','importounivamalg #4','NULL',NULL,NULL,NULL,'NULL',834733.96,88
+'Books','home repair','importounivamalg #6','NULL',NULL,NULL,NULL,'NULL',672760.54,100
+'Books','home repair','importounivamalg #8','NULL',NULL,NULL,NULL,'NULL',1748378.72,21
+'Books','home repair','NULL','NULL',NULL,NULL,NULL,'NULL',6635811.65,8
+'Books','mystery','corpunivamalg #10','NULL',NULL,NULL,NULL,'NULL',862402.84,81
+'Books','mystery','corpunivamalg #12','NULL',NULL,NULL,NULL,'NULL',1132883.60,55
+'Books','mystery','corpunivamalg #2','NULL',NULL,NULL,NULL,'NULL',843818.57,84
+'Books','mystery','corpunivamalg #6','NULL',NULL,NULL,NULL,'NULL',1002261.69,62
+'Books','mystery','corpunivamalg #8','NULL',NULL,NULL,NULL,'NULL',727595.71,98
+'Books','mystery','NULL','NULL',NULL,NULL,NULL,'NULL',5139547.86,16
+'Books','parenting','corpmaxi #10','NULL',NULL,NULL,NULL,'NULL',1297132.15,40
+'Books','parenting','corpmaxi #12','NULL',NULL,NULL,NULL,'NULL',994252.14,64
+'Books','parenting','corpmaxi #2','NULL',NULL,NULL,NULL,'NULL',817919.30,90
+'Books','parenting','corpmaxi #4','NULL',NULL,NULL,NULL,'NULL',1137537.84,53
+'Books','parenting','corpmaxi #6','NULL',NULL,NULL,NULL,'NULL',1756990.35,20
+'Books','parenting','corpmaxi #8','NULL',NULL,NULL,NULL,'NULL',786459.87,92
+'Books','parenting','NULL','NULL',NULL,NULL,NULL,'NULL',6790291.65,5
+'Books','reference','brandmaxi #10','NULL',NULL,NULL,NULL,'NULL',901096.84,74
+'Books','reference','brandmaxi #12','NULL',NULL,NULL,NULL,'NULL',1331752.35,35
+'Books','reference','brandmaxi #2','NULL',NULL,NULL,NULL,'NULL',975383.60,70
+'Books','reference','brandmaxi #4','NULL',NULL,NULL,NULL,'NULL',1492547.50,24
+'Books','reference','brandmaxi #6','NULL',NULL,NULL,NULL,'NULL',1277662.78,41
+'Books','reference','brandmaxi #8','NULL',NULL,NULL,NULL,'NULL',1354720.24,33
+'Books','reference','NULL','NULL',NULL,NULL,NULL,'NULL',7333163.31,2
+'Books','romance','namelessmaxi #10','NULL',NULL,NULL,NULL,'NULL',749766.99,96
+'Books','romance','namelessmaxi #12','NULL',NULL,NULL,NULL,'NULL',1297611.04,39
+'Books','romance','namelessmaxi #2','NULL',NULL,NULL,NULL,'NULL',1208741.34,47
+'Books','romance','namelessmaxi #4','NULL',NULL,NULL,NULL,'NULL',1303175.14,38
+'Books','romance','namelessmaxi #6','NULL',NULL,NULL,NULL,'NULL',1329372.02,36
+'Books','romance','namelessmaxi #8','NULL',NULL,NULL,NULL,'NULL',872633.95,78
+'Books','romance','NULL','NULL',NULL,NULL,NULL,'NULL',6761300.48,6
+'Books','science','maximaxi #10','NULL',NULL,NULL,NULL,'NULL',843554.63,85
+'Books','science','maximaxi #2','NULL',NULL,NULL,NULL,'NULL',837350.77,87
+'Books','science','maximaxi #6','NULL',NULL,NULL,NULL,'NULL',1492461.17,25
+'Books','science','maximaxi #8','NULL',NULL,NULL,NULL,'NULL',1200421.62,48
+'Books','science','NULL','NULL',NULL,NULL,NULL,'NULL',5457882.47,14
+'Books','self-help','exportiunivamalg #10','NULL',NULL,NULL,NULL,'NULL',1161828.20,50
+'Books','self-help','exportiunivamalg #2','NULL',NULL,NULL,NULL,'NULL',1094139.27,57
+'Books','self-help','exportiunivamalg #4','NULL',NULL,NULL,NULL,'NULL',777051.93,93
+'Books','self-help','exportiunivamalg #6','NULL',NULL,NULL,NULL,'NULL',1404452.50,29
+'Books','self-help','exportiunivamalg #8','NULL',NULL,NULL,NULL,'NULL',739121.55,97
+'Books','self-help','NULL','NULL',NULL,NULL,NULL,'NULL',5652129.32,12
+'Books','sports','edu packunivamalg #10','NULL',NULL,NULL,NULL,'NULL',1240893.26,42
+'Books','sports','edu packunivamalg #12','NULL',NULL,NULL,NULL,'NULL',914992.76,73
+'Books','sports','edu packunivamalg #4','NULL',NULL,NULL,NULL,'NULL',1137120.66,54
+'Books','sports','edu packunivamalg #6','NULL',NULL,NULL,NULL,'NULL',1503138.10,23
+'Books','sports','edu packunivamalg #8','NULL',NULL,NULL,NULL,'NULL',1222640.97,44
+'Books','sports','NULL','NULL',NULL,NULL,NULL,'NULL',6435144.67,9
+'Books','travel','univunivamalg #10','NULL',NULL,NULL,NULL,'NULL',705516.40,99
+'Books','travel','univunivamalg #12','NULL',NULL,NULL,NULL,'NULL',1144238.62,52
+'Books','travel','univunivamalg #2','NULL',NULL,NULL,NULL,'NULL',980611.95,67
+'Books','travel','univunivamalg #4','NULL',NULL,NULL,NULL,'NULL',1220300.37,45
+'Books','travel','univunivamalg #8','NULL',NULL,NULL,NULL,'NULL',1459320.64,27
+'Books','travel','NULL','NULL',NULL,NULL,NULL,'NULL',6168021.72,10
+'Books','NULL','NULL','NULL',NULL,NULL,NULL,'NULL',98805619.53,1
+---- TYPES
+STRING, STRING, STRING, STRING, INT, INT, INT, STRING, DECIMAL, BIGINT
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q77.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q77.test
new file mode 100644
index 0000000..4d9a7e0
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q77.test
@@ -0,0 +1,155 @@
+====
+---- QUERY: TPCDS-Q77
+with ss as
+ (select s_store_sk,
+         sum(ss_ext_sales_price) as sales,
+         sum(ss_net_profit) as profit
+ from store_sales,
+      date_dim,
+      store
+ where ss_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and ss_store_sk = s_store_sk
+ group by s_store_sk)
+ ,
+ sr as
+ (select s_store_sk,
+         sum(sr_return_amt) as `returns`,
+         sum(sr_net_loss) as profit_loss
+ from store_returns,
+      date_dim,
+      store
+ where sr_returned_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and sr_store_sk = s_store_sk
+ group by s_store_sk),
+ cs as
+ (select cs_call_center_sk,
+        sum(cs_ext_sales_price) as sales,
+        sum(cs_net_profit) as profit
+ from catalog_sales,
+      date_dim
+ where cs_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+ group by cs_call_center_sk
+ ),
+ cr as
+ (select
+        sum(cr_return_amount) as `returns`,
+        sum(cr_net_loss) as profit_loss
+ from catalog_returns,
+      date_dim
+ where cr_returned_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+ ),
+ ws as
+ ( select wp_web_page_sk,
+        sum(ws_ext_sales_price) as sales,
+        sum(ws_net_profit) as profit
+ from web_sales,
+      date_dim,
+      web_page
+ where ws_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and ws_web_page_sk = wp_web_page_sk
+ group by wp_web_page_sk),
+ wr as
+ (select wp_web_page_sk,
+        sum(wr_return_amt) as `returns`,
+        sum(wr_net_loss) as profit_loss
+ from web_returns,
+      date_dim,
+      web_page
+ where wr_returned_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and wr_web_page_sk = wp_web_page_sk
+ group by wp_web_page_sk)
+  select  channel
+        , id
+        , sum(sales) as sales
+        , sum(`returns`) as `returns`
+        , sum(profit) as profit
+ from
+ (select 'store channel' as channel
+        , ss.s_store_sk as id
+        , sales
+        , coalesce(`returns`, 0) as `returns`
+        , (profit - coalesce(profit_loss,0)) as profit
+ from   ss left join sr
+        on  ss.s_store_sk = sr.s_store_sk
+ union all
+ select 'catalog channel' as channel
+        , cs_call_center_sk as id
+        , sales
+        , `returns`
+        , (profit - profit_loss) as profit
+ from  cs
+       , cr
+ union all
+ select 'web channel' as channel
+        , ws.wp_web_page_sk as id
+        , sales
+        , coalesce(`returns`, 0) `returns`
+        , (profit - coalesce(profit_loss,0)) as profit
+ from   ws left join wr
+        on  ws.wp_web_page_sk = wr.wp_web_page_sk
+ ) x
+ group by rollup (channel, id)
+ order by channel
+         ,id
+         ,sales desc
+ limit 100;
+---- RESULTS
+'catalog channel',1,26059763.88,1676013.07,-3439678.62
+'catalog channel',2,26848946.97,1676013.07,-3818396.48
+'catalog channel',4,27291299.87,1676013.07,-4012447.66
+'catalog channel',NULL,80395563.85,6704052.28,-12276833.88
+'catalog channel',NULL,195553.13,1676013.07,-1006311.12
+'store channel',1,18173324.99,458396.78,-7884303.32
+'store channel',2,19801551.45,482036.27,-8829753.47
+'store channel',4,19389251.68,487721.54,-8683007.74
+'store channel',7,18542305.22,441352.00,-8390550.61
+'store channel',8,18949957.95,520739.84,-8609781.61
+'store channel',10,19471021.10,424474.72,-8634992.52
+'store channel',NULL,114327412.39,2814721.15,-51032389.27
+'web channel',1,1368093.56,18413.39,-109814.89
+'web channel',2,1360282.03,20851.65,-262088.83
+'web channel',4,1406457.78,23644.41,-123413.05
+'web channel',7,1500004.47,21499.66,-154155.36
+'web channel',8,1446371.50,23082.66,-94682.60
+'web channel',10,1332588.96,17483.48,-116693.82
+'web channel',13,1482737.42,22291.09,-143363.78
+'web channel',14,1284016.42,27528.32,-109703.25
+'web channel',16,1243361.71,17003.20,-177836.49
+'web channel',19,1255323.32,19886.02,-164063.62
+'web channel',20,1453667.29,26935.69,-108401.67
+'web channel',22,1387121.58,13920.95,-178199.43
+'web channel',25,1335607.81,22147.53,-244732.70
+'web channel',26,1373568.78,13978.71,-203947.57
+'web channel',28,1421052.17,20238.90,-182295.36
+'web channel',31,1359126.56,33528.64,-187639.56
+'web channel',32,1352687.30,24759.85,-136361.30
+'web channel',34,1375278.93,20797.04,-128682.38
+'web channel',37,1280097.59,22871.42,-72902.31
+'web channel',38,1479886.09,19766.90,-165440.16
+'web channel',40,1412821.75,13967.60,-129666.09
+'web channel',43,1218207.79,12950.30,-267935.28
+'web channel',44,1394049.31,9287.77,-69579.24
+'web channel',46,1307728.82,20036.96,-169346.37
+'web channel',49,1523143.08,16493.90,-187746.56
+'web channel',50,1389142.56,26725.69,-75992.98
+'web channel',52,1428230.31,21974.82,-168225.24
+'web channel',55,1494853.34,33347.97,-143467.09
+'web channel',56,1390529.31,6835.23,-126132.78
+'web channel',58,1126120.88,31121.74,-218628.31
+'web channel',NULL,41182158.42,623371.49,-4621138.07
+'NULL',NULL,235905134.66,10142144.92,-67930361.22
+---- TYPES
+STRING, INT, DECIMAL, DECIMAL, DECIMAL
+====
diff --git a/tests/query_test/test_aggregation.py b/tests/query_test/test_aggregation.py
index 8106145..a4820e7 100644
--- a/tests/query_test/test_aggregation.py
+++ b/tests/query_test/test_aggregation.py
@@ -384,6 +384,12 @@ class TestAggregationQueries(ImpalaTestSuite):
       for i in xrange(14, 16):
         self.appx_equals(int(sampled_ndv_vals[i]) * sample_perc, int(ndv_vals[i]), 2.0)
 
+  def test_grouping_sets(self, vector):
+    """Tests for ROLLUP, CUBE and GROUPING SETS."""
+    if vector.get_value('table_format').file_format == 'hbase':
+      pytest.xfail(reason="IMPALA-283 - HBase null handling is inconsistent")
+    self.run_test_case('QueryTest/grouping-sets', vector)
+
 
 class TestDistinctAggregation(ImpalaTestSuite):
   """Run the distinct aggregation test suite, with codegen and shuffle_distinct_exprs
diff --git a/tests/query_test/test_tpcds_queries.py b/tests/query_test/test_tpcds_queries.py
index f9e911d..40d9dfe 100644
--- a/tests/query_test/test_tpcds_queries.py
+++ b/tests/query_test/test_tpcds_queries.py
@@ -351,6 +351,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q4(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q4', vector)
 
+  def test_tpcds_q5(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q5', vector)
+
   def test_tpcds_q6(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q6', vector)
 
@@ -384,6 +387,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q17(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q17', vector)
 
+  def test_tpcds_q18(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q18', vector)
+
   def test_tpcds_q18a(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q18a', vector)
 
@@ -396,6 +402,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q21(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q21', vector)
 
+  def test_tpcds_q22(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q22', vector)
+
   def test_tpcds_q22a(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q22a', vector)
 
@@ -510,6 +519,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q65(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q65', vector)
 
+  def test_tpcds_q67(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q67', vector)
+
   def test_tpcds_q67a(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q67a', vector)
 
@@ -540,6 +552,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q76(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q76', vector)
 
+  def test_tpcds_q77(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q77', vector)
+
   def test_tpcds_q77a(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q77a', vector)
 
diff --git a/tests/util/parse_util.py b/tests/util/parse_util.py
index 80b1c1d..c5105ee 100644
--- a/tests/util/parse_util.py
+++ b/tests/util/parse_util.py
@@ -22,7 +22,7 @@ from datetime import datetime
 # changed, and the stress test loses the ability to run the full set of queries. Set
 # these constants and assert that when a workload is used, all the queries we expect to
 # use are there.
-EXPECTED_TPCDS_QUERIES_COUNT = 87
+EXPECTED_TPCDS_QUERIES_COUNT = 92
 EXPECTED_TPCH_NESTED_QUERIES_COUNT = 22
 EXPECTED_TPCH_QUERIES_COUNT = 22
 # Add the number of stress test specific queries, i.e. in files like '*-stress-*.test'


[impala] 02/02: IMPALA-9917: grouping() and grouping_id() support

Posted by ta...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

tarmstrong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 574fef2a7690ff2543df0e5e11932b4957d40676
Author: Tim Armstrong <ta...@cloudera.com>
AuthorDate: Fri Jul 3 12:10:38 2020 -0700

    IMPALA-9917: grouping() and grouping_id() support
    
    Implements grouping() and grouping_id() builtins.
    grouping_id() has both a no-arg version, which returns
    a bit vector of all grouping exprs and a varargs version,
    which returns a bit vector of the provided arguments.
    
    Grouping is a keyword, so needs special handling in the
    parser to be accepted as a function name.
    
    These functions are implemented in the transpose agg
    with a CASE expression similar to other aggregate functions,
    but returning the grouping() or grouping_id() value for that
    aggregation class instead of an aggregated value.
    
    Testing:
    * Added parser test for grouping keyword.
    * Added analysis tests for the functions.
    * Added basic planner test to show expressions generated
    * Added some TPC-DS queries that use grouping() - queries
      80, 70 and 86 using reference .test files from Fang-Yu
      Rao. 27 and 36 were added with reference results from
      https://github.com/cwida/tpcds-result-reproduction
    * Add targeted end-to-end tests.
    * Added view compatibility test with Hive.
    
    Change-Id: If0b1640d606256c0fe9204d2a21a8f6d06abcdb6
    Reviewed-on: http://gerrit.cloudera.org:8080/16140
    Reviewed-by: Tim Armstrong <ta...@cloudera.com>
    Tested-by: Tim Armstrong <ta...@cloudera.com>
---
 fe/src/main/cup/sql-parser.cup                     |    5 +
 .../apache/impala/analysis/FunctionCallExpr.java   |   39 +-
 .../org/apache/impala/analysis/GroupByClause.java  |    4 -
 .../apache/impala/analysis/MultiAggregateInfo.java |  170 ++-
 .../apache/impala/catalog/AggregateFunction.java   |   18 +
 .../java/org/apache/impala/catalog/BuiltinsDb.java |   11 +
 .../apache/impala/analysis/AnalyzeStmtsTest.java   |   84 ++
 .../org/apache/impala/analysis/ParserTest.java     |   36 +-
 .../queries/PlannerTest/grouping-sets.test         |  134 ++
 .../queries/PlannerTest/tpcds-all.test             | 1413 ++++++++++++++++++++
 .../queries/QueryTest/grouping-sets.test           |  172 +++
 .../queries/QueryTest/views-compatibility.test     |   29 +
 .../tpcds/queries/tpcds-decimal_v2-q27.test        |  126 ++
 .../tpcds/queries/tpcds-decimal_v2-q36.test        |  134 ++
 .../tpcds/queries/tpcds-decimal_v2-q70.test        |   46 +
 .../tpcds/queries/tpcds-decimal_v2-q80.test        |  199 +++
 .../tpcds/queries/tpcds-decimal_v2-q86.test        |  131 ++
 tests/query_test/test_tpcds_queries.py             |   15 +
 tests/util/parse_util.py                           |    2 +-
 19 files changed, 2686 insertions(+), 82 deletions(-)

diff --git a/fe/src/main/cup/sql-parser.cup b/fe/src/main/cup/sql-parser.cup
index d7d1c26..bc124a4 100644
--- a/fe/src/main/cup/sql-parser.cup
+++ b/fe/src/main/cup/sql-parser.cup
@@ -2880,6 +2880,11 @@ function_name ::=
   // Use 'dotted_path' to avoid a reduce/reduce with slot_ref.
   dotted_path:path
   {: RESULT = new FunctionName(path); :}
+  // Grouping is both a keyword and a builtin aggregate function.
+  | KW_GROUPING:r
+  {: List<String> path = new ArrayList<>();
+     path.add(r.toString());
+     RESULT = new FunctionName(path); :}
   ;
 
 from_clause ::=
diff --git a/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java b/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java
index 86af1c2..86a8876 100644
--- a/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java
+++ b/fe/src/main/java/org/apache/impala/analysis/FunctionCallExpr.java
@@ -35,6 +35,8 @@ import org.apache.impala.thrift.TExprNode;
 import org.apache.impala.thrift.TExprNodeType;
 import org.apache.impala.thrift.TFunctionBinaryType;
 import org.apache.impala.thrift.TQueryOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
@@ -42,6 +44,8 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 
 public class FunctionCallExpr extends Expr {
+  private final static Logger LOG = LoggerFactory.getLogger(FunctionCallExpr.class);
+
   private final FunctionName fnName_;
   private final FunctionParams params_;
   private boolean isAnalyticFnCall_ = false;
@@ -118,11 +122,17 @@ public class FunctionCallExpr extends Expr {
 
   /** Returns true if fnName is a built-in with given name. */
   private static boolean functionNameEqualsBuiltin(FunctionName fnName, String name) {
-    return fnName.getFnNamePath().size() == 1
-           && fnName.getFnNamePath().get(0).equalsIgnoreCase(name)
-        || fnName.getFnNamePath().size() == 2
-           && fnName.getFnNamePath().get(0).equals(BuiltinsDb.NAME)
-           && fnName.getFnNamePath().get(1).equalsIgnoreCase(name);
+    // We could either have a function path, or the function name and optional database.
+    if (fnName.getFnNamePath() != null) {
+      return fnName.getFnNamePath().size() == 1
+             && fnName.getFnNamePath().get(0).equalsIgnoreCase(name)
+          || fnName.getFnNamePath().size() == 2
+             && fnName.getFnNamePath().get(0).equals(BuiltinsDb.NAME)
+             && fnName.getFnNamePath().get(1).equalsIgnoreCase(name);
+    } else {
+      return (fnName.getDb() == null || fnName.getDb().equals(BuiltinsDb.NAME)) &&
+          fnName.getFunction().equalsIgnoreCase(name);
+    }
   }
 
   /**
@@ -250,6 +260,16 @@ public class FunctionCallExpr extends Expr {
     return fn_ instanceof AggregateFunction && !isAnalyticFnCall_;
   }
 
+  /** Returns true if this function is a call to the built-in grouping() function. */
+  public boolean isGroupingBuiltin() {
+    return functionNameEqualsBuiltin(fnName_, "grouping");
+  }
+
+  /** Returns true if this function is a call to the built-in grouping_id() function. */
+  public boolean isGroupingIdBuiltin() {
+    return functionNameEqualsBuiltin(fnName_, "grouping_id");
+  }
+
   /**
    * Returns true if this is a call to an aggregate function that returns
    * non-null on an empty input (e.g. count).
@@ -522,6 +542,15 @@ public class FunctionCallExpr extends Expr {
       return;
     }
 
+    // grouping_id() can take any set of input slot arguments. Just resolve it to the
+    // zero-argument version so it can be rewritten in MultiAggregateInfo.
+    if (isGroupingIdBuiltin()) {
+      Function searchDesc = new Function(fnName_, new Type[0], Type.INVALID, false);
+      fn_ = db.getFunction(searchDesc, Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
+      type_ = fn_.getReturnType();
+      return;
+    }
+
     // TODO: We allow implicit cast from string->timestamp but only
     // support avg(timestamp). This means avg(string_col) would work
     // from our casting rules. This is not right.
diff --git a/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java b/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java
index 2a663d8..7f92dc8 100644
--- a/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java
+++ b/fe/src/main/java/org/apache/impala/analysis/GroupByClause.java
@@ -117,10 +117,6 @@ public class GroupByClause {
   }
 
   public List<Expr> getOrigGroupingExprs() { return origGroupingExprs_; }
-  public List<Long> getGroupingIDs() {
-    Preconditions.checkState(isAnalyzed_);
-    return groupingIDs_;
-  }
   public List<List<Expr>> getAnalyzedGroupingSets() {
     Preconditions.checkState(isAnalyzed_);
     return analyzedGroupingSets_;
diff --git a/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java b/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java
index 0d998ef..fe9c27e 100644
--- a/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java
+++ b/fe/src/main/java/org/apache/impala/analysis/MultiAggregateInfo.java
@@ -30,6 +30,8 @@ import org.apache.impala.catalog.Type;
 import org.apache.impala.common.AnalysisException;
 import org.apache.impala.common.InternalException;
 import org.apache.kudu.shaded.com.google.common.collect.Lists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Preconditions;
 
@@ -109,6 +111,8 @@ import com.google.common.base.Preconditions;
  * the transposition to the output of the new simplified aggregation.
  */
 public class MultiAggregateInfo {
+  private final static Logger LOG = LoggerFactory.getLogger(MultiAggregateInfo.class);
+
   public static enum AggPhase {
     FIRST,
     FIRST_MERGE,
@@ -164,9 +168,6 @@ public class MultiAggregateInfo {
   // TODO: could generalise this to allow omitting NULL expressions.
   private List<List<Expr>> groupingSets_;
 
-  // Indicates whether to generate the grouping_id column for grouping sets
-  private boolean generateGroupingId_;
-
   public MultiAggregateInfo(List<Expr> groupingExprs, List<FunctionCallExpr> aggExprs,
       List<List<Expr>> groupingSets) {
     groupingExprs_ = Expr.cloneList(Preconditions.checkNotNull(groupingExprs));
@@ -233,11 +234,16 @@ public class MultiAggregateInfo {
     // into a separate list.
     // List of all DISTINCT expr lists and the grouped agg exprs.
     // distinctExprs[i] corresponds to groupedDistinctAggExprs[i]
+    // grouping() and grouping_id() expressions will be replaced by constants so are
+    // not included in either list.
     List<List<Expr>> distinctExprs = new ArrayList<>();
     List<List<FunctionCallExpr>> groupedDistinctAggExprs = new ArrayList<>();
     List<FunctionCallExpr> nonDistinctAggExprs = new ArrayList<>();
+    List<FunctionCallExpr> groupingBuiltinExprs = new ArrayList<>();
     for (FunctionCallExpr aggExpr : aggExprs_) {
-      if (aggExpr.isDistinct()) {
+      if (aggExpr.isGroupingBuiltin() || aggExpr.isGroupingIdBuiltin()) {
+        groupingBuiltinExprs.add(aggExpr);
+      } else if (aggExpr.isDistinct()) {
         List<Expr> children = AggregateFunction.getCanonicalDistinctAggChildren(aggExpr);
         int groupIdx = distinctExprs.indexOf(children);
         List<FunctionCallExpr> groupAggFns;
@@ -287,9 +293,20 @@ public class MultiAggregateInfo {
       aggInfos_.add(AggregateInfo.create(groupingExprs_, aggClass, analyzer));
     }
 
+    // Set up outputSmap_, which maps from the original grouping and aggregate exprs in
+    // all aggregation classes to the output of the final aggregation.
+    outputSmap_ = aggInfos_.size() == 1 ?
+        aggInfos_.get(0).getResultSmap().clone() : new ExprSubstitutionMap();
+    if (groupingBuiltinExprs.size() > 0) {
+      // grouping() and grouping_id() must be replaced with constants.
+      for (FunctionCallExpr aggExpr : groupingBuiltinExprs) {
+        outputSmap_.put(aggExpr.clone(),
+            getGroupingId(aggExpr, aggInfos_.get(0), groupingExprs_));
+      }
+    }
+
     if (aggInfos_.size() == 1) {
       // Only a single aggregation class, no transposition step is needed.
-      outputSmap_ = aggInfos_.get(0).getResultSmap();
       return;
     }
 
@@ -303,10 +320,9 @@ public class MultiAggregateInfo {
           new SlotRef(outputSlots.get(i)), groupingExprs_.get(i));
     }
 
-    // Maps from the original grouping and aggregate exprs in all aggregation classes to
-    // the final output produced by the transposing aggregation.
-    outputSmap_ = new ExprSubstitutionMap();
-    // Add mappings for grouping exprs.
+    // Set up outputSmap_ to map from the original grouping and aggregate exprs in
+    // all aggregation classes to the final output produced by the transposing
+    // aggregation.
     for (int i = 0; i < groupingExprs_.size(); ++i) {
       outputSmap_.put(groupingExprs_.get(i).clone(), new SlotRef(outputSlots.get(i)));
     }
@@ -330,6 +346,9 @@ public class MultiAggregateInfo {
    *    analysis error occurs.
    */
   private void analyzeGroupingSets(Analyzer analyzer) throws AnalysisException {
+    // Regular aggregate expressions that are evaluated over input rows.
+    List<FunctionCallExpr> inputAggExprs = new ArrayList<>();
+    // grouping() and grouping_id() expressions that are evaluated during transpose.
     for (FunctionCallExpr aggExpr : aggExprs_) {
       if (aggExpr.isDistinct()) {
         // We can't handle this now - it would require enumerating more aggregation
@@ -337,13 +356,18 @@ public class MultiAggregateInfo {
         throw new AnalysisException("Distinct aggregate functions and grouping sets " +
             "are not supported in the same query block.");
       }
+      if (!aggExpr.isGroupingBuiltin() && !aggExpr.isGroupingIdBuiltin()) {
+        inputAggExprs.add(aggExpr);
+      }
     }
 
+    // Enumerate the aggregate classes that need to be evaluated before the final
+    // transposition.
     List<List<FunctionCallExpr>> aggClasses = new ArrayList<>(groupingSets_.size());
     List<AggregateInfo> aggInfos = new ArrayList<>(groupingSets_.size());
     for (List<Expr> groupingSet : groupingSets_) {
-      aggClasses.add(aggExprs_);
-      aggInfos.add(AggregateInfo.create(groupingSet, aggExprs_, analyzer));
+      aggClasses.add(inputAggExprs);
+      aggInfos.add(AggregateInfo.create(groupingSet, inputAggExprs, analyzer));
     }
     // analyzeCustomClasses() will do the rest of the analysis and set 'isAnalyzed_'
     analyzeCustomClasses(analyzer, aggClasses, aggInfos);
@@ -372,7 +396,16 @@ public class MultiAggregateInfo {
 
     if (aggInfos_.size() == 1) {
       // Only a single aggregation class, no transposition step is needed.
-      outputSmap_ = aggInfos_.get(0).getResultSmap();
+      // Grouping builtins like grouping_id() would otherwise be handled in transpose agg.
+      // With only one agg class, we can replace them with constants.
+      for (FunctionCallExpr aggExpr : aggExprs_) {
+        if (aggExpr.isGroupingBuiltin() || aggExpr.isGroupingIdBuiltin()) {
+          if (outputSmap_ == null) outputSmap_ = aggInfos_.get(0).getResultSmap().clone();
+          outputSmap_.put(aggExpr.clone(),
+              getGroupingId(aggExpr, aggInfos_.get(0), groupingExprs_));
+        }
+      }
+      if (outputSmap_ == null) outputSmap_ = aggInfos_.get(0).getResultSmap();
       return;
     }
 
@@ -520,7 +553,9 @@ public class MultiAggregateInfo {
    *    END
    *
    *    Notes:
-   *      - This method optionally projects a grouping_id field for the grouping sets
+   *      - This method handles grouping() and grouping_id() aggregate functions
+   *        by generating CASE expressions with the same structure as above that
+   *        return the appropriate grouping()/grouping_id() value for the agg class.
    *      - The max number of grouping exprs allowed per grouping set is 64
    *      - For the transpose phase grouping exprs we add the TupleId also - this
    *        ensures that NULL values in the data are differentiated from NULLs in the
@@ -551,22 +586,42 @@ public class MultiAggregateInfo {
     List<CaseWhenClause> caseWhenClauses = new ArrayList<>();
 
     // Create aggregate exprs for the transposing agg.
-    for (int aggIndex = numGroupingExprs; aggIndex < numSlots; aggIndex++) {
+    int inputAggSlotIdx = numGroupingExprs;
+    for (int i = 0; i < aggExprs.size(); ++i) {
       caseWhenClauses.clear();
-      for (AggregateInfo aggInfo : aggInfos) {
+      FunctionCallExpr aggExpr = aggExprs.get(i);
+      boolean isGroupingFn = aggExpr.isGroupingBuiltin() || aggExpr.isGroupingIdBuiltin();
+      if (isGroupingFn) {
+        for (Expr childExpr : aggExpr.getChildren()) {
+          if (!groupingExprs.contains(childExpr)) {
+            throw new AnalysisException(childExpr.toSql() + " is not a grouping " +
+                "expression in " + aggExpr.toSql());
+          }
+        }
+      }
+      for (int j = 0; j < aggInfos.size(); ++j) {
+        AggregateInfo aggInfo = aggInfos.get(j);
         TupleDescriptor aggTuple = aggInfo.getResultTupleDesc();
-        Preconditions.checkState(aggIndex < aggTuple.getSlots().size());
         Expr whenExpr = NumericLiteral.create(aggTuple.getId().asInt());
-        Expr thenExpr = new SlotRef(aggTuple.getSlots().get(aggIndex));
+        Expr thenExpr;
+        if (isGroupingFn) {
+          thenExpr = getGroupingId(aggExpr, aggInfo, groupingExprs);
+        } else {
+          Preconditions.checkState(inputAggSlotIdx < aggTuple.getSlots().size(),
+              inputAggSlotIdx + " >= " + aggTuple.getSlots().size() + ": " +
+              aggTuple.debugString());
+          thenExpr = new SlotRef(aggTuple.getSlots().get(inputAggSlotIdx));
+        }
         caseWhenClauses.add(new CaseWhenClause(whenExpr, thenExpr));
       }
       CaseExpr caseExpr =
         new CaseExpr(new ValidTupleIdExpr(aggTids), caseWhenClauses, null);
       caseExpr.analyzeNoThrow(analyzer);
       List<Expr> aggFnParams = Lists.<Expr>newArrayList(tidInPred, caseExpr);
-      FunctionCallExpr aggExpr = new FunctionCallExpr("aggif", aggFnParams);
-      aggExpr.analyzeNoThrow(analyzer);
-      transAggExprs.add(aggExpr);
+      FunctionCallExpr transAggExpr = new FunctionCallExpr("aggif", aggFnParams);
+      transAggExpr.analyzeNoThrow(analyzer);
+      transAggExprs.add(transAggExpr);
+      if (!isGroupingFn) ++inputAggSlotIdx;
     }
 
     // Create grouping exprs for the transposing agg.
@@ -609,50 +664,45 @@ public class MultiAggregateInfo {
     caseExprForTids.analyzeNoThrow(analyzer);
     transGroupingExprs.add(caseExprForTids);
 
-    // Create the grouping_id column if requested.
-    // consider grouping sets: ((a1, b1), (a1, NULL), (NULL,NULL))
-    // By SQL standards, in the above NULL implies '1', non-null implies '0'.
-    // We multiply this by the place value of that column which is power-of-2
-    // This gives a grouping_id for each grouping set which is encapsulated
-    // within an aggif() function as below:
-    // AGGIF(valid_tid(1, 2, 3) IN (1, 2, 3),
-    //        CASE valid_tid(1, 2, 3) WHEN 1 then <groupingId_for_gs1>
-    //                                WHEN 2 then <groupingId_for_gs2>
-    //                                WHEN 3 then <groupingId_for_gs3>
-    //                               END)
-    if (generateGroupingId_) {
-      caseWhenClauses.clear();
-      for (AggregateInfo aggInfo : aggInfos) {
-        long groupingId = 0;
-        TupleDescriptor aggTuple = aggInfo.getResultTupleDesc();
-        Expr whenExpr = NumericLiteral.create(aggTuple.getId().asInt());
-        int numGbExprs = aggInfo.getGroupingExprs().size();
-        for (int i = numGbExprs - 1, j = 0; i >= 0; i--, j++) {
-          Expr aggInfoGbExpr = aggInfo.getGroupingExprs().get(i);
-          long placeValue = 1L << j;
-          int factor = aggInfoGbExpr instanceof NullLiteral ? 1 : 0;
-          groupingId += factor * placeValue;
-        }
-        Expr thenExpr = NumericLiteral.create(groupingId, Type.BIGINT);
-        caseWhenClauses.add(new CaseWhenClause(whenExpr, thenExpr));
-      }
-      CaseExpr caseExpr =
-        new CaseExpr(new ValidTupleIdExpr(aggTids), caseWhenClauses, null);
-
-      // wrap this in an aggif expr because an AggregateInfo only allows either
-      // aggregate exprs or grouping exprs
-      List<Expr> aggFnParams = Lists.<Expr>newArrayList(tidInPred, caseExpr);
-      FunctionCallExpr aggExpr = new FunctionCallExpr("aggif", aggFnParams);
-      aggExpr.analyze(analyzer);
-      transAggExprs.add(aggExpr);
-    }
-
     AggregateInfo result =
         AggregateInfo.create(transGroupingExprs, transAggExprs, analyzer);
     return result;
   }
 
   /**
+   * Return the value for grouping() or grouping_id() for a particular aggregation class.
+   * @param aggExpr the grouping() or grouping_id() function
+   * @param aggInfo the aggregate info for the aggregation class.
+   * @param groupingExprs all grouping expressions
+   */
+  private static NumericLiteral getGroupingId(FunctionCallExpr aggExpr,
+      AggregateInfo aggInfo, List<Expr> groupingExprs) {
+    if (aggExpr.isGroupingBuiltin()) {
+      boolean inSet = aggInfo.getGroupingExprs().contains(aggExpr.getChild(0));
+      // grouping(col) returns 1 if this is the aggregate across all values of
+      // the grouping expression, i.e. if the expression is *not* one of the
+      // grouping expressions for this aggregate class.
+      return NumericLiteral.create(inSet ? 0 : 1, Type.TINYINT);
+    } else {
+      Preconditions.checkState(aggExpr.isGroupingIdBuiltin());
+      // grouping_id() returns a bit vector of the groups exprs included in this
+      // agg class. The last grouping expression is the least significant bit in
+      // the returned value. The bit vector has 1 if this is the aggregate across
+      // all values of the grouping expression, the same as for grouping().
+      // The zero-argument version of grouping_id() generates the bit vector for all
+      // the grouping expressions.
+      List<Expr> inputExprs = aggExpr.getChildren().size() > 0 ?
+          aggExpr.getChildren() : groupingExprs;
+      long val = 0;
+      for (Expr child : inputExprs) {
+        val <<= 1;
+        val |= aggInfo.getGroupingExprs().contains(child) ? 0 : 1;
+      }
+      return NumericLiteral.create(val, Type.BIGINT);
+    }
+  }
+
+  /**
    * Wraps the given groupingExprs into a CASE that switches on the valid tuple id.
    */
   private List<Expr> getTransposeGroupingExprs(
@@ -680,10 +730,6 @@ public class MultiAggregateInfo {
 
   public boolean getIsGroupingSet() { return isGroupingSet_; }
 
-  public void setGenerateGroupingId(boolean generateGroupingId) {
-    generateGroupingId_ = generateGroupingId;
-  }
-
   /**
    * Determines which aggregate exprs are required to produce the final query result.
    * Eliminates irrelevant aggregation classes and simplifies the aggregation plan,
diff --git a/fe/src/main/java/org/apache/impala/catalog/AggregateFunction.java b/fe/src/main/java/org/apache/impala/catalog/AggregateFunction.java
index f4786b3..6b7b686 100644
--- a/fe/src/main/java/org/apache/impala/catalog/AggregateFunction.java
+++ b/fe/src/main/java/org/apache/impala/catalog/AggregateFunction.java
@@ -156,6 +156,24 @@ public class AggregateFunction extends Function {
     return fn;
   }
 
+  /**
+   * Create a builtin with no concrete implementation that will be rewritten during
+   * analysis, e.g. grouping() and grouping_id().
+   */
+  public static AggregateFunction createRewrittenBuiltin(Db db, String name,
+      List<Type> argTypes, Type retType, boolean ignoresDistinct,
+      boolean isAnalyticFn, boolean returnsNonNullOnEmpty) {
+    AggregateFunction fn = new AggregateFunction(new FunctionName(db.getName(), name),
+        argTypes, retType, false);
+    fn.setBinaryType(TFunctionBinaryType.BUILTIN);
+    fn.ignoresDistinct_ = ignoresDistinct;
+    fn.isAnalyticFn_ = isAnalyticFn;
+    fn.isAggregateFn_ = true;
+    fn.returnsNonNullOnEmpty_ = returnsNonNullOnEmpty;
+    fn.setIsPersistent(true);
+    return fn;
+  }
+
   public static AggregateFunction createAnalyticBuiltin(Db db, String name,
       List<Type> argTypes, Type retType, Type intermediateType) {
     return createAnalyticBuiltin(db, name, argTypes, retType, intermediateType, null,
diff --git a/fe/src/main/java/org/apache/impala/catalog/BuiltinsDb.java b/fe/src/main/java/org/apache/impala/catalog/BuiltinsDb.java
index 36dc88c..bad84cb 100644
--- a/fe/src/main/java/org/apache/impala/catalog/BuiltinsDb.java
+++ b/fe/src/main/java/org/apache/impala/catalog/BuiltinsDb.java
@@ -1403,6 +1403,17 @@ public class BuiltinsDb extends Db {
       db.addBuiltin(AggregateFunction.createAnalyticBuiltin(
             db, "lead", Lists.newArrayList(t, Type.BIGINT), t, t));
     }
+
+    // Grouping ID functions for grouping sets. These are rewritten during analysis.
+    db.addBuiltin(AggregateFunction.createRewrittenBuiltin(db, "grouping_id",
+        Collections.<Type>emptyList(), Type.BIGINT, /*ignoresDistinct=*/ true,
+        /*isAnalyticFn=*/ false, /*returnsNonNullOnEmpty=*/ true));
+    // grouping() can take any grouping expression as input.
+    for (Type t: Type.getSupportedTypes()) {
+      db.addBuiltin(AggregateFunction.createRewrittenBuiltin(db, "grouping",
+          Lists.newArrayList(t), Type.TINYINT, /*ignoresDistinct=*/ true,
+          /*isAnalyticFn=*/ false, /*returnsNonNullOnEmpty=*/ true));
+    }
   }
 
   // Resolve the intermediate data type by searching for one in the
diff --git a/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java b/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java
index 0a86d20..85d2e0c 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AnalyzeStmtsTest.java
@@ -2493,6 +2493,90 @@ public class AnalyzeStmtsTest extends AnalyzerTest {
         "from functional.alltypes " +
         "group by rollup(int_col, bool_col)",
         "cannot combine SELECT DISTINCT with aggregate functions or GROUP BY");
+
+    // grouping_id() and grouping() are aggregate functions that can be used in any
+    // GROUP BY, even though they are only meaningful in conjunction with
+    // CUBE/ROLLUP/GROUPING SETS. grouping() must reference exactly one of the grouping
+    // columns.
+    AnalyzesOk("select int_col, string_col, grouping_id(), grouping(int_col), " +
+        "grouping(string_col) " +
+        "from functional.alltypes " +
+        "group by rollup(int_col, string_col)");
+    AnalyzesOk("select int_col, string_col, grouping_id(), grouping(int_col), " +
+        "grouping(string_col) " +
+        "from functional.alltypes " +
+        "group by cube(int_col, string_col)");
+    AnalyzesOk("select int_col, string_col, grouping_id(), grouping(int_col), " +
+        "grouping(string_col) " +
+        "from functional.alltypes " +
+        "group by grouping sets((), (int_col, string_col))");
+
+    // grouping_id() can reference a variable number of grouping columns.
+    AnalyzesOk("select int_col, string_col, grouping_id(int_col, string_col), " +
+        "grouping(string_col) " +
+        "from functional.alltypes " +
+        "group by rollup(int_col, string_col)");
+
+    // Non-trivial expressions involving grouping_id().
+    AnalyzesOk("select bitand(grouping_id(), 1), cast(grouping(int_col) as string) " +
+        "from functional.alltypes " +
+        "group by rollup(int_col, string_col)");
+
+    // grouping() can reference non-trivial grouping expressions
+    AnalyzesOk("select int_col * 2, string_col, grouping(int_col * 2),  count(*) " +
+        "from functional.alltypes " +
+        "group by rollup(1, 2)");
+
+    // grouping_id() and grouping() can be used in conjunction with a plain GROUP BY.
+    AnalyzesOk("select int_col, string_col, grouping_id() " +
+        "from functional.alltypes " +
+        "group by int_col, string_col");
+    AnalyzesOk("select int_col, string_col, grouping(int_col) " +
+        "from functional.alltypes " +
+        "group by int_col, string_col");
+
+    // grouping() and grouping_id() can be used in the degenerate case of a single
+    // grouping set.
+    AnalyzesOk("select int_col, string_col, grouping_id(), grouping(int_col) " +
+        "from functional.alltypes " +
+        "group by grouping sets((int_col, string_col))");
+
+    // grouping() raises analysis error if given wrong number of args.
+    AnalysisError("select int_col, string_col, grouping(int_col, string_col) " +
+        "from functional.alltypes " +
+        "group by grouping sets((), (int_col, string_col))",
+        "No matching function with signature: grouping(INT, STRING).");
+    AnalysisError("select int_col, string_col, grouping() " +
+        "from functional.alltypes " +
+        "group by grouping sets((), (int_col, string_col))",
+        "No matching function with signature: grouping().");
+
+    // grouping() and grouping_id() must reference grouping expressions.
+    AnalysisError("select int_col, grouping('test') from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "'test' is not a grouping expression");
+    AnalysisError("select int_col, grouping(1) from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "1 is not a grouping expression");
+    AnalysisError("select grouping(tinyint_col) from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "tinyint_col is not a grouping expression");
+    AnalysisError("select count(*), grouping(count(*)) from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "aggregate function must not contain aggregate parameters: grouping(count(*))");
+    AnalysisError("select int_col, grouping_id(int_col, 'test') " +
+        "from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "'test' is not a grouping expression");
+    AnalysisError("select int_col, grouping_id(1) from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "1 is not a grouping expression");
+    AnalysisError("select grouping_id(int_col, tinyint_col) from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "tinyint_col is not a grouping expression");
+    AnalysisError("select count(*), grouping_id(count(*)) from functional.alltypes " +
+        "group by rollup(int_col, string_col)",
+        "count(*) is not a grouping expression");
   }
 
   @Test
diff --git a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
index 0eae3c9..4539f03 100644
--- a/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/ParserTest.java
@@ -3393,8 +3393,8 @@ public class ParserTest extends FrontendTestBase {
         "select from t\n" +
         "       ^\n" +
         "Encountered: FROM\n" +
-        "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, IF, " +
-        "INTERVAL, LEFT, NOT, NULL, REPLACE, RIGHT, STRAIGHT_JOIN, TRUNCATE, TRUE, " +
+        "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, GROUPING, " +
+        "IF, INTERVAL, LEFT, NOT, NULL, REPLACE, RIGHT, STRAIGHT_JOIN, TRUNCATE, TRUE, " +
         "IDENTIFIER");
 
     // missing from
@@ -3420,8 +3420,8 @@ public class ParserTest extends FrontendTestBase {
         "select c, b, c from t where\n" +
         "                           ^\n" +
         "Encountered: EOF\n" +
-        "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, IF, INTERVAL, LEFT, NOT, " +
-        "NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER");
+        "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, GROUPING, IF, INTERVAL, " +
+        "LEFT, NOT, NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER");
 
     // missing predicate in where clause (group by)
     ParserError("select c, b, c from t where group by a, b",
@@ -3429,8 +3429,8 @@ public class ParserTest extends FrontendTestBase {
         "select c, b, c from t where group by a, b\n" +
         "                            ^\n" +
         "Encountered: GROUP\n" +
-        "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, IF, INTERVAL, LEFT, NOT, " +
-        "NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER");
+        "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, GROUPING, IF, INTERVAL, " +
+        "LEFT, NOT, NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER");
 
     // unmatched string literal starting with "
     ParserError("select c, \"b, c from t",
@@ -3490,8 +3490,8 @@ public class ParserTest extends FrontendTestBase {
         "...c,c,c,c,c,c,c,c,cd,c,d,d, ,c, from t\n" +
         "                             ^\n" +
         "Encountered: COMMA\n" +
-        "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, IF, INTERVAL, LEFT, NOT, " +
-        "NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER");
+        "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, GROUPING, IF, INTERVAL, " +
+        "LEFT, NOT, NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER");
 
     // Parsing identifiers that have different names printed as EXPECTED
     ParserError("DROP DATA SRC foo",
@@ -3527,7 +3527,7 @@ public class ParserTest extends FrontendTestBase {
          "\n" +
          "^\n" +
          "Encountered: EOF\n" +
-         "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, " +
+         "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, GROUPING, " +
          "IF, INTERVAL, LEFT, NOT, NULL, REPLACE, RIGHT, " +
          "STRAIGHT_JOIN, TRUNCATE, TRUE, IDENTIFIER\n");
     ParserError("SELECT\n\n",
@@ -3535,7 +3535,7 @@ public class ParserTest extends FrontendTestBase {
          "\n" +
          "^\n" +
          "Encountered: EOF\n" +
-         "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, " +
+         "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, GROUPING, " +
          "IF, INTERVAL, LEFT, NOT, NULL, REPLACE, RIGHT, " +
          "STRAIGHT_JOIN, TRUNCATE, TRUE, IDENTIFIER\n");
   }
@@ -3737,6 +3737,22 @@ public class ParserTest extends FrontendTestBase {
     ParserError("SELECT a FROM foo GROUP BY GROUPING SETS((), ((a), (b))");
   }
 
+  /**
+   * Test that grouping() can be parsed as a function name, but not as a general
+   * identifier.
+   */
+  @Test
+  public void TestGroupingKeyword() {
+    ParsesOk("SELECT a, grouping(a) FROM foo GROUP BY GROUPING SETS((a, b))");
+    ParsesOk("SELECT a, gRoUpInG(a) FROM foo GROUP BY GROUPING SETS((a, b))");
+    ParsesOk("SELECT a, `grouping`(a) FROM foo GROUP BY GROUPING SETS((a, b))");
+
+    ParsesOk("SELECT a, grouping(a) as `grouping` FROM foo " +
+        "GROUP BY GROUPING SETS((a, b))");
+    ParserError("SELECT a, grouping(a) as grouping FROM foo " +
+        "GROUP BY GROUPING SETS((a, b))");
+  }
+
   @Test
   public void TestSet() {
     ParsesOk("SET foo='bar'");
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test b/testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test
index a81d0af..dde2a04 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/grouping-sets.test
@@ -1195,3 +1195,137 @@ PLAN-ROOT SINK
    HDFS partitions=11/11 files=11 size=814.73KB
    row-size=19B cardinality=11.00K
 ====
+# grouping() and grouping_id() are implemented in transpose aggregate.
+select int_col, string_col, grouping_id(), grouping(string_col), count(*)
+from functional.alltypesagg
+group by rollup(int_col, string_col)
+having grouping_id() > 0
+---- PLAN
+PLAN-ROOT SINK
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN 0 WHEN 2 THEN 1 WHEN 3 THEN 3 END), aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN 0 WHEN 2 THEN 1 WHEN 3 THEN 1 END), aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 END
+|  having: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN 0 WHEN 2 THEN 1 WHEN 3 THEN 3 END) > 0
+|  row-size=37B cardinality=1.20K
+|
+01:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: NULL, NULL
+|  row-size=75B cardinality=11.96K
+|
+00:SCAN HDFS [functional.alltypesagg]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   row-size=19B cardinality=11.00K
+---- DISTRIBUTEDPLAN
+PLAN-ROOT SINK
+|
+05:EXCHANGE [UNPARTITIONED]
+|
+02:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN 0 WHEN 2 THEN 1 WHEN 3 THEN 3 END), aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN 0 WHEN 2 THEN 1 WHEN 3 THEN 1 END), aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN count(*) WHEN 2 THEN count(*) WHEN 3 THEN count(*) END)
+|  group by: CASE valid_tid(1,2,3) WHEN 1 THEN int_col WHEN 2 THEN int_col WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN string_col WHEN 2 THEN NULL WHEN 3 THEN NULL END, CASE valid_tid(1,2,3) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 END
+|  having: aggif(valid_tid(1,2,3) IN (1, 2, 3), CASE valid_tid(1,2,3) WHEN 1 THEN 0 WHEN 2 THEN 1 WHEN 3 THEN 3 END) > 0
+|  row-size=37B cardinality=1.20K
+|
+04:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count:merge(*)
+|    group by: int_col, string_col
+|  Class 1
+|    output: count:merge(*)
+|    group by: int_col, NULL
+|  Class 2
+|    output: count:merge(*)
+|    group by: NULL, NULL
+|  row-size=75B cardinality=11.96K
+|
+03:EXCHANGE [HASH(CASE valid_tid(1,2,3) WHEN 1 THEN murmur_hash(int_col) WHEN 2 THEN murmur_hash(int_col) WHEN 3 THEN murmur_hash(NULL) END,CASE valid_tid(1,2,3) WHEN 1 THEN murmur_hash(string_col) WHEN 2 THEN murmur_hash(NULL) WHEN 3 THEN murmur_hash(NULL) END)]
+|
+01:AGGREGATE [STREAMING]
+|  Class 0
+|    output: count(*)
+|    group by: int_col, string_col
+|  Class 1
+|    output: count(*)
+|    group by: int_col, NULL
+|  Class 2
+|    output: count(*)
+|    group by: NULL, NULL
+|  row-size=75B cardinality=11.96K
+|
+00:SCAN HDFS [functional.alltypesagg]
+   HDFS partitions=11/11 files=11 size=814.73KB
+   row-size=19B cardinality=11.00K
+====
+# grouping() and grouping_id() with plain GROUP BY. They are constants in this
+# case so do not show up in plan.
+select
+  grouping_id(int_col, string_col),
+  grouping_id(),
+  grouping(int_col),
+  grouping(string_col),
+  int_col,
+  string_col,
+  count(*)
+from functional.alltypestiny
+group by int_col, string_col
+---- PLAN
+PLAN-ROOT SINK
+|
+01:AGGREGATE [FINALIZE]
+|  output: count(*)
+|  group by: int_col, string_col
+|  row-size=25B cardinality=4
+|
+00:SCAN HDFS [functional.alltypestiny]
+   HDFS partitions=4/4 files=4 size=460B
+   row-size=17B cardinality=8
+====
+# grouping() and grouping_id() with plain GROUP BY and multiple agg classes. The
+# function calls are replaced with constants so do not show up in plan.
+select
+  grouping_id(int_col, string_col),
+  grouping(int_col),
+  grouping(string_col),
+  int_col,
+  string_col,
+  count(distinct tinyint_col),
+  count(distinct timestamp_col)
+from functional.alltypestiny
+group by int_col, string_col
+---- PLAN
+PLAN-ROOT SINK
+|
+03:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(2,4) = 2, count(tinyint_col)), aggif(valid_tid(2,4) = 4, count(timestamp_col))
+|  group by: CASE valid_tid(2,4) WHEN 2 THEN int_col WHEN 4 THEN int_col END, CASE valid_tid(2,4) WHEN 2 THEN string_col WHEN 4 THEN string_col END
+|  row-size=32B cardinality=8
+|
+02:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: count(tinyint_col)
+|    group by: int_col, string_col
+|  Class 1
+|    output: count(timestamp_col)
+|    group by: int_col, string_col
+|  row-size=50B cardinality=8
+|
+01:AGGREGATE
+|  Class 0
+|    group by: int_col, string_col, tinyint_col
+|  Class 1
+|    group by: int_col, string_col, timestamp_col
+|  row-size=51B cardinality=16
+|
+00:SCAN HDFS [functional.alltypestiny]
+   HDFS partitions=4/4 files=4 size=460B
+   row-size=34B cardinality=8
+====
diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test b/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test
index f60fa9b..4c3845e 100644
--- a/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test
+++ b/testdata/workloads/functional-planner/queries/PlannerTest/tpcds-all.test
@@ -12212,3 +12212,1416 @@ PLAN-ROOT SINK
    runtime filters: RF000 -> ss_store_sk, RF002 -> ss_sold_date_sk
    row-size=16B cardinality=2.88M
 ====
+# TPCDS-Q70
+select
+    sum(ss_net_profit) as total_sum
+   ,s_state
+   ,s_county
+   ,grouping(s_state)+grouping(s_county) as lochierarchy
+   ,rank() over (
+        partition by grouping(s_state)+grouping(s_county),
+        case when grouping(s_county) = 0 then s_state end
+        order by sum(ss_net_profit) desc) as rank_within_parent
+ from
+    store_sales
+   ,date_dim       d1
+   ,store
+ where
+    d1.d_month_seq between 1212 and 1212+11
+ and d1.d_date_sk = ss_sold_date_sk
+ and s_store_sk  = ss_store_sk
+ and s_state in
+             ( select s_state
+               from  (select s_state as s_state,
+                            rank() over ( partition by s_state order by sum(ss_net_profit) desc) as ranking
+                      from   store_sales, store, date_dim
+                      where  d_month_seq between 1212 and 1212+11
+                            and d_date_sk = ss_sold_date_sk
+                            and s_store_sk  = ss_store_sk
+                      group by s_state
+                     ) tmp1
+               where ranking <= 5
+             )
+ group by rollup(s_state,s_county)
+ order by
+   lochierarchy desc
+  -- Modified from original because Impala does not allow aliases within order by
+  -- expressions, except at the top level.
+  ,case when grouping(s_state)+grouping(s_county) = 0 then s_state end
+  ,rank_within_parent
+ limit 100
+---- PLAN
+Max Per-Host Resource Reservation: Memory=52.77MB Threads=7
+Per-Host Resource Estimates: Memory=437MB
+PLAN-ROOT SINK
+|
+19:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 [...]
+|  row-size=50B cardinality=3
+|
+18:ANALYTIC
+|  functions: rank()
+|  partition by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END), CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) = 0 THEN CASE valid_tid(11,12,13) WHEN 11 THEN s_state WHEN 12 THEN s_state WHEN 13 THEN NULL END END
+|  order by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN sum(ss_net_profit) WHEN 12 THEN sum(ss_net_profit) WHEN 13 THEN sum(ss_net_profit) END) DESC
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=54B cardinality=3
+|
+17:SORT
+|  order by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) ASC NULLS FIRST, CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) = 0 THEN CASE valid_tid(11,12,13) WHEN 11 THEN s_state WHEN 12 THEN s_state WHEN 13 THEN NULL EN [...]
+|  row-size=46B cardinality=3
+|
+16:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN sum(ss_net_profit) WHEN 12 THEN sum(ss_net_profit) WHEN 13 THEN sum(ss_net_profit) END), aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END), aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END)
+|  group by: CASE valid_tid(11,12,13) WHEN 11 THEN s_state WHEN 12 THEN s_state WHEN 13 THEN NULL END, CASE valid_tid(11,12,13) WHEN 11 THEN s_county WHEN 12 THEN NULL WHEN 13 THEN NULL END, CASE valid_tid(11,12,13) WHEN 11 THEN 11 WHEN 12 THEN 12 WHEN 13 THEN 13 END
+|  row-size=46B cardinality=3
+|
+15:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum(ss_net_profit)
+|    group by: s_state, s_county
+|  Class 1
+|    output: sum(ss_net_profit)
+|    group by: s_state, NULL
+|  Class 2
+|    output: sum(ss_net_profit)
+|    group by: NULL, NULL
+|  row-size=141B cardinality=3
+|
+14:HASH JOIN [LEFT SEMI JOIN]
+|  hash predicates: s_state = s_state
+|  runtime filters: RF000 <- s_state
+|  row-size=67B cardinality=2.88M
+|
+|--11:SELECT
+|  |  predicates: rank() <= 5
+|  |  row-size=38B cardinality=1
+|  |
+|  10:ANALYTIC
+|  |  functions: rank()
+|  |  partition by: s_state
+|  |  order by: sum(ss_net_profit) DESC
+|  |  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  |  row-size=38B cardinality=1
+|  |
+|  09:SORT
+|  |  order by: s_state ASC NULLS FIRST, sum(ss_net_profit) DESC
+|  |  row-size=30B cardinality=1
+|  |
+|  08:AGGREGATE [FINALIZE]
+|  |  output: sum(ss_net_profit)
+|  |  group by: s_state
+|  |  row-size=30B cardinality=1
+|  |
+|  07:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ss_sold_date_sk = d_date_sk
+|  |  runtime filters: RF006 <- d_date_sk
+|  |  row-size=38B cardinality=2.88M
+|  |
+|  |--05:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_month_seq <= 1223, d_month_seq >= 1212
+|  |     row-size=8B cardinality=7.30K
+|  |
+|  06:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ss_store_sk = s_store_sk
+|  |  runtime filters: RF008 <- s_store_sk
+|  |  row-size=30B cardinality=2.88M
+|  |
+|  |--04:SCAN HDFS [tpcds.store]
+|  |     HDFS partitions=1/1 files=1 size=3.08KB
+|  |     row-size=18B cardinality=12
+|  |
+|  03:SCAN HDFS [tpcds.store_sales]
+|     HDFS partitions=1824/1824 files=1824 size=346.60MB
+|     runtime filters: RF006 -> ss_sold_date_sk, RF008 -> ss_store_sk
+|     row-size=12B cardinality=2.88M
+|
+13:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF002 <- s_store_sk
+|  row-size=67B cardinality=2.88M
+|
+|--02:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     runtime filters: RF000 -> s_state
+|     row-size=47B cardinality=12
+|
+12:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_sold_date_sk = d1.d_date_sk
+|  runtime filters: RF004 <- d1.d_date_sk
+|  row-size=20B cardinality=2.88M
+|
+|--01:SCAN HDFS [tpcds.date_dim d1]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d1.d_month_seq <= 1223, d1.d_month_seq >= 1212
+|     row-size=8B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF002 -> ss_store_sk, RF004 -> ss_sold_date_sk
+   row-size=12B cardinality=2.88M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=97.39MB Threads=16
+Per-Host Resource Estimates: Memory=554MB
+PLAN-ROOT SINK
+|
+30:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 [...]
+|  limit: 100
+|
+19:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 [...]
+|  row-size=50B cardinality=3
+|
+18:ANALYTIC
+|  functions: rank()
+|  partition by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END), CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) = 0 THEN CASE valid_tid(11,12,13) WHEN 11 THEN s_state WHEN 12 THEN s_state WHEN 13 THEN NULL END END
+|  order by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN sum(ss_net_profit) WHEN 12 THEN sum(ss_net_profit) WHEN 13 THEN sum(ss_net_profit) END) DESC
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=54B cardinality=3
+|
+17:SORT
+|  order by: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) ASC NULLS FIRST, CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) = 0 THEN CASE valid_tid(11,12,13) WHEN 11 THEN s_state WHEN 12 THEN s_state WHEN 13 THEN NULL EN [...]
+|  row-size=46B cardinality=3
+|
+29:EXCHANGE [HASH(aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END) + aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END),CASE WHEN aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END) = 0 THEN CASE valid_tid(11,12,13) WHEN 11 THEN s_state WHEN 12 THEN s_state WHEN 13 THEN NULL END END)]
+|
+16:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN sum(ss_net_profit) WHEN 12 THEN sum(ss_net_profit) WHEN 13 THEN sum(ss_net_profit) END), aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 0 WHEN 13 THEN 1 END), aggif(valid_tid(11,12,13) IN (11, 12, 13), CASE valid_tid(11,12,13) WHEN 11 THEN 0 WHEN 12 THEN 1 WHEN 13 THEN 1 END)
+|  group by: CASE valid_tid(11,12,13) WHEN 11 THEN s_state WHEN 12 THEN s_state WHEN 13 THEN NULL END, CASE valid_tid(11,12,13) WHEN 11 THEN s_county WHEN 12 THEN NULL WHEN 13 THEN NULL END, CASE valid_tid(11,12,13) WHEN 11 THEN 11 WHEN 12 THEN 12 WHEN 13 THEN 13 END
+|  row-size=46B cardinality=3
+|
+28:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum:merge(ss_net_profit)
+|    group by: s_state, s_county
+|  Class 1
+|    output: sum:merge(ss_net_profit)
+|    group by: s_state, NULL
+|  Class 2
+|    output: sum:merge(ss_net_profit)
+|    group by: NULL, NULL
+|  row-size=141B cardinality=3
+|
+27:EXCHANGE [HASH(CASE valid_tid(11,12,13) WHEN 11 THEN murmur_hash(s_state) WHEN 12 THEN murmur_hash(s_state) WHEN 13 THEN murmur_hash(NULL) END,CASE valid_tid(11,12,13) WHEN 11 THEN murmur_hash(s_county) WHEN 12 THEN murmur_hash(NULL) WHEN 13 THEN murmur_hash(NULL) END)]
+|
+15:AGGREGATE [STREAMING]
+|  Class 0
+|    output: sum(ss_net_profit)
+|    group by: s_state, s_county
+|  Class 1
+|    output: sum(ss_net_profit)
+|    group by: s_state, NULL
+|  Class 2
+|    output: sum(ss_net_profit)
+|    group by: NULL, NULL
+|  row-size=141B cardinality=3
+|
+14:HASH JOIN [LEFT SEMI JOIN, BROADCAST]
+|  hash predicates: s_state = s_state
+|  runtime filters: RF000 <- s_state
+|  row-size=67B cardinality=2.88M
+|
+|--26:EXCHANGE [BROADCAST]
+|  |
+|  11:SELECT
+|  |  predicates: rank() <= 5
+|  |  row-size=38B cardinality=1
+|  |
+|  10:ANALYTIC
+|  |  functions: rank()
+|  |  partition by: s_state
+|  |  order by: sum(ss_net_profit) DESC
+|  |  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  |  row-size=38B cardinality=1
+|  |
+|  09:SORT
+|  |  order by: s_state ASC NULLS FIRST, sum(ss_net_profit) DESC
+|  |  row-size=30B cardinality=1
+|  |
+|  25:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(ss_net_profit)
+|  |  group by: s_state
+|  |  row-size=30B cardinality=1
+|  |
+|  24:EXCHANGE [HASH(s_state)]
+|  |
+|  08:AGGREGATE [STREAMING]
+|  |  output: sum(ss_net_profit)
+|  |  group by: s_state
+|  |  row-size=30B cardinality=1
+|  |
+|  07:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ss_sold_date_sk = d_date_sk
+|  |  runtime filters: RF006 <- d_date_sk
+|  |  row-size=38B cardinality=2.88M
+|  |
+|  |--23:EXCHANGE [BROADCAST]
+|  |  |
+|  |  05:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_month_seq <= 1223, d_month_seq >= 1212
+|  |     row-size=8B cardinality=7.30K
+|  |
+|  06:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ss_store_sk = s_store_sk
+|  |  runtime filters: RF008 <- s_store_sk
+|  |  row-size=30B cardinality=2.88M
+|  |
+|  |--22:EXCHANGE [BROADCAST]
+|  |  |
+|  |  04:SCAN HDFS [tpcds.store]
+|  |     HDFS partitions=1/1 files=1 size=3.08KB
+|  |     row-size=18B cardinality=12
+|  |
+|  03:SCAN HDFS [tpcds.store_sales]
+|     HDFS partitions=1824/1824 files=1824 size=346.60MB
+|     runtime filters: RF006 -> ss_sold_date_sk, RF008 -> ss_store_sk
+|     row-size=12B cardinality=2.88M
+|
+13:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF002 <- s_store_sk
+|  row-size=67B cardinality=2.88M
+|
+|--21:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     runtime filters: RF000 -> s_state
+|     row-size=47B cardinality=12
+|
+12:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_sold_date_sk = d1.d_date_sk
+|  runtime filters: RF004 <- d1.d_date_sk
+|  row-size=20B cardinality=2.88M
+|
+|--20:EXCHANGE [BROADCAST]
+|  |
+|  01:SCAN HDFS [tpcds.date_dim d1]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d1.d_month_seq <= 1223, d1.d_month_seq >= 1212
+|     row-size=8B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF002 -> ss_store_sk, RF004 -> ss_sold_date_sk
+   row-size=12B cardinality=2.88M
+====
+# TPCDS-Q80
+with ssr as
+ (select  s_store_id as store_id,
+          sum(ss_ext_sales_price) as sales,
+          sum(coalesce(sr_return_amt, 0)) as `returns`,
+          sum(ss_net_profit - coalesce(sr_net_loss, 0)) as profit
+  from store_sales left outer join store_returns on
+         (ss_item_sk = sr_item_sk and ss_ticket_number = sr_ticket_number),
+     date_dim,
+     store,
+     item,
+     promotion
+ where ss_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and ss_store_sk = s_store_sk
+       and ss_item_sk = i_item_sk
+       and i_current_price > 50
+       and ss_promo_sk = p_promo_sk
+       and p_channel_tv = 'N'
+ group by s_store_id)
+ ,
+ csr as
+ (select  cp_catalog_page_id as catalog_page_id,
+          sum(cs_ext_sales_price) as sales,
+          sum(coalesce(cr_return_amount, 0)) as `returns`,
+          sum(cs_net_profit - coalesce(cr_net_loss, 0)) as profit
+  from catalog_sales left outer join catalog_returns on
+         (cs_item_sk = cr_item_sk and cs_order_number = cr_order_number),
+     date_dim,
+     catalog_page,
+     item,
+     promotion
+ where cs_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+        and cs_catalog_page_sk = cp_catalog_page_sk
+       and cs_item_sk = i_item_sk
+       and i_current_price > 50
+       and cs_promo_sk = p_promo_sk
+       and p_channel_tv = 'N'
+group by cp_catalog_page_id)
+ ,
+ wsr as
+ (select  web_site_id,
+          sum(ws_ext_sales_price) as sales,
+          sum(coalesce(wr_return_amt, 0)) as `returns`,
+          sum(ws_net_profit - coalesce(wr_net_loss, 0)) as profit
+  from web_sales left outer join web_returns on
+         (ws_item_sk = wr_item_sk and ws_order_number = wr_order_number),
+     date_dim,
+     web_site,
+     item,
+     promotion
+ where ws_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+        and ws_web_site_sk = web_site_sk
+       and ws_item_sk = i_item_sk
+       and i_current_price > 50
+       and ws_promo_sk = p_promo_sk
+       and p_channel_tv = 'N'
+group by web_site_id)
+  select  channel
+        , id
+        , sum(sales) as sales
+        , sum(`returns`) as `returns`
+        , sum(profit) as profit
+ from
+ (select 'store channel' as channel
+        , 'store' || store_id as id
+        , sales
+        , `returns`
+        , profit
+ from   ssr
+ union all
+ select 'catalog channel' as channel
+        , 'catalog_page' || catalog_page_id as id
+        , sales
+        , `returns`
+        , profit
+ from  csr
+ union all
+ select 'web channel' as channel
+        , 'web_site' || web_site_id as id
+        , sales
+        , `returns`
+        , profit
+ from   wsr
+ ) x
+ group by rollup (channel, id)
+ order by channel
+         ,id
+ limit 100
+---- PLAN
+Max Per-Host Resource Reservation: Memory=66.95MB Threads=7
+Per-Host Resource Estimates: Memory=436MB
+PLAN-ROOT SINK
+|
+39:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(26,27,28) WHEN 26 THEN channel WHEN 27 THEN channel WHEN 28 THEN NULL END ASC, CASE valid_tid(26,27,28) WHEN 26 THEN id WHEN 27 THEN NULL WHEN 28 THEN NULL END ASC
+|  row-size=72B cardinality=100
+|
+38:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(26,27,28) IN (26, 27, 28), CASE valid_tid(26,27,28) WHEN 26 THEN sum(sales) WHEN 27 THEN sum(sales) WHEN 28 THEN sum(sales) END), aggif(valid_tid(26,27,28) IN (26, 27, 28), CASE valid_tid(26,27,28) WHEN 26 THEN sum(`returns`) WHEN 27 THEN sum(`returns`) WHEN 28 THEN sum(`returns`) END), aggif(valid_tid(26,27,28) IN (26, 27, 28), CASE valid_tid(26,27,28) WHEN 26 THEN sum(profit) WHEN 27 THEN sum(profit) WHEN 28 THEN sum(profit) END)
+|  group by: CASE valid_tid(26,27,28) WHEN 26 THEN channel WHEN 27 THEN channel WHEN 28 THEN NULL END, CASE valid_tid(26,27,28) WHEN 26 THEN id WHEN 27 THEN NULL WHEN 28 THEN NULL END, CASE valid_tid(26,27,28) WHEN 26 THEN 26 WHEN 27 THEN 27 WHEN 28 THEN 28 END
+|  row-size=76B cardinality=11.56K
+|
+37:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: NULL, NULL
+|  row-size=216B cardinality=11.56K
+|
+00:UNION
+|  row-size=72B cardinality=11.56K
+|
+|--36:AGGREGATE [FINALIZE]
+|  |  output: sum(ws_ext_sales_price), sum(coalesce(wr_return_amt, 0)), sum(ws_net_profit - coalesce(wr_net_loss, 0))
+|  |  group by: web_site_id
+|  |  row-size=76B cardinality=15
+|  |
+|  35:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ws_web_site_sk = web_site_sk
+|  |  row-size=147B cardinality=71.46K
+|  |
+|  |--28:SCAN HDFS [tpcds.web_site]
+|  |     HDFS partitions=1/1 files=1 size=8.57KB
+|  |     row-size=32B cardinality=30
+|  |
+|  34:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ws_sold_date_sk = d_date_sk
+|  |  row-size=115B cardinality=71.46K
+|  |
+|  |--27:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  33:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ws_promo_sk = p_promo_sk
+|  |  runtime filters: RF020 <- p_promo_sk
+|  |  row-size=89B cardinality=71.46K
+|  |
+|  |--30:SCAN HDFS [tpcds.promotion]
+|  |     HDFS partitions=1/1 files=1 size=36.36KB
+|  |     predicates: p_channel_tv = 'N'
+|  |     row-size=17B cardinality=300
+|  |
+|  32:HASH JOIN [INNER JOIN]
+|  |  hash predicates: ws_item_sk = i_item_sk
+|  |  runtime filters: RF022 <- i_item_sk
+|  |  row-size=72B cardinality=71.94K
+|  |
+|  |--29:SCAN HDFS [tpcds.item]
+|  |     HDFS partitions=1/1 files=1 size=4.82MB
+|  |     predicates: i_current_price > 50
+|  |     row-size=12B cardinality=1.80K
+|  |
+|  31:HASH JOIN [LEFT OUTER JOIN]
+|  |  hash predicates: ws_item_sk = wr_item_sk, ws_order_number = wr_order_number
+|  |  row-size=60B cardinality=719.38K
+|  |
+|  |--26:SCAN HDFS [tpcds.web_returns]
+|  |     HDFS partitions=1/1 files=1 size=9.35MB
+|  |     runtime filters: RF022 -> tpcds.web_returns.wr_item_sk
+|  |     row-size=24B cardinality=71.76K
+|  |
+|  25:SCAN HDFS [tpcds.web_sales]
+|     HDFS partitions=1/1 files=1 size=140.07MB
+|     runtime filters: RF022 -> ws_item_sk, RF020 -> ws_promo_sk
+|     row-size=36B cardinality=719.38K
+|
+|--24:AGGREGATE [FINALIZE]
+|  |  output: sum(cs_ext_sales_price), sum(coalesce(cr_return_amount, 0)), sum(cs_net_profit - coalesce(cr_net_loss, 0))
+|  |  group by: cp_catalog_page_id
+|  |  row-size=76B cardinality=11.54K
+|  |
+|  23:HASH JOIN [INNER JOIN]
+|  |  hash predicates: cs_catalog_page_sk = cp_catalog_page_sk
+|  |  runtime filters: RF008 <- cp_catalog_page_sk
+|  |  row-size=147B cardinality=143.20K
+|  |
+|  |--16:SCAN HDFS [tpcds.catalog_page]
+|  |     HDFS partitions=1/1 files=1 size=1.56MB
+|  |     row-size=32B cardinality=11.72K
+|  |
+|  22:HASH JOIN [INNER JOIN]
+|  |  hash predicates: cs_sold_date_sk = d_date_sk
+|  |  runtime filters: RF010 <- d_date_sk
+|  |  row-size=115B cardinality=143.20K
+|  |
+|  |--15:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  21:HASH JOIN [INNER JOIN]
+|  |  hash predicates: cs_promo_sk = p_promo_sk
+|  |  runtime filters: RF012 <- p_promo_sk
+|  |  row-size=89B cardinality=143.20K
+|  |
+|  |--18:SCAN HDFS [tpcds.promotion]
+|  |     HDFS partitions=1/1 files=1 size=36.36KB
+|  |     predicates: p_channel_tv = 'N'
+|  |     row-size=17B cardinality=300
+|  |
+|  20:HASH JOIN [INNER JOIN]
+|  |  hash predicates: cs_item_sk = i_item_sk
+|  |  runtime filters: RF014 <- i_item_sk
+|  |  row-size=72B cardinality=144.16K
+|  |
+|  |--17:SCAN HDFS [tpcds.item]
+|  |     HDFS partitions=1/1 files=1 size=4.82MB
+|  |     predicates: i_current_price > 50
+|  |     row-size=12B cardinality=1.80K
+|  |
+|  19:HASH JOIN [LEFT OUTER JOIN]
+|  |  hash predicates: cs_item_sk = cr_item_sk, cs_order_number = cr_order_number
+|  |  row-size=60B cardinality=1.44M
+|  |
+|  |--14:SCAN HDFS [tpcds.catalog_returns]
+|  |     HDFS partitions=1/1 files=1 size=20.39MB
+|  |     runtime filters: RF014 -> tpcds.catalog_returns.cr_item_sk
+|  |     row-size=24B cardinality=144.07K
+|  |
+|  13:SCAN HDFS [tpcds.catalog_sales]
+|     HDFS partitions=1/1 files=1 size=282.20MB
+|     runtime filters: RF014 -> cs_item_sk, RF012 -> cs_promo_sk, RF008 -> cs_catalog_page_sk, RF010 -> cs_sold_date_sk
+|     row-size=36B cardinality=1.44M
+|
+12:AGGREGATE [FINALIZE]
+|  output: sum(ss_ext_sales_price), sum(coalesce(sr_return_amt, 0)), sum(ss_net_profit - coalesce(sr_net_loss, 0))
+|  group by: s_store_id
+|  row-size=76B cardinality=6
+|
+11:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=147B cardinality=286.13K
+|
+|--04:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=32B cardinality=12
+|
+10:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF002 <- d_date_sk
+|  row-size=115B cardinality=286.13K
+|
+|--03:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|     row-size=26B cardinality=7.30K
+|
+09:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_promo_sk = p_promo_sk
+|  runtime filters: RF004 <- p_promo_sk
+|  row-size=89B cardinality=286.13K
+|
+|--06:SCAN HDFS [tpcds.promotion]
+|     HDFS partitions=1/1 files=1 size=36.36KB
+|     predicates: p_channel_tv = 'N'
+|     row-size=17B cardinality=300
+|
+08:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF006 <- i_item_sk
+|  row-size=72B cardinality=288.04K
+|
+|--05:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     predicates: i_current_price > 50
+|     row-size=12B cardinality=1.80K
+|
+07:HASH JOIN [LEFT OUTER JOIN]
+|  hash predicates: ss_item_sk = sr_item_sk, ss_ticket_number = sr_ticket_number
+|  row-size=60B cardinality=2.88M
+|
+|--02:SCAN HDFS [tpcds.store_returns]
+|     HDFS partitions=1/1 files=1 size=31.19MB
+|     runtime filters: RF006 -> tpcds.store_returns.sr_item_sk
+|     row-size=24B cardinality=287.51K
+|
+01:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF006 -> ss_item_sk, RF004 -> ss_promo_sk, RF000 -> ss_store_sk, RF002 -> ss_sold_date_sk
+   row-size=36B cardinality=2.88M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=174.21MB Threads=39
+Per-Host Resource Estimates: Memory=1.23GB
+PLAN-ROOT SINK
+|
+63:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: CASE valid_tid(26,27,28) WHEN 26 THEN channel WHEN 27 THEN channel WHEN 28 THEN NULL END ASC, CASE valid_tid(26,27,28) WHEN 26 THEN id WHEN 27 THEN NULL WHEN 28 THEN NULL END ASC
+|  limit: 100
+|
+39:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(26,27,28) WHEN 26 THEN channel WHEN 27 THEN channel WHEN 28 THEN NULL END ASC, CASE valid_tid(26,27,28) WHEN 26 THEN id WHEN 27 THEN NULL WHEN 28 THEN NULL END ASC
+|  row-size=72B cardinality=100
+|
+38:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(26,27,28) IN (26, 27, 28), CASE valid_tid(26,27,28) WHEN 26 THEN sum(sales) WHEN 27 THEN sum(sales) WHEN 28 THEN sum(sales) END), aggif(valid_tid(26,27,28) IN (26, 27, 28), CASE valid_tid(26,27,28) WHEN 26 THEN sum(`returns`) WHEN 27 THEN sum(`returns`) WHEN 28 THEN sum(`returns`) END), aggif(valid_tid(26,27,28) IN (26, 27, 28), CASE valid_tid(26,27,28) WHEN 26 THEN sum(profit) WHEN 27 THEN sum(profit) WHEN 28 THEN sum(profit) END)
+|  group by: CASE valid_tid(26,27,28) WHEN 26 THEN channel WHEN 27 THEN channel WHEN 28 THEN NULL END, CASE valid_tid(26,27,28) WHEN 26 THEN id WHEN 27 THEN NULL WHEN 28 THEN NULL END, CASE valid_tid(26,27,28) WHEN 26 THEN 26 WHEN 27 THEN 27 WHEN 28 THEN 28 END
+|  row-size=76B cardinality=11.56K
+|
+62:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum:merge(sales), sum:merge(`returns`), sum:merge(profit)
+|    group by: NULL, NULL
+|  row-size=216B cardinality=11.56K
+|
+61:EXCHANGE [HASH(CASE valid_tid(26,27,28) WHEN 26 THEN murmur_hash(channel) WHEN 27 THEN murmur_hash(channel) WHEN 28 THEN murmur_hash(NULL) END,CASE valid_tid(26,27,28) WHEN 26 THEN murmur_hash(id) WHEN 27 THEN murmur_hash(NULL) WHEN 28 THEN murmur_hash(NULL) END)]
+|
+37:AGGREGATE [STREAMING]
+|  Class 0
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, id
+|  Class 1
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: channel, NULL
+|  Class 2
+|    output: sum(sales), sum(returns), sum(profit)
+|    group by: NULL, NULL
+|  row-size=216B cardinality=11.56K
+|
+00:UNION
+|  row-size=72B cardinality=11.56K
+|
+|--60:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(ws_ext_sales_price), sum:merge(coalesce(wr_return_amt, 0)), sum:merge(ws_net_profit - coalesce(wr_net_loss, 0))
+|  |  group by: web_site_id
+|  |  row-size=76B cardinality=15
+|  |
+|  59:EXCHANGE [HASH(web_site_id)]
+|  |
+|  36:AGGREGATE [STREAMING]
+|  |  output: sum(ws_ext_sales_price), sum(coalesce(wr_return_amt, 0)), sum(ws_net_profit - coalesce(wr_net_loss, 0))
+|  |  group by: web_site_id
+|  |  row-size=76B cardinality=15
+|  |
+|  35:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ws_web_site_sk = web_site_sk
+|  |  row-size=147B cardinality=71.46K
+|  |
+|  |--58:EXCHANGE [BROADCAST]
+|  |  |
+|  |  28:SCAN HDFS [tpcds.web_site]
+|  |     HDFS partitions=1/1 files=1 size=8.57KB
+|  |     row-size=32B cardinality=30
+|  |
+|  34:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ws_sold_date_sk = d_date_sk
+|  |  row-size=115B cardinality=71.46K
+|  |
+|  |--57:EXCHANGE [BROADCAST]
+|  |  |
+|  |  27:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  33:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ws_promo_sk = p_promo_sk
+|  |  runtime filters: RF020 <- p_promo_sk
+|  |  row-size=89B cardinality=71.46K
+|  |
+|  |--56:EXCHANGE [BROADCAST]
+|  |  |
+|  |  30:SCAN HDFS [tpcds.promotion]
+|  |     HDFS partitions=1/1 files=1 size=36.36KB
+|  |     predicates: p_channel_tv = 'N'
+|  |     row-size=17B cardinality=300
+|  |
+|  32:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: ws_item_sk = i_item_sk
+|  |  runtime filters: RF022 <- i_item_sk
+|  |  row-size=72B cardinality=71.94K
+|  |
+|  |--55:EXCHANGE [BROADCAST]
+|  |  |
+|  |  29:SCAN HDFS [tpcds.item]
+|  |     HDFS partitions=1/1 files=1 size=4.82MB
+|  |     predicates: i_current_price > 50
+|  |     row-size=12B cardinality=1.80K
+|  |
+|  31:HASH JOIN [LEFT OUTER JOIN, BROADCAST]
+|  |  hash predicates: ws_item_sk = wr_item_sk, ws_order_number = wr_order_number
+|  |  row-size=60B cardinality=719.38K
+|  |
+|  |--54:EXCHANGE [BROADCAST]
+|  |  |
+|  |  26:SCAN HDFS [tpcds.web_returns]
+|  |     HDFS partitions=1/1 files=1 size=9.35MB
+|  |     runtime filters: RF022 -> tpcds.web_returns.wr_item_sk
+|  |     row-size=24B cardinality=71.76K
+|  |
+|  25:SCAN HDFS [tpcds.web_sales]
+|     HDFS partitions=1/1 files=1 size=140.07MB
+|     runtime filters: RF022 -> ws_item_sk, RF020 -> ws_promo_sk
+|     row-size=36B cardinality=719.38K
+|
+|--53:AGGREGATE [FINALIZE]
+|  |  output: sum:merge(cs_ext_sales_price), sum:merge(coalesce(cr_return_amount, 0)), sum:merge(cs_net_profit - coalesce(cr_net_loss, 0))
+|  |  group by: cp_catalog_page_id
+|  |  row-size=76B cardinality=11.54K
+|  |
+|  52:EXCHANGE [HASH(cp_catalog_page_id)]
+|  |
+|  24:AGGREGATE [STREAMING]
+|  |  output: sum(cs_ext_sales_price), sum(coalesce(cr_return_amount, 0)), sum(cs_net_profit - coalesce(cr_net_loss, 0))
+|  |  group by: cp_catalog_page_id
+|  |  row-size=76B cardinality=11.54K
+|  |
+|  23:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: cs_catalog_page_sk = cp_catalog_page_sk
+|  |  runtime filters: RF008 <- cp_catalog_page_sk
+|  |  row-size=147B cardinality=143.20K
+|  |
+|  |--51:EXCHANGE [BROADCAST]
+|  |  |
+|  |  16:SCAN HDFS [tpcds.catalog_page]
+|  |     HDFS partitions=1/1 files=1 size=1.56MB
+|  |     row-size=32B cardinality=11.72K
+|  |
+|  22:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: cs_sold_date_sk = d_date_sk
+|  |  runtime filters: RF010 <- d_date_sk
+|  |  row-size=115B cardinality=143.20K
+|  |
+|  |--50:EXCHANGE [BROADCAST]
+|  |  |
+|  |  15:SCAN HDFS [tpcds.date_dim]
+|  |     HDFS partitions=1/1 files=1 size=9.84MB
+|  |     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|  |     row-size=26B cardinality=7.30K
+|  |
+|  21:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: cs_promo_sk = p_promo_sk
+|  |  runtime filters: RF012 <- p_promo_sk
+|  |  row-size=89B cardinality=143.20K
+|  |
+|  |--49:EXCHANGE [BROADCAST]
+|  |  |
+|  |  18:SCAN HDFS [tpcds.promotion]
+|  |     HDFS partitions=1/1 files=1 size=36.36KB
+|  |     predicates: p_channel_tv = 'N'
+|  |     row-size=17B cardinality=300
+|  |
+|  20:HASH JOIN [INNER JOIN, BROADCAST]
+|  |  hash predicates: cs_item_sk = i_item_sk
+|  |  runtime filters: RF014 <- i_item_sk
+|  |  row-size=72B cardinality=144.16K
+|  |
+|  |--48:EXCHANGE [BROADCAST]
+|  |  |
+|  |  17:SCAN HDFS [tpcds.item]
+|  |     HDFS partitions=1/1 files=1 size=4.82MB
+|  |     predicates: i_current_price > 50
+|  |     row-size=12B cardinality=1.80K
+|  |
+|  19:HASH JOIN [LEFT OUTER JOIN, BROADCAST]
+|  |  hash predicates: cs_item_sk = cr_item_sk, cs_order_number = cr_order_number
+|  |  row-size=60B cardinality=1.44M
+|  |
+|  |--47:EXCHANGE [BROADCAST]
+|  |  |
+|  |  14:SCAN HDFS [tpcds.catalog_returns]
+|  |     HDFS partitions=1/1 files=1 size=20.39MB
+|  |     runtime filters: RF014 -> tpcds.catalog_returns.cr_item_sk
+|  |     row-size=24B cardinality=144.07K
+|  |
+|  13:SCAN HDFS [tpcds.catalog_sales]
+|     HDFS partitions=1/1 files=1 size=282.20MB
+|     runtime filters: RF014 -> cs_item_sk, RF012 -> cs_promo_sk, RF008 -> cs_catalog_page_sk, RF010 -> cs_sold_date_sk
+|     row-size=36B cardinality=1.44M
+|
+46:AGGREGATE [FINALIZE]
+|  output: sum:merge(ss_ext_sales_price), sum:merge(coalesce(sr_return_amt, 0)), sum:merge(ss_net_profit - coalesce(sr_net_loss, 0))
+|  group by: s_store_id
+|  row-size=76B cardinality=6
+|
+45:EXCHANGE [HASH(s_store_id)]
+|
+12:AGGREGATE [STREAMING]
+|  output: sum(ss_ext_sales_price), sum(coalesce(sr_return_amt, 0)), sum(ss_net_profit - coalesce(sr_net_loss, 0))
+|  group by: s_store_id
+|  row-size=76B cardinality=6
+|
+11:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=147B cardinality=286.13K
+|
+|--44:EXCHANGE [BROADCAST]
+|  |
+|  04:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     row-size=32B cardinality=12
+|
+10:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF002 <- d_date_sk
+|  row-size=115B cardinality=286.13K
+|
+|--43:EXCHANGE [BROADCAST]
+|  |
+|  03:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_date <= DATE '1998-09-03', d_date >= DATE '1998-08-04'
+|     row-size=26B cardinality=7.30K
+|
+09:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_promo_sk = p_promo_sk
+|  runtime filters: RF004 <- p_promo_sk
+|  row-size=89B cardinality=286.13K
+|
+|--42:EXCHANGE [BROADCAST]
+|  |
+|  06:SCAN HDFS [tpcds.promotion]
+|     HDFS partitions=1/1 files=1 size=36.36KB
+|     predicates: p_channel_tv = 'N'
+|     row-size=17B cardinality=300
+|
+08:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF006 <- i_item_sk
+|  row-size=72B cardinality=288.04K
+|
+|--41:EXCHANGE [BROADCAST]
+|  |
+|  05:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     predicates: i_current_price > 50
+|     row-size=12B cardinality=1.80K
+|
+07:HASH JOIN [LEFT OUTER JOIN, BROADCAST]
+|  hash predicates: ss_item_sk = sr_item_sk, ss_ticket_number = sr_ticket_number
+|  row-size=60B cardinality=2.88M
+|
+|--40:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.store_returns]
+|     HDFS partitions=1/1 files=1 size=31.19MB
+|     runtime filters: RF006 -> tpcds.store_returns.sr_item_sk
+|     row-size=24B cardinality=287.51K
+|
+01:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF006 -> ss_item_sk, RF004 -> ss_promo_sk, RF000 -> ss_store_sk, RF002 -> ss_sold_date_sk
+   row-size=36B cardinality=2.88M
+====
+# TPCDS-Q86
+select
+    sum(ws_net_paid) as total_sum
+   ,i_category
+   ,i_class
+   ,grouping(i_category)+grouping(i_class) as lochierarchy
+   ,rank() over (
+        partition by grouping(i_category)+grouping(i_class),
+        case when grouping(i_class) = 0 then i_category end
+        order by sum(ws_net_paid) desc) as rank_within_parent
+ from
+    web_sales
+   ,date_dim       d1
+   ,item
+ where
+    d1.d_month_seq between 1212 and 1212+11
+ and d1.d_date_sk = ws_sold_date_sk
+ and i_item_sk  = ws_item_sk
+ group by rollup(i_category,i_class)
+ order by
+   lochierarchy desc,
+  -- Modified from original because Impala does not allow aliases within order by
+  -- expressions, except at the top level.
+   case when grouping(i_category)+grouping(i_class) = 0 then i_category end,
+   rank_within_parent
+ limit 100
+---- PLAN
+Max Per-Host Resource Reservation: Memory=33.88MB Threads=4
+Per-Host Resource Estimates: Memory=250MB
+PLAN-ROOT SINK
+|
+09:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN [...]
+|  row-size=50B cardinality=100
+|
+08:ANALYTIC
+|  functions: rank()
+|  partition by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END), CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN 3 THEN i_category WHEN 4 THEN i_category WHEN 5 THEN NULL END END
+|  order by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN sum(ws_net_paid) WHEN 4 THEN sum(ws_net_paid) WHEN 5 THEN sum(ws_net_paid) END) DESC
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=54B cardinality=1.00K
+|
+07:SORT
+|  order by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) ASC NULLS FIRST, CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN 3 THEN i_category WHEN 4 THEN i_category WHEN 5 THEN NULL END END ASC NULLS FIRST, aggif(valid_t [...]
+|  row-size=46B cardinality=1.00K
+|
+06:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN sum(ws_net_paid) WHEN 4 THEN sum(ws_net_paid) WHEN 5 THEN sum(ws_net_paid) END), aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END), aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END)
+|  group by: CASE valid_tid(3,4,5) WHEN 3 THEN i_category WHEN 4 THEN i_category WHEN 5 THEN NULL END, CASE valid_tid(3,4,5) WHEN 3 THEN i_class WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(3,4,5) WHEN 3 THEN 3 WHEN 4 THEN 4 WHEN 5 THEN 5 END
+|  row-size=46B cardinality=1.00K
+|
+05:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum(ws_net_paid)
+|    group by: i_category, i_class
+|  Class 1
+|    output: sum(ws_net_paid)
+|    group by: i_category, NULL
+|  Class 2
+|    output: sum(ws_net_paid)
+|    group by: NULL, NULL
+|  row-size=140B cardinality=1.00K
+|
+04:HASH JOIN [INNER JOIN]
+|  hash predicates: ws_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=70B cardinality=719.38K
+|
+|--02:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=46B cardinality=18.00K
+|
+03:HASH JOIN [INNER JOIN]
+|  hash predicates: ws_sold_date_sk = d1.d_date_sk
+|  runtime filters: RF002 <- d1.d_date_sk
+|  row-size=24B cardinality=719.38K
+|
+|--01:SCAN HDFS [tpcds.date_dim d1]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d1.d_month_seq <= 1223, d1.d_month_seq >= 1212
+|     row-size=8B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.web_sales]
+   HDFS partitions=1/1 files=1 size=140.07MB
+   runtime filters: RF000 -> ws_item_sk, RF002 -> ws_sold_date_sk
+   row-size=16B cardinality=719.38K
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=59.62MB Threads=9
+Per-Host Resource Estimates: Memory=333MB
+PLAN-ROOT SINK
+|
+15:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN [...]
+|  limit: 100
+|
+09:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN [...]
+|  row-size=50B cardinality=100
+|
+08:ANALYTIC
+|  functions: rank()
+|  partition by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END), CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN 3 THEN i_category WHEN 4 THEN i_category WHEN 5 THEN NULL END END
+|  order by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN sum(ws_net_paid) WHEN 4 THEN sum(ws_net_paid) WHEN 5 THEN sum(ws_net_paid) END) DESC
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=54B cardinality=1.00K
+|
+07:SORT
+|  order by: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) ASC NULLS FIRST, CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN 3 THEN i_category WHEN 4 THEN i_category WHEN 5 THEN NULL END END ASC NULLS FIRST, aggif(valid_t [...]
+|  row-size=46B cardinality=1.00K
+|
+14:EXCHANGE [HASH(aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END) + aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END),CASE WHEN aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END) = 0 THEN CASE valid_tid(3,4,5) WHEN 3 THEN i_category WHEN 4 THEN i_category WHEN 5 THEN NULL END END)]
+|
+06:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN sum(ws_net_paid) WHEN 4 THEN sum(ws_net_paid) WHEN 5 THEN sum(ws_net_paid) END), aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 0 WHEN 5 THEN 1 END), aggif(valid_tid(3,4,5) IN (3, 4, 5), CASE valid_tid(3,4,5) WHEN 3 THEN 0 WHEN 4 THEN 1 WHEN 5 THEN 1 END)
+|  group by: CASE valid_tid(3,4,5) WHEN 3 THEN i_category WHEN 4 THEN i_category WHEN 5 THEN NULL END, CASE valid_tid(3,4,5) WHEN 3 THEN i_class WHEN 4 THEN NULL WHEN 5 THEN NULL END, CASE valid_tid(3,4,5) WHEN 3 THEN 3 WHEN 4 THEN 4 WHEN 5 THEN 5 END
+|  row-size=46B cardinality=1.00K
+|
+13:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum:merge(ws_net_paid)
+|    group by: i_category, i_class
+|  Class 1
+|    output: sum:merge(ws_net_paid)
+|    group by: i_category, NULL
+|  Class 2
+|    output: sum:merge(ws_net_paid)
+|    group by: NULL, NULL
+|  row-size=140B cardinality=1.00K
+|
+12:EXCHANGE [HASH(CASE valid_tid(3,4,5) WHEN 3 THEN murmur_hash(i_category) WHEN 4 THEN murmur_hash(i_category) WHEN 5 THEN murmur_hash(NULL) END,CASE valid_tid(3,4,5) WHEN 3 THEN murmur_hash(i_class) WHEN 4 THEN murmur_hash(NULL) WHEN 5 THEN murmur_hash(NULL) END)]
+|
+05:AGGREGATE [STREAMING]
+|  Class 0
+|    output: sum(ws_net_paid)
+|    group by: i_category, i_class
+|  Class 1
+|    output: sum(ws_net_paid)
+|    group by: i_category, NULL
+|  Class 2
+|    output: sum(ws_net_paid)
+|    group by: NULL, NULL
+|  row-size=140B cardinality=1.00K
+|
+04:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ws_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=70B cardinality=719.38K
+|
+|--11:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=46B cardinality=18.00K
+|
+03:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ws_sold_date_sk = d1.d_date_sk
+|  runtime filters: RF002 <- d1.d_date_sk
+|  row-size=24B cardinality=719.38K
+|
+|--10:EXCHANGE [BROADCAST]
+|  |
+|  01:SCAN HDFS [tpcds.date_dim d1]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d1.d_month_seq <= 1223, d1.d_month_seq >= 1212
+|     row-size=8B cardinality=7.30K
+|
+00:SCAN HDFS [tpcds.web_sales]
+   HDFS partitions=1/1 files=1 size=140.07MB
+   runtime filters: RF000 -> ws_item_sk, RF002 -> ws_sold_date_sk
+   row-size=16B cardinality=719.38K
+====
+# TPCDS-Q27
+select i_item_id,
+       s_state, grouping(s_state) g_state,
+       avg(ss_quantity) agg1,
+       avg(ss_list_price) agg2,
+       avg(ss_coupon_amt) agg3,
+       avg(ss_sales_price) agg4
+from store_sales, customer_demographics, date_dim, store, item
+where ss_sold_date_sk = d_date_sk and
+      ss_item_sk = i_item_sk and
+      ss_store_sk = s_store_sk and
+      ss_cdemo_sk = cd_demo_sk and
+      cd_gender = 'M' and
+      cd_marital_status = 'S' and
+      cd_education_status = 'College' and
+      d_year = 2002 and
+      s_state in ('TN')
+group by rollup (i_item_id, s_state)
+order by i_item_id
+        ,s_state
+limit 100
+---- PLAN
+Max Per-Host Resource Reservation: Memory=50.32MB Threads=6
+Per-Host Resource Estimates: Memory=434MB
+PLAN-ROOT SINK
+|
+11:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(6,8,10) WHEN 6 THEN i_item_id WHEN 8 THEN i_item_id WHEN 10 THEN NULL END ASC, CASE valid_tid(6,8,10) WHEN 6 THEN s_state WHEN 8 THEN NULL WHEN 10 THEN NULL END ASC
+|  row-size=57B cardinality=100
+|
+10:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_tid(6,8,10) WHEN 6 THEN 0 WHEN 8 THEN 1 WHEN 10 THEN 1 END), aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_tid(6,8,10) WHEN 6 THEN avg(ss_quantity) WHEN 8 THEN avg(ss_quantity) WHEN 10 THEN avg(ss_quantity) END), aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_tid(6,8,10) WHEN 6 THEN avg(ss_list_price) WHEN 8 THEN avg(ss_list_price) WHEN 10 THEN avg(ss_list_price) END), aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_ti [...]
+|  group by: CASE valid_tid(6,8,10) WHEN 6 THEN i_item_id WHEN 8 THEN i_item_id WHEN 10 THEN NULL END, CASE valid_tid(6,8,10) WHEN 6 THEN s_state WHEN 8 THEN NULL WHEN 10 THEN NULL END, CASE valid_tid(6,8,10) WHEN 6 THEN 6 WHEN 8 THEN 8 WHEN 10 THEN 10 END
+|  row-size=61B cardinality=17.71K
+|
+09:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: avg(ss_quantity), avg(ss_list_price), avg(ss_coupon_amt), avg(ss_sales_price)
+|    group by: i_item_id, s_state
+|  Class 1
+|    output: avg(ss_quantity), avg(ss_list_price), avg(ss_coupon_amt), avg(ss_sales_price)
+|    group by: i_item_id, NULL
+|  Class 2
+|    output: avg(ss_quantity), avg(ss_list_price), avg(ss_coupon_amt), avg(ss_sales_price)
+|    group by: NULL, NULL
+|  row-size=202B cardinality=17.71K
+|
+08:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=150B cardinality=263.34K
+|
+|--04:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=36B cardinality=18.00K
+|
+07:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF002 <- s_store_sk
+|  row-size=114B cardinality=263.34K
+|
+|--03:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     predicates: s_state IN ('TN')
+|     row-size=18B cardinality=12
+|
+06:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_cdemo_sk = cd_demo_sk
+|  runtime filters: RF004 <- cd_demo_sk
+|  row-size=96B cardinality=263.34K
+|
+|--01:SCAN HDFS [tpcds.customer_demographics]
+|     HDFS partitions=1/1 files=1 size=76.92MB
+|     predicates: cd_marital_status = 'S', cd_gender = 'M', cd_education_status = 'College'
+|     row-size=52B cardinality=97.40K
+|
+05:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF006 <- d_date_sk
+|  row-size=44B cardinality=589.03K
+|
+|--02:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_year = 2002
+|     row-size=8B cardinality=373
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_item_sk, RF002 -> ss_store_sk, RF004 -> ss_cdemo_sk, RF006 -> ss_sold_date_sk
+   row-size=36B cardinality=2.88M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=67.95MB Threads=12
+Per-Host Resource Estimates: Memory=510MB
+PLAN-ROOT SINK
+|
+18:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: CASE valid_tid(6,8,10) WHEN 6 THEN i_item_id WHEN 8 THEN i_item_id WHEN 10 THEN NULL END ASC, CASE valid_tid(6,8,10) WHEN 6 THEN s_state WHEN 8 THEN NULL WHEN 10 THEN NULL END ASC
+|  limit: 100
+|
+11:TOP-N [LIMIT=100]
+|  order by: CASE valid_tid(6,8,10) WHEN 6 THEN i_item_id WHEN 8 THEN i_item_id WHEN 10 THEN NULL END ASC, CASE valid_tid(6,8,10) WHEN 6 THEN s_state WHEN 8 THEN NULL WHEN 10 THEN NULL END ASC
+|  row-size=57B cardinality=100
+|
+10:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_tid(6,8,10) WHEN 6 THEN 0 WHEN 8 THEN 1 WHEN 10 THEN 1 END), aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_tid(6,8,10) WHEN 6 THEN avg(ss_quantity) WHEN 8 THEN avg(ss_quantity) WHEN 10 THEN avg(ss_quantity) END), aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_tid(6,8,10) WHEN 6 THEN avg(ss_list_price) WHEN 8 THEN avg(ss_list_price) WHEN 10 THEN avg(ss_list_price) END), aggif(valid_tid(6,8,10) IN (6, 8, 10), CASE valid_ti [...]
+|  group by: CASE valid_tid(6,8,10) WHEN 6 THEN i_item_id WHEN 8 THEN i_item_id WHEN 10 THEN NULL END, CASE valid_tid(6,8,10) WHEN 6 THEN s_state WHEN 8 THEN NULL WHEN 10 THEN NULL END, CASE valid_tid(6,8,10) WHEN 6 THEN 6 WHEN 8 THEN 8 WHEN 10 THEN 10 END
+|  row-size=61B cardinality=17.71K
+|
+17:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: avg:merge(ss_quantity), avg:merge(ss_list_price), avg:merge(ss_coupon_amt), avg:merge(ss_sales_price)
+|    group by: i_item_id, s_state
+|  Class 1
+|    output: avg:merge(ss_quantity), avg:merge(ss_list_price), avg:merge(ss_coupon_amt), avg:merge(ss_sales_price)
+|    group by: i_item_id, NULL
+|  Class 2
+|    output: avg:merge(ss_quantity), avg:merge(ss_list_price), avg:merge(ss_coupon_amt), avg:merge(ss_sales_price)
+|    group by: NULL, NULL
+|  row-size=202B cardinality=17.71K
+|
+16:EXCHANGE [HASH(CASE valid_tid(5,7,9) WHEN 5 THEN murmur_hash(i_item_id) WHEN 7 THEN murmur_hash(i_item_id) WHEN 9 THEN murmur_hash(NULL) END,CASE valid_tid(5,7,9) WHEN 5 THEN murmur_hash(s_state) WHEN 7 THEN murmur_hash(NULL) WHEN 9 THEN murmur_hash(NULL) END)]
+|
+09:AGGREGATE [STREAMING]
+|  Class 0
+|    output: avg(ss_quantity), avg(ss_list_price), avg(ss_coupon_amt), avg(ss_sales_price)
+|    group by: i_item_id, s_state
+|  Class 1
+|    output: avg(ss_quantity), avg(ss_list_price), avg(ss_coupon_amt), avg(ss_sales_price)
+|    group by: i_item_id, NULL
+|  Class 2
+|    output: avg(ss_quantity), avg(ss_list_price), avg(ss_coupon_amt), avg(ss_sales_price)
+|    group by: NULL, NULL
+|  row-size=202B cardinality=17.71K
+|
+08:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF000 <- i_item_sk
+|  row-size=150B cardinality=263.34K
+|
+|--15:EXCHANGE [BROADCAST]
+|  |
+|  04:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=36B cardinality=18.00K
+|
+07:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF002 <- s_store_sk
+|  row-size=114B cardinality=263.34K
+|
+|--14:EXCHANGE [BROADCAST]
+|  |
+|  03:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     predicates: s_state IN ('TN')
+|     row-size=18B cardinality=12
+|
+06:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_cdemo_sk = cd_demo_sk
+|  runtime filters: RF004 <- cd_demo_sk
+|  row-size=96B cardinality=263.34K
+|
+|--13:EXCHANGE [BROADCAST]
+|  |
+|  01:SCAN HDFS [tpcds.customer_demographics]
+|     HDFS partitions=1/1 files=1 size=76.92MB
+|     predicates: cd_marital_status = 'S', cd_gender = 'M', cd_education_status = 'College'
+|     row-size=52B cardinality=97.40K
+|
+05:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_sold_date_sk = d_date_sk
+|  runtime filters: RF006 <- d_date_sk
+|  row-size=44B cardinality=589.03K
+|
+|--12:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.date_dim]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d_year = 2002
+|     row-size=8B cardinality=373
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_item_sk, RF002 -> ss_store_sk, RF004 -> ss_cdemo_sk, RF006 -> ss_sold_date_sk
+   row-size=36B cardinality=2.88M
+====
+# TPCDS-Q36
+select
+    sum(ss_net_profit)/sum(ss_ext_sales_price) as gross_margin
+   ,i_category
+   ,i_class
+   ,grouping(i_category)+grouping(i_class) as lochierarchy
+   ,rank() over (
+        partition by grouping(i_category)+grouping(i_class),
+        case when grouping(i_class) = 0 then i_category end
+        order by sum(ss_net_profit)/sum(ss_ext_sales_price) asc) as rank_within_parent
+ from
+    store_sales
+   ,date_dim       d1
+   ,item
+   ,store
+ where
+    d1.d_year = 2001
+ and d1.d_date_sk = ss_sold_date_sk
+ and i_item_sk  = ss_item_sk
+ and s_store_sk  = ss_store_sk
+ and s_state in ('TN')
+ group by rollup(i_category,i_class)
+ order by
+   lochierarchy desc
+  -- Modified from original because Impala does not allow aliases within order by
+  -- expressions, except at the top level.
+  ,case when grouping(i_category)+grouping(i_class) = 0 then i_category end
+  ,rank_within_parent
+  limit 100
+---- PLAN
+Max Per-Host Resource Reservation: Memory=36.82MB Threads=5
+Per-Host Resource Estimates: Memory=253MB
+PLAN-ROOT SINK
+|
+11:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN [...]
+|  row-size=66B cardinality=100
+|
+10:ANALYTIC
+|  functions: rank()
+|  partition by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END), CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN NULL END END
+|  order by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_net_profit) WHEN 5 THEN sum(ss_net_profit) WHEN 6 THEN sum(ss_net_profit) END) / aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_ext_sales_price) WHEN 5 THEN sum(ss_ext_sales_price) WHEN 6 THEN sum(ss_ext_sales_price) END) ASC
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=70B cardinality=1.00K
+|
+09:SORT
+|  order by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) ASC NULLS FIRST, CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN NULL END END ASC NULLS FIRST, aggif(valid_t [...]
+|  row-size=62B cardinality=1.00K
+|
+08:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_net_profit) WHEN 5 THEN sum(ss_net_profit) WHEN 6 THEN sum(ss_net_profit) END), aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_ext_sales_price) WHEN 5 THEN sum(ss_ext_sales_price) WHEN 6 THEN sum(ss_ext_sales_price) END), aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END), aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE v [...]
+|  group by: CASE valid_tid(4,5,6) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN NULL END, CASE valid_tid(4,5,6) WHEN 4 THEN i_class WHEN 5 THEN NULL WHEN 6 THEN NULL END, CASE valid_tid(4,5,6) WHEN 4 THEN 4 WHEN 5 THEN 5 WHEN 6 THEN 6 END
+|  row-size=62B cardinality=1.00K
+|
+07:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum(ss_net_profit), sum(ss_ext_sales_price)
+|    group by: i_category, i_class
+|  Class 1
+|    output: sum(ss_net_profit), sum(ss_ext_sales_price)
+|    group by: i_category, NULL
+|  Class 2
+|    output: sum(ss_net_profit), sum(ss_ext_sales_price)
+|    group by: NULL, NULL
+|  row-size=188B cardinality=1.00K
+|
+06:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=96B cardinality=589.03K
+|
+|--03:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     predicates: s_state IN ('TN')
+|     row-size=18B cardinality=12
+|
+05:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF002 <- i_item_sk
+|  row-size=78B cardinality=589.03K
+|
+|--02:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=46B cardinality=18.00K
+|
+04:HASH JOIN [INNER JOIN]
+|  hash predicates: ss_sold_date_sk = d1.d_date_sk
+|  runtime filters: RF004 <- d1.d_date_sk
+|  row-size=32B cardinality=589.03K
+|
+|--01:SCAN HDFS [tpcds.date_dim d1]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d1.d_year = 2001
+|     row-size=8B cardinality=373
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_store_sk, RF002 -> ss_item_sk, RF004 -> ss_sold_date_sk
+   row-size=24B cardinality=2.88M
+---- DISTRIBUTEDPLAN
+Max Per-Host Resource Reservation: Memory=62.57MB Threads=11
+Per-Host Resource Estimates: Memory=336MB
+PLAN-ROOT SINK
+|
+18:MERGING-EXCHANGE [UNPARTITIONED]
+|  order by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN [...]
+|  limit: 100
+|
+11:TOP-N [LIMIT=100]
+|  order by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) DESC, CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN [...]
+|  row-size=66B cardinality=100
+|
+10:ANALYTIC
+|  functions: rank()
+|  partition by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END), CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN NULL END END
+|  order by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_net_profit) WHEN 5 THEN sum(ss_net_profit) WHEN 6 THEN sum(ss_net_profit) END) / aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_ext_sales_price) WHEN 5 THEN sum(ss_ext_sales_price) WHEN 6 THEN sum(ss_ext_sales_price) END) ASC
+|  window: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+|  row-size=70B cardinality=1.00K
+|
+09:SORT
+|  order by: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) ASC NULLS FIRST, CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN NULL END END ASC NULLS FIRST, aggif(valid_t [...]
+|  row-size=62B cardinality=1.00K
+|
+17:EXCHANGE [HASH(aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END) + aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END),CASE WHEN aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 1 WHEN 6 THEN 1 END) = 0 THEN CASE valid_tid(4,5,6) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN NULL END END)]
+|
+08:AGGREGATE [FINALIZE]
+|  output: aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_net_profit) WHEN 5 THEN sum(ss_net_profit) WHEN 6 THEN sum(ss_net_profit) END), aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN sum(ss_ext_sales_price) WHEN 5 THEN sum(ss_ext_sales_price) WHEN 6 THEN sum(ss_ext_sales_price) END), aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE valid_tid(4,5,6) WHEN 4 THEN 0 WHEN 5 THEN 0 WHEN 6 THEN 1 END), aggif(valid_tid(4,5,6) IN (4, 5, 6), CASE v [...]
+|  group by: CASE valid_tid(4,5,6) WHEN 4 THEN i_category WHEN 5 THEN i_category WHEN 6 THEN NULL END, CASE valid_tid(4,5,6) WHEN 4 THEN i_class WHEN 5 THEN NULL WHEN 6 THEN NULL END, CASE valid_tid(4,5,6) WHEN 4 THEN 4 WHEN 5 THEN 5 WHEN 6 THEN 6 END
+|  row-size=62B cardinality=1.00K
+|
+16:AGGREGATE [FINALIZE]
+|  Class 0
+|    output: sum:merge(ss_net_profit), sum:merge(ss_ext_sales_price)
+|    group by: i_category, i_class
+|  Class 1
+|    output: sum:merge(ss_net_profit), sum:merge(ss_ext_sales_price)
+|    group by: i_category, NULL
+|  Class 2
+|    output: sum:merge(ss_net_profit), sum:merge(ss_ext_sales_price)
+|    group by: NULL, NULL
+|  row-size=188B cardinality=1.00K
+|
+15:EXCHANGE [HASH(CASE valid_tid(4,5,6) WHEN 4 THEN murmur_hash(i_category) WHEN 5 THEN murmur_hash(i_category) WHEN 6 THEN murmur_hash(NULL) END,CASE valid_tid(4,5,6) WHEN 4 THEN murmur_hash(i_class) WHEN 5 THEN murmur_hash(NULL) WHEN 6 THEN murmur_hash(NULL) END)]
+|
+07:AGGREGATE [STREAMING]
+|  Class 0
+|    output: sum(ss_net_profit), sum(ss_ext_sales_price)
+|    group by: i_category, i_class
+|  Class 1
+|    output: sum(ss_net_profit), sum(ss_ext_sales_price)
+|    group by: i_category, NULL
+|  Class 2
+|    output: sum(ss_net_profit), sum(ss_ext_sales_price)
+|    group by: NULL, NULL
+|  row-size=188B cardinality=1.00K
+|
+06:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_store_sk = s_store_sk
+|  runtime filters: RF000 <- s_store_sk
+|  row-size=96B cardinality=589.03K
+|
+|--14:EXCHANGE [BROADCAST]
+|  |
+|  03:SCAN HDFS [tpcds.store]
+|     HDFS partitions=1/1 files=1 size=3.08KB
+|     predicates: s_state IN ('TN')
+|     row-size=18B cardinality=12
+|
+05:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_item_sk = i_item_sk
+|  runtime filters: RF002 <- i_item_sk
+|  row-size=78B cardinality=589.03K
+|
+|--13:EXCHANGE [BROADCAST]
+|  |
+|  02:SCAN HDFS [tpcds.item]
+|     HDFS partitions=1/1 files=1 size=4.82MB
+|     row-size=46B cardinality=18.00K
+|
+04:HASH JOIN [INNER JOIN, BROADCAST]
+|  hash predicates: ss_sold_date_sk = d1.d_date_sk
+|  runtime filters: RF004 <- d1.d_date_sk
+|  row-size=32B cardinality=589.03K
+|
+|--12:EXCHANGE [BROADCAST]
+|  |
+|  01:SCAN HDFS [tpcds.date_dim d1]
+|     HDFS partitions=1/1 files=1 size=9.84MB
+|     predicates: d1.d_year = 2001
+|     row-size=8B cardinality=373
+|
+00:SCAN HDFS [tpcds.store_sales]
+   HDFS partitions=1824/1824 files=1824 size=346.60MB
+   runtime filters: RF000 -> ss_store_sk, RF002 -> ss_item_sk, RF004 -> ss_sold_date_sk
+   row-size=24B cardinality=2.88M
+====
diff --git a/testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test b/testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test
index aba927d..ffda59c 100644
--- a/testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test
+++ b/testdata/workloads/functional-query/queries/QueryTest/grouping-sets.test
@@ -153,3 +153,175 @@ where exists
 ---- TYPES
 INT
 ====
+---- QUERY
+# Test grouping() and grouping_id() on a table with null values.
+select
+  grouping_id(),
+  grouping(int_col % 2),
+  grouping(substring(string_col, 1, 1)),
+  grouping(bool_col),
+  int_col % 2 i,
+  substring(string_col, 1, 1) str,
+  bool_col,
+  count(*)
+from alltypesagg
+group by cube(i, str, bool_col)
+order by grouping_id() desc, i, str, bool_col
+---- RESULTS
+7,1,1,1,NULL,'NULL',NULL,11000
+6,1,1,0,NULL,'NULL',false,5000
+6,1,1,0,NULL,'NULL',true,6000
+5,1,0,1,NULL,'0',NULL,20
+5,1,0,1,NULL,'1',NULL,1220
+5,1,0,1,NULL,'2',NULL,1220
+5,1,0,1,NULL,'3',NULL,1220
+5,1,0,1,NULL,'4',NULL,1220
+5,1,0,1,NULL,'5',NULL,1220
+5,1,0,1,NULL,'6',NULL,1220
+5,1,0,1,NULL,'7',NULL,1220
+5,1,0,1,NULL,'8',NULL,1220
+5,1,0,1,NULL,'9',NULL,1220
+4,1,0,0,NULL,'0',true,20
+4,1,0,0,NULL,'1',false,560
+4,1,0,0,NULL,'1',true,660
+4,1,0,0,NULL,'2',false,550
+4,1,0,0,NULL,'2',true,670
+4,1,0,0,NULL,'3',false,560
+4,1,0,0,NULL,'3',true,660
+4,1,0,0,NULL,'4',false,550
+4,1,0,0,NULL,'4',true,670
+4,1,0,0,NULL,'5',false,560
+4,1,0,0,NULL,'5',true,660
+4,1,0,0,NULL,'6',false,550
+4,1,0,0,NULL,'6',true,670
+4,1,0,0,NULL,'7',false,560
+4,1,0,0,NULL,'7',true,660
+4,1,0,0,NULL,'8',false,550
+4,1,0,0,NULL,'8',true,670
+4,1,0,0,NULL,'9',false,560
+4,1,0,0,NULL,'9',true,660
+3,0,1,1,0,'NULL',NULL,5980
+3,0,1,1,1,'NULL',NULL,5000
+3,0,1,1,NULL,'NULL',NULL,20
+2,0,1,0,0,'NULL',true,5980
+2,0,1,0,1,'NULL',false,5000
+2,0,1,0,NULL,'NULL',true,20
+1,0,0,1,0,'1',NULL,660
+1,0,0,1,0,'2',NULL,670
+1,0,0,1,0,'3',NULL,660
+1,0,0,1,0,'4',NULL,670
+1,0,0,1,0,'5',NULL,660
+1,0,0,1,0,'6',NULL,670
+1,0,0,1,0,'7',NULL,660
+1,0,0,1,0,'8',NULL,670
+1,0,0,1,0,'9',NULL,660
+1,0,0,1,1,'1',NULL,560
+1,0,0,1,1,'2',NULL,550
+1,0,0,1,1,'3',NULL,560
+1,0,0,1,1,'4',NULL,550
+1,0,0,1,1,'5',NULL,560
+1,0,0,1,1,'6',NULL,550
+1,0,0,1,1,'7',NULL,560
+1,0,0,1,1,'8',NULL,550
+1,0,0,1,1,'9',NULL,560
+1,0,0,1,NULL,'0',NULL,20
+0,0,0,0,0,'1',true,660
+0,0,0,0,0,'2',true,670
+0,0,0,0,0,'3',true,660
+0,0,0,0,0,'4',true,670
+0,0,0,0,0,'5',true,660
+0,0,0,0,0,'6',true,670
+0,0,0,0,0,'7',true,660
+0,0,0,0,0,'8',true,670
+0,0,0,0,0,'9',true,660
+0,0,0,0,1,'1',false,560
+0,0,0,0,1,'2',false,550
+0,0,0,0,1,'3',false,560
+0,0,0,0,1,'4',false,550
+0,0,0,0,1,'5',false,560
+0,0,0,0,1,'6',false,550
+0,0,0,0,1,'7',false,560
+0,0,0,0,1,'8',false,550
+0,0,0,0,1,'9',false,560
+0,0,0,0,NULL,'0',true,20
+---- TYPES
+BIGINT, TINYINT, TINYINT, TINYINT, INT, STRING, BOOLEAN, BIGINT
+====
+---- QUERY
+# Test grouping_id() with different argument list order and length
+select
+  grouping_id(bool_col, string_col, int_col),
+  grouping_id(bool_col),
+  int_col,
+  string_col,
+  bool_col,
+  count(*)
+from alltypestiny
+group by rollup(int_col, string_col, bool_col)
+---- RESULTS
+6,1,0,'NULL',NULL,4
+0,0,1,'1',false,4
+7,1,NULL,'NULL',NULL,8
+4,1,0,'0',NULL,4
+4,1,1,'1',NULL,4
+0,0,0,'0',true,4
+6,1,1,'NULL',NULL,4
+---- TYPES
+BIGINT, BIGINT, INT, STRING, BOOLEAN, BIGINT
+====
+---- QUERY
+# Test grouping() and grouping_id() with a single grouping set.
+select
+  grouping_id(int_col, string_col),
+  grouping(int_col),
+  grouping(string_col),
+  int_col,
+  string_col,
+  count(*)
+from alltypestiny
+group by grouping sets((int_col, string_col))
+---- RESULTS
+0,0,0,1,'1',4
+0,0,0,0,'0',4
+---- TYPES
+BIGINT, TINYINT, TINYINT, INT, STRING, BIGINT
+====
+---- QUERY
+# Test grouping() and grouping_id() with plain GROUP BY, where they always
+# return 0.
+select
+  grouping_id(int_col, string_col),
+  grouping_id(),
+  grouping(int_col),
+  grouping(string_col),
+  int_col,
+  string_col,
+  count(*)
+from alltypestiny
+group by int_col, string_col
+---- RESULTS
+0,0,0,0,1,'1',4
+0,0,0,0,0,'0',4
+---- TYPES
+BIGINT, BIGINT, TINYINT, TINYINT, INT, STRING, BIGINT
+====
+---- QUERY
+# Test grouping() and grouping_id() with plain GROUP BY and multiple aggregation
+# classes from the distinct aggregate functions. They always return 0 in this
+# case.
+select
+  grouping_id(int_col, string_col),
+  grouping(int_col),
+  grouping(string_col),
+  int_col,
+  string_col,
+  count(distinct tinyint_col),
+  count(distinct timestamp_col)
+from alltypestiny
+group by int_col, string_col
+---- RESULTS
+0,0,0,1,'1',1,4
+0,0,0,0,'0',1,4
+---- TYPES
+BIGINT, TINYINT, TINYINT, INT, STRING, BIGINT, BIGINT
+====
diff --git a/testdata/workloads/functional-query/queries/QueryTest/views-compatibility.test b/testdata/workloads/functional-query/queries/QueryTest/views-compatibility.test
index 2ca7a2d..c8d6e85 100644
--- a/testdata/workloads/functional-query/queries/QueryTest/views-compatibility.test
+++ b/testdata/workloads/functional-query/queries/QueryTest/views-compatibility.test
@@ -232,3 +232,32 @@ IMPALA=SUCCESS
 ---- QUERY_IMPALA_VIEW_RESULTS
 HIVE=SUCCESS
 ====
+---- CREATE_VIEW
+# View with ROLLUP and grouping(), which are supported by Impala and Hive
+create view test (i, b, c, g1, g2) as
+select int_col, bigint_col, count(string_col), grouping(int_col),
+    grouping(bigint_col)
+from functional.alltypessmall
+group by rollup(int_col, bigint_col)
+---- CREATE_VIEW_RESULTS
+IMPALA=SUCCESS
+HIVE=SUCCESS
+---- QUERY_HIVE_VIEW_RESULTS
+IMPALA=SUCCESS
+---- QUERY_IMPALA_VIEW_RESULTS
+IMPALA=SUCCESS
+HIVE=SUCCESS
+====
+---- CREATE_VIEW
+# View with ROLLUP and grouping_id(), which is not supported by Hive
+create view test (i, b, c, g) as
+select int_col, bigint_col, count(string_col), grouping_id(int_col, bigint_col)
+from functional.alltypessmall
+group by rollup(int_col, bigint_col)
+---- CREATE_VIEW_RESULTS
+IMPALA=SUCCESS
+HIVE=FAILURE
+---- QUERY_IMPALA_VIEW_RESULTS
+IMPALA=SUCCESS
+HIVE=FAILURE
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q27.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q27.test
new file mode 100644
index 0000000..9acbaf5
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q27.test
@@ -0,0 +1,126 @@
+====
+---- QUERY: TPCDS-Q27
+select i_item_id,
+       s_state, grouping(s_state) g_state,
+       avg(ss_quantity) agg1,
+       avg(ss_list_price) agg2,
+       avg(ss_coupon_amt) agg3,
+       avg(ss_sales_price) agg4
+from store_sales, customer_demographics, date_dim, store, item
+where ss_sold_date_sk = d_date_sk and
+      ss_item_sk = i_item_sk and
+      ss_store_sk = s_store_sk and
+      ss_cdemo_sk = cd_demo_sk and
+      cd_gender = 'M' and
+      cd_marital_status = 'S' and
+      cd_education_status = 'College' and
+      d_year = 2002 and
+      s_state in ('TN')
+group by rollup (i_item_id, s_state)
+order by i_item_id
+        ,s_state
+limit 100
+---- RESULTS
+'AAAAAAAAAAABAAAA','TN',0,46,114.720000,0.000000,32.120000
+'AAAAAAAAAAABAAAA','NULL',1,46,114.720000,0.000000,32.120000
+'AAAAAAAAAAAEAAAA','TN',0,100,77.970000,0.000000,57.690000
+'AAAAAAAAAAAEAAAA','NULL',1,100,77.970000,0.000000,57.690000
+'AAAAAAAAAABAAAAA','TN',0,6,48.890000,13.610000,2.440000
+'AAAAAAAAAABAAAAA','NULL',1,6,48.890000,13.610000,2.440000
+'AAAAAAAAAACAAAAA','TN',0,62,32.213333,0.000000,5.506667
+'AAAAAAAAAACAAAAA','NULL',1,62,32.213333,0.000000,5.506667
+'AAAAAAAAAACDAAAA','TN',0,97,161.780000,0.000000,55.000000
+'AAAAAAAAAACDAAAA','NULL',1,97,161.780000,0.000000,55.000000
+'AAAAAAAAAADBAAAA','TN',0,28,136.010000,575.760000,73.440000
+'AAAAAAAAAADBAAAA','NULL',1,28,136.010000,575.760000,73.440000
+'AAAAAAAAAADCAAAA','TN',0,64,116.140000,0.000000,108.010000
+'AAAAAAAAAADCAAAA','NULL',1,64,116.140000,0.000000,108.010000
+'AAAAAAAAAAEBAAAA','TN',0,9,5.430000,0.000000,5.210000
+'AAAAAAAAAAEBAAAA','NULL',1,9,5.430000,0.000000,5.210000
+'AAAAAAAAAAEDAAAA','TN',0,45.5,87.635000,0.000000,51.840000
+'AAAAAAAAAAEDAAAA','NULL',1,45.5,87.635000,0.000000,51.840000
+'AAAAAAAAAAGBAAAA','TN',0,29,101.940000,0.000000,100.920000
+'AAAAAAAAAAGBAAAA','NULL',1,29,101.940000,0.000000,100.920000
+'AAAAAAAAAAGCAAAA','TN',0,97,65.270000,0.000000,21.530000
+'AAAAAAAAAAGCAAAA','NULL',1,97,65.270000,0.000000,21.530000
+'AAAAAAAAAAGEAAAA','TN',0,55,73.875000,0.000000,71.000000
+'AAAAAAAAAAGEAAAA','NULL',1,55,73.875000,0.000000,71.000000
+'AAAAAAAAAAHAAAAA','TN',0,20,134.300000,0.000000,119.520000
+'AAAAAAAAAAHAAAAA','NULL',1,20,134.300000,0.000000,119.520000
+'AAAAAAAAAAHBAAAA','TN',0,97,7.400000,0.000000,6.730000
+'AAAAAAAAAAHBAAAA','NULL',1,97,7.400000,0.000000,6.730000
+'AAAAAAAAAAHDAAAA','TN',0,69,5.620000,0.000000,0.330000
+'AAAAAAAAAAHDAAAA','NULL',1,69,5.620000,0.000000,0.330000
+'AAAAAAAAAAIAAAAA','TN',0,87,106.980000,0.000000,27.810000
+'AAAAAAAAAAIAAAAA','NULL',1,87,106.980000,0.000000,27.810000
+'AAAAAAAAAAKBAAAA','TN',0,84.5,66.175000,847.835000,57.380000
+'AAAAAAAAAAKBAAAA','NULL',1,84.5,66.175000,847.835000,57.380000
+'AAAAAAAAAALAAAAA','TN',0,6,32.280000,0.000000,7.420000
+'AAAAAAAAAALAAAAA','NULL',1,6,32.280000,0.000000,7.420000
+'AAAAAAAAAALCAAAA','TN',0,98,34.310000,55.300000,1.710000
+'AAAAAAAAAALCAAAA','NULL',1,98,34.310000,55.300000,1.710000
+'AAAAAAAAAALDAAAA','TN',0,59,129.470000,713.900000,22.000000
+'AAAAAAAAAALDAAAA','NULL',1,59,129.470000,713.900000,22.000000
+'AAAAAAAAAANAAAAA','TN',0,74,60.875000,0.000000,13.200000
+'AAAAAAAAAANAAAAA','NULL',1,74,60.875000,0.000000,13.200000
+'AAAAAAAAAANBAAAA','TN',0,96,130.790000,1864.510000,129.480000
+'AAAAAAAAAANBAAAA','NULL',1,96,130.790000,1864.510000,129.480000
+'AAAAAAAAAAOAAAAA','TN',0,85,130.340000,0.000000,122.510000
+'AAAAAAAAAAOAAAAA','NULL',1,85,130.340000,0.000000,122.510000
+'AAAAAAAAAAOCAAAA','TN',0,14,64.575000,0.000000,33.975000
+'AAAAAAAAAAOCAAAA','NULL',1,14,64.575000,0.000000,33.975000
+'AAAAAAAAAAODAAAA','TN',0,84,30.500000,0.000000,28.970000
+'AAAAAAAAAAODAAAA','NULL',1,84,30.500000,0.000000,28.970000
+'AAAAAAAAAAPBAAAA','TN',0,45,118.890000,2380.595000,84.715000
+'AAAAAAAAAAPBAAAA','NULL',1,45,118.890000,2380.595000,84.715000
+'AAAAAAAAABAAAAAA','TN',0,97,124.750000,0.000000,74.850000
+'AAAAAAAAABAAAAAA','NULL',1,97,124.750000,0.000000,74.850000
+'AAAAAAAAABABAAAA','TN',0,31,29.980000,17.090000,27.580000
+'AAAAAAAAABABAAAA','NULL',1,31,29.980000,17.090000,27.580000
+'AAAAAAAAABAEAAAA','TN',0,34,17.210000,0.000000,13.420000
+'AAAAAAAAABAEAAAA','NULL',1,34,17.210000,0.000000,13.420000
+'AAAAAAAAABCBAAAA','TN',0,38,131.375000,0.000000,52.320000
+'AAAAAAAAABCBAAAA','NULL',1,38,131.375000,0.000000,52.320000
+'AAAAAAAAABDAAAAA','TN',0,31.5,68.345000,0.000000,63.275000
+'AAAAAAAAABDAAAAA','NULL',1,31.5,68.345000,0.000000,63.275000
+'AAAAAAAAABDBAAAA','TN',0,56,103.850000,1624.170000,59.190000
+'AAAAAAAAABDBAAAA','NULL',1,56,103.850000,1624.170000,59.190000
+'AAAAAAAAABDDAAAA','TN',0,67.33333333333333,119.420000,23.186667,38.720000
+'AAAAAAAAABDDAAAA','NULL',1,67.33333333333333,119.420000,23.186667,38.720000
+'AAAAAAAAABDEAAAA','TN',0,61,85.950000,2282.360000,44.605000
+'AAAAAAAAABDEAAAA','NULL',1,61,85.950000,2282.360000,44.605000
+'AAAAAAAAABEDAAAA','TN',0,61,85.140000,0.000000,50.230000
+'AAAAAAAAABEDAAAA','NULL',1,61,85.140000,0.000000,50.230000
+'AAAAAAAAABFBAAAA','TN',0,31,23.550000,0.000000,13.650000
+'AAAAAAAAABFBAAAA','NULL',1,31,23.550000,0.000000,13.650000
+'AAAAAAAAABFCAAAA','TN',0,83,15.500000,0.000000,0.000000
+'AAAAAAAAABFCAAAA','NULL',1,83,15.500000,0.000000,0.000000
+'AAAAAAAAABGAAAAA','TN',0,64,35.080000,0.000000,3.150000
+'AAAAAAAAABGAAAAA','NULL',1,64,35.080000,0.000000,3.150000
+'AAAAAAAAABGBAAAA','TN',0,40,25.070000,0.000000,16.040000
+'AAAAAAAAABGBAAAA','NULL',1,40,25.070000,0.000000,16.040000
+'AAAAAAAAABGEAAAA','TN',0,39.5,56.765000,14.255000,36.005000
+'AAAAAAAAABGEAAAA','NULL',1,39.5,56.765000,14.255000,36.005000
+'AAAAAAAAABHAAAAA','TN',0,93,74.800000,0.000000,21.690000
+'AAAAAAAAABHAAAAA','NULL',1,93,74.800000,0.000000,21.690000
+'AAAAAAAAABHCAAAA','TN',0,31.5,79.835000,181.380000,61.935000
+'AAAAAAAAABHCAAAA','NULL',1,31.5,79.835000,181.380000,61.935000
+'AAAAAAAAABIBAAAA','TN',0,48,170.890000,0.000000,140.120000
+'AAAAAAAAABIBAAAA','NULL',1,48,170.890000,0.000000,140.120000
+'AAAAAAAAABJAAAAA','TN',0,46.5,82.325000,0.000000,44.985000
+'AAAAAAAAABJAAAAA','NULL',1,46.5,82.325000,0.000000,44.985000
+'AAAAAAAAABJBAAAA','TN',0,76,12.400000,0.000000,3.960000
+'AAAAAAAAABJBAAAA','NULL',1,76,12.400000,0.000000,3.960000
+'AAAAAAAAABJDAAAA','TN',0,34,118.505000,0.000000,61.205000
+'AAAAAAAAABJDAAAA','NULL',1,34,118.505000,0.000000,61.205000
+'AAAAAAAAABKAAAAA','TN',0,65,8.775000,0.000000,5.180000
+'AAAAAAAAABKAAAAA','NULL',1,65,8.775000,0.000000,5.180000
+'AAAAAAAAABKCAAAA','TN',0,26,51.570000,0.000000,41.250000
+'AAAAAAAAABKCAAAA','NULL',1,26,51.570000,0.000000,41.250000
+'AAAAAAAAABLBAAAA','TN',0,61.5,149.120000,0.000000,82.540000
+'AAAAAAAAABLBAAAA','NULL',1,61.5,149.120000,0.000000,82.540000
+'AAAAAAAAABMAAAAA','TN',0,14,109.970000,0.000000,10.990000
+'AAAAAAAAABMAAAAA','NULL',1,14,109.970000,0.000000,10.990000
+---- TYPES
+STRING, STRING, TINYINT, DOUBLE, DECIMAL, DECIMAL, DECIMAL
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q36.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q36.test
new file mode 100644
index 0000000..ac0c084
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q36.test
@@ -0,0 +1,134 @@
+====
+---- QUERY: TPCDS-Q36
+select
+    sum(ss_net_profit)/sum(ss_ext_sales_price) as gross_margin
+   ,i_category
+   ,i_class
+   ,grouping(i_category)+grouping(i_class) as lochierarchy
+   ,rank() over (
+        partition by grouping(i_category)+grouping(i_class),
+        case when grouping(i_class) = 0 then i_category end
+        order by sum(ss_net_profit)/sum(ss_ext_sales_price) asc) as rank_within_parent
+ from
+    store_sales
+   ,date_dim       d1
+   ,item
+   ,store
+ where
+    d1.d_year = 2001
+ and d1.d_date_sk = ss_sold_date_sk
+ and i_item_sk  = ss_item_sk
+ and s_store_sk  = ss_store_sk
+ and s_state in ('TN')
+ group by rollup(i_category,i_class)
+ order by
+   lochierarchy desc
+  -- Modified from original because Impala does not allow aliases within order by
+  -- expressions, except at the top level.
+  ,case when grouping(i_category)+grouping(i_class) = 0 then i_category end
+  ,rank_within_parent
+  limit 100
+---- RESULTS
+-0.434627,'NULL','NULL',2,1
+-0.445726,'Electronics','NULL',1,1
+-0.439058,'Children','NULL',1,2
+-0.435375,'Home','NULL',1,3
+-0.435135,'Sports','NULL',1,4
+-0.434787,'Shoes','NULL',1,5
+-0.433937,'Men','NULL',1,6
+-0.432814,'Books','NULL',1,7
+-0.432786,'Women','NULL',1,8
+-0.428839,'Jewelry','NULL',1,9
+-0.428397,'Music','NULL',1,10
+-0.408364,'NULL','NULL',1,11
+-0.493138,'Books','NULL',0,1
+-0.459060,'Books','self-help',0,2
+-0.453614,'Books','romance',0,3
+-0.453097,'Books','parenting',0,4
+-0.452429,'Books','home repair',0,5
+-0.439811,'Books','arts',0,6
+-0.437717,'Books','computers',0,7
+-0.436171,'Books','entertainments',0,8
+-0.433511,'Books','business',0,9
+-0.429332,'Books','cooking',0,10
+-0.427767,'Books','history',0,11
+-0.426951,'Books','sports',0,12
+-0.425222,'Books','fiction',0,13
+-0.421390,'Books','travel',0,14
+-0.413109,'Books','reference',0,15
+-0.407932,'Books','science',0,16
+-0.402453,'Books','mystery',0,17
+-0.519729,'Children','NULL',0,1
+-0.443955,'Children','infants',0,2
+-0.441166,'Children','toddlers',0,3
+-0.439389,'Children','newborn',0,4
+-0.431534,'Children','school-uniforms',0,5
+-0.481111,'Electronics','scanners',0,1
+-0.474762,'Electronics','wireless',0,2
+-0.468814,'Electronics','musical',0,3
+-0.459644,'Electronics','audio',0,4
+-0.459285,'Electronics','portable',0,5
+-0.457669,'Electronics','automotive',0,6
+-0.450597,'Electronics','personal',0,7
+-0.446587,'Electronics','karoke',0,8
+-0.444758,'Electronics','camcorders',0,9
+-0.442434,'Electronics','stereo',0,10
+-0.435633,'Electronics','monitors',0,11
+-0.430062,'Electronics','disk drives',0,12
+-0.429565,'Electronics','dvd/vcr players',0,13
+-0.427792,'Electronics','televisions',0,14
+-0.427485,'Electronics','cameras',0,15
+-0.417896,'Electronics','memory',0,16
+-0.458207,'Home','wallpaper',0,1
+-0.458124,'Home','rugs',0,2
+-0.453909,'Home','blinds/shades',0,3
+-0.450805,'Home','tables',0,4
+-0.450006,'Home','bedding',0,5
+-0.444429,'Home','paint',0,6
+-0.442900,'Home','kids',0,7
+-0.441957,'Home','decor',0,8
+-0.440270,'Home','curtains/drapes',0,9
+-0.436733,'Home','accent',0,10
+-0.429606,'Home','glassware',0,11
+-0.421293,'Home','flatware',0,12
+-0.419606,'Home','mattresses',0,13
+-0.416797,'Home','furniture',0,14
+-0.416138,'Home','bathroom',0,15
+-0.397446,'Home','lighting',0,16
+-0.275087,'Home','NULL',0,17
+-0.482023,'Jewelry','consignment',0,1
+-0.452923,'Jewelry','gold',0,2
+-0.442266,'Jewelry','womens watch',0,3
+-0.438958,'Jewelry','semi-precious',0,4
+-0.437528,'Jewelry','NULL',0,5
+-0.435691,'Jewelry','bracelets',0,6
+-0.434631,'Jewelry','birdal',0,7
+-0.434527,'Jewelry','loose stones',0,8
+-0.433407,'Jewelry','rings',0,9
+-0.430755,'Jewelry','estate',0,10
+-0.429736,'Jewelry','pendants',0,11
+-0.422080,'Jewelry','earings',0,12
+-0.417512,'Jewelry','costume',0,13
+-0.412604,'Jewelry','jewelry boxes',0,14
+-0.412515,'Jewelry','mens watch',0,15
+-0.405426,'Jewelry','custom',0,16
+-0.403669,'Jewelry','diamonds',0,17
+-0.440736,'Men','sports-apparel',0,1
+-0.437901,'Men','accessories',0,2
+-0.435914,'Men','pants',0,3
+-0.423606,'Men','shirts',0,4
+-0.376248,'Men','NULL',0,5
+-0.474311,'Music','NULL',0,1
+-0.433297,'Music','pop',0,2
+-0.430202,'Music','country',0,3
+-0.427121,'Music','classical',0,4
+-0.421368,'Music','rock',0,5
+-0.644087,'Shoes','NULL',0,1
+-0.443544,'Shoes','mens',0,2
+-0.442976,'Shoes','womens',0,3
+-0.430849,'Shoes','kids',0,4
+-0.420041,'Shoes','athletic',0,5
+-0.467490,'Sports','basketball',0,1
+---- TYPES
+DECIMAL, STRING, STRING, SMALLINT, BIGINT
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q70.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q70.test
new file mode 100644
index 0000000..51d1604
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q70.test
@@ -0,0 +1,46 @@
+====
+---- QUERY: TPCDS-Q70
+select
+    sum(ss_net_profit) as total_sum
+   ,s_state
+   ,s_county
+   ,grouping(s_state)+grouping(s_county) as lochierarchy
+   ,rank() over (
+        partition by grouping(s_state)+grouping(s_county),
+        case when grouping(s_county) = 0 then s_state end
+        order by sum(ss_net_profit) desc) as rank_within_parent
+ from
+    store_sales
+   ,date_dim       d1
+   ,store
+ where
+    d1.d_month_seq between 1212 and 1212+11
+ and d1.d_date_sk = ss_sold_date_sk
+ and s_store_sk  = ss_store_sk
+ and s_state in
+             ( select s_state
+               from  (select s_state as s_state,
+                            rank() over ( partition by s_state order by sum(ss_net_profit) desc) as ranking
+                      from   store_sales, store, date_dim
+                      where  d_month_seq between 1212 and 1212+11
+                            and d_date_sk = ss_sold_date_sk
+                            and s_store_sk  = ss_store_sk
+                      group by s_state
+                     ) tmp1
+               where ranking <= 5
+             )
+ group by rollup(s_state,s_county)
+ order by
+   lochierarchy desc
+  -- Modified from original because Impala does not allow aliases within order by
+  -- expressions, except at the top level.
+  ,case when grouping(s_state)+grouping(s_county) = 0 then s_state end
+  ,rank_within_parent
+ limit 100;
+---- RESULTS
+-437983751.26,'NULL','NULL',2,1
+-437983751.26,'TN','NULL',1,1
+-437983751.26,'TN','Williamson County',0,1
+---- TYPES
+DECIMAL, STRING, STRING, SMALLINT, BIGINT
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q80.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q80.test
new file mode 100644
index 0000000..4daa802
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q80.test
@@ -0,0 +1,199 @@
+====
+---- QUERY: TPCDS-Q80
+with ssr as
+ (select  s_store_id as store_id,
+          sum(ss_ext_sales_price) as sales,
+          sum(coalesce(sr_return_amt, 0)) as `returns`,
+          sum(ss_net_profit - coalesce(sr_net_loss, 0)) as profit
+  from store_sales left outer join store_returns on
+         (ss_item_sk = sr_item_sk and ss_ticket_number = sr_ticket_number),
+     date_dim,
+     store,
+     item,
+     promotion
+ where ss_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+       and ss_store_sk = s_store_sk
+       and ss_item_sk = i_item_sk
+       and i_current_price > 50
+       and ss_promo_sk = p_promo_sk
+       and p_channel_tv = 'N'
+ group by s_store_id)
+ ,
+ csr as
+ (select  cp_catalog_page_id as catalog_page_id,
+          sum(cs_ext_sales_price) as sales,
+          sum(coalesce(cr_return_amount, 0)) as `returns`,
+          sum(cs_net_profit - coalesce(cr_net_loss, 0)) as profit
+  from catalog_sales left outer join catalog_returns on
+         (cs_item_sk = cr_item_sk and cs_order_number = cr_order_number),
+     date_dim,
+     catalog_page,
+     item,
+     promotion
+ where cs_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+        and cs_catalog_page_sk = cp_catalog_page_sk
+       and cs_item_sk = i_item_sk
+       and i_current_price > 50
+       and cs_promo_sk = p_promo_sk
+       and p_channel_tv = 'N'
+group by cp_catalog_page_id)
+ ,
+ wsr as
+ (select  web_site_id,
+          sum(ws_ext_sales_price) as sales,
+          sum(coalesce(wr_return_amt, 0)) as `returns`,
+          sum(ws_net_profit - coalesce(wr_net_loss, 0)) as profit
+  from web_sales left outer join web_returns on
+         (ws_item_sk = wr_item_sk and ws_order_number = wr_order_number),
+     date_dim,
+     web_site,
+     item,
+     promotion
+ where ws_sold_date_sk = d_date_sk
+       and d_date between cast('1998-08-04' as date)
+                  and (cast('1998-08-04' as date) +  interval 30 days)
+        and ws_web_site_sk = web_site_sk
+       and ws_item_sk = i_item_sk
+       and i_current_price > 50
+       and ws_promo_sk = p_promo_sk
+       and p_channel_tv = 'N'
+group by web_site_id)
+  select  channel
+        , id
+        , sum(sales) as sales
+        , sum(`returns`) as `returns`
+        , sum(profit) as profit
+ from
+ (select 'store channel' as channel
+        , 'store' || store_id as id
+        , sales
+        , `returns`
+        , profit
+ from   ssr
+ union all
+ select 'catalog channel' as channel
+        , 'catalog_page' || catalog_page_id as id
+        , sales
+        , `returns`
+        , profit
+ from  csr
+ union all
+ select 'web channel' as channel
+        , 'web_site' || web_site_id as id
+        , sales
+        , `returns`
+        , profit
+ from   wsr
+ ) x
+ group by rollup (channel, id)
+ order by channel
+         ,id
+ limit 100;
+---- RESULTS
+'catalog channel','catalog_pageAAAAAAAAAABAAAAA',11343.60,0.00,-8455.61
+'catalog channel','catalog_pageAAAAAAAAAADAAAAA',10707.91,2876.92,-18008.32
+'catalog channel','catalog_pageAAAAAAAAABBAAAAA',388.99,0.00,-385.50
+'catalog channel','catalog_pageAAAAAAAAABDAAAAA',271.89,0.00,102.33
+'catalog channel','catalog_pageAAAAAAAAACBAAAAA',45271.27,0.00,-10610.43
+'catalog channel','catalog_pageAAAAAAAAACDAAAAA',2390.88,0.00,-2864.36
+'catalog channel','catalog_pageAAAAAAAAADBAAAAA',669.44,0.00,94.72
+'catalog channel','catalog_pageAAAAAAAAADDAAAAA',15358.55,0.00,106.84
+'catalog channel','catalog_pageAAAAAAAAAEBAAAAA',281.80,0.00,-453.95
+'catalog channel','catalog_pageAAAAAAAAAEDAAAAA',18518.13,0.00,5728.56
+'catalog channel','catalog_pageAAAAAAAAAFDAAAAA',16526.48,197.58,-1389.80
+'catalog channel','catalog_pageAAAAAAAAAGDAAAAA',5929.11,0.00,-112.35
+'catalog channel','catalog_pageAAAAAAAAAHAAAAAA',54503.95,3463.68,13965.89
+'catalog channel','catalog_pageAAAAAAAAAIAAAAAA',2530.56,1003.03,-1026.80
+'catalog channel','catalog_pageAAAAAAAAAJAAAAAA',40512.67,0.00,5695.91
+'catalog channel','catalog_pageAAAAAAAAAKAAAAAA',18808.72,0.00,732.97
+'catalog channel','catalog_pageAAAAAAAAALAAAAAA',28215.09,4112.81,-13640.89
+'catalog channel','catalog_pageAAAAAAAAAMAAAAAA',32302.27,567.64,9530.88
+'catalog channel','catalog_pageAAAAAAAAANAAAAAA',26198.95,3218.98,335.22
+'catalog channel','catalog_pageAAAAAAAAANCAAAAA',3312.80,21.90,-1221.91
+'catalog channel','catalog_pageAAAAAAAAAOAAAAAA',3178.52,544.26,-9162.40
+'catalog channel','catalog_pageAAAAAAAAAOCAAAAA',1291.46,0.00,-1143.59
+'catalog channel','catalog_pageAAAAAAAAAPAAAAAA',15502.68,1219.50,-1185.99
+'catalog channel','catalog_pageAAAAAAAABABAAAAA',13412.36,0.00,1995.27
+'catalog channel','catalog_pageAAAAAAAABADAAAAA',1352.54,0.00,-3327.72
+'catalog channel','catalog_pageAAAAAAAABBBAAAAA',2900.78,0.00,-5454.12
+'catalog channel','catalog_pageAAAAAAAABBDAAAAA',12345.38,0.00,-2224.86
+'catalog channel','catalog_pageAAAAAAAABCBAAAAA',20.40,0.00,-512.20
+'catalog channel','catalog_pageAAAAAAAABCDAAAAA',8753.94,97.46,-2000.56
+'catalog channel','catalog_pageAAAAAAAABDBAAAAA',13505.86,0.00,-11090.53
+'catalog channel','catalog_pageAAAAAAAABDDAAAAA',1170.19,0.00,-187.02
+'catalog channel','catalog_pageAAAAAAAABEBAAAAA',27353.22,29.80,7942.47
+'catalog channel','catalog_pageAAAAAAAABFDAAAAA',4484.91,0.00,-101.44
+'catalog channel','catalog_pageAAAAAAAABHAAAAAA',12499.47,0.00,-5039.94
+'catalog channel','catalog_pageAAAAAAAABIAAAAAA',27239.18,269.31,-1272.29
+'catalog channel','catalog_pageAAAAAAAABJAAAAAA',27380.39,2428.52,-4449.84
+'catalog channel','catalog_pageAAAAAAAABKAAAAAA',12323.62,1131.59,-9207.22
+'catalog channel','catalog_pageAAAAAAAABKCAAAAA',726.00,0.00,-2502.06
+'catalog channel','catalog_pageAAAAAAAABLAAAAAA',35217.83,0.00,12178.97
+'catalog channel','catalog_pageAAAAAAAABLCAAAAA',416.58,0.00,-257.05
+'catalog channel','catalog_pageAAAAAAAABMAAAAAA',29249.68,2447.46,-18367.34
+'catalog channel','catalog_pageAAAAAAAABNAAAAAA',39939.39,11895.60,9443.01
+'catalog channel','catalog_pageAAAAAAAABNCAAAAA',829.25,0.00,-432.14
+'catalog channel','catalog_pageAAAAAAAABOAAAAAA',4776.55,645.29,-12353.36
+'catalog channel','catalog_pageAAAAAAAABOCAAAAA',4132.80,3911.40,-502.34
+'catalog channel','catalog_pageAAAAAAAABPAAAAAA',3780.14,0.00,-2369.55
+'catalog channel','catalog_pageAAAAAAAACABAAAAA',13864.50,286.83,1568.92
+'catalog channel','catalog_pageAAAAAAAACADAAAAA',5004.63,0.00,-2452.32
+'catalog channel','catalog_pageAAAAAAAACBBAAAAA',5458.11,0.00,-2324.38
+'catalog channel','catalog_pageAAAAAAAACBDAAAAA',7448.97,1110.56,-1789.38
+'catalog channel','catalog_pageAAAAAAAACCBAAAAA',11176.44,0.00,-9025.80
+'catalog channel','catalog_pageAAAAAAAACCDAAAAA',9009.45,0.00,-2737.84
+'catalog channel','catalog_pageAAAAAAAACDBAAAAA',12882.97,0.00,-4257.79
+'catalog channel','catalog_pageAAAAAAAACDDAAAAA',2039.06,429.36,-620.74
+'catalog channel','catalog_pageAAAAAAAACEBAAAAA',5189.62,508.68,-4592.89
+'catalog channel','catalog_pageAAAAAAAACEDAAAAA',11068.07,0.00,-828.38
+'catalog channel','catalog_pageAAAAAAAACFDAAAAA',861.40,0.00,223.80
+'catalog channel','catalog_pageAAAAAAAACHAAAAAA',16829.91,2360.82,-4144.50
+'catalog channel','catalog_pageAAAAAAAACIAAAAAA',36094.60,0.00,797.28
+'catalog channel','catalog_pageAAAAAAAACJAAAAAA',16513.90,0.00,-2482.24
+'catalog channel','catalog_pageAAAAAAAACKAAAAAA',20101.46,18.52,-12781.69
+'catalog channel','catalog_pageAAAAAAAACLAAAAAA',6942.39,0.00,-2652.10
+'catalog channel','catalog_pageAAAAAAAACLCAAAAA',4435.30,2478.55,1582.13
+'catalog channel','catalog_pageAAAAAAAACMAAAAAA',10339.92,714.59,-3810.28
+'catalog channel','catalog_pageAAAAAAAACNAAAAAA',24171.31,219.63,-6876.07
+'catalog channel','catalog_pageAAAAAAAACOAAAAAA',13386.50,51.66,-7675.85
+'catalog channel','catalog_pageAAAAAAAACPAAAAAA',6673.28,170.28,1669.79
+'catalog channel','catalog_pageAAAAAAAACPCAAAAA',4929.64,0.00,-4073.43
+'catalog channel','catalog_pageAAAAAAAADABAAAAA',17341.07,0.00,2722.99
+'catalog channel','catalog_pageAAAAAAAADADAAAAA',32122.29,0.00,13409.04
+'catalog channel','catalog_pageAAAAAAAADBBAAAAA',29586.90,1697.61,8235.35
+'catalog channel','catalog_pageAAAAAAAADBDAAAAA',3357.38,0.00,702.69
+'catalog channel','catalog_pageAAAAAAAADCBAAAAA',5549.77,66.06,-2859.07
+'catalog channel','catalog_pageAAAAAAAADCDAAAAA',3286.08,0.00,753.55
+'catalog channel','catalog_pageAAAAAAAADDBAAAAA',3698.78,0.00,-8553.47
+'catalog channel','catalog_pageAAAAAAAADDDAAAAA',1939.50,0.00,-3540.68
+'catalog channel','catalog_pageAAAAAAAADEBAAAAA',3836.53,0.00,-3502.31
+'catalog channel','catalog_pageAAAAAAAADEDAAAAA',18565.37,2379.24,2082.84
+'catalog channel','catalog_pageAAAAAAAADFDAAAAA',14363.88,76.05,7598.96
+'catalog channel','catalog_pageAAAAAAAADHAAAAAA',9513.67,0.00,-786.64
+'catalog channel','catalog_pageAAAAAAAADIAAAAAA',37019.24,0.00,2227.96
+'catalog channel','catalog_pageAAAAAAAADJAAAAAA',23475.03,4540.20,2173.17
+'catalog channel','catalog_pageAAAAAAAADKAAAAAA',18901.65,0.00,3918.05
+'catalog channel','catalog_pageAAAAAAAADLAAAAAA',28825.95,566.80,-9241.85
+'catalog channel','catalog_pageAAAAAAAADMAAAAAA',40676.08,1322.46,-6120.84
+'catalog channel','catalog_pageAAAAAAAADNAAAAAA',28061.92,109.98,-1584.13
+'catalog channel','catalog_pageAAAAAAAADOAAAAAA',6751.41,0.00,-1356.88
+'catalog channel','catalog_pageAAAAAAAADOCAAAAA',5190.10,0.00,-238.00
+'catalog channel','catalog_pageAAAAAAAADPAAAAAA',10347.89,0.00,-7517.40
+'catalog channel','catalog_pageAAAAAAAAEABAAAAA',22553.74,10270.04,-7133.94
+'catalog channel','catalog_pageAAAAAAAAEADAAAAA',2807.40,0.00,-1325.01
+'catalog channel','catalog_pageAAAAAAAAEBBAAAAA',3230.69,0.00,-4409.06
+'catalog channel','catalog_pageAAAAAAAAEBDAAAAA',11651.24,0.00,-8437.86
+'catalog channel','catalog_pageAAAAAAAAECBAAAAA',23100.03,0.00,4471.83
+'catalog channel','catalog_pageAAAAAAAAECDAAAAA',580.53,0.00,-5204.99
+'catalog channel','catalog_pageAAAAAAAAEDBAAAAA',11601.95,2958.72,-5116.64
+'catalog channel','catalog_pageAAAAAAAAEDDAAAAA',20014.23,7330.00,-3652.86
+'catalog channel','catalog_pageAAAAAAAAEEBAAAAA',19857.72,0.00,5511.15
+'catalog channel','catalog_pageAAAAAAAAEEDAAAAA',8554.80,0.00,-10166.86
+'catalog channel','catalog_pageAAAAAAAAEFDAAAAA',38432.66,11077.80,10213.68
+---- TYPES
+STRING, STRING, DECIMAL, DECIMAL, DECIMAL
+====
diff --git a/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q86.test b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q86.test
new file mode 100644
index 0000000..36562bf
--- /dev/null
+++ b/testdata/workloads/tpcds/queries/tpcds-decimal_v2-q86.test
@@ -0,0 +1,131 @@
+====
+---- QUERY: TPCDS-Q86
+select
+    sum(ws_net_paid) as total_sum
+   ,i_category
+   ,i_class
+   ,grouping(i_category)+grouping(i_class) as lochierarchy
+   ,rank() over (
+        partition by grouping(i_category)+grouping(i_class),
+        case when grouping(i_class) = 0 then i_category end
+        order by sum(ws_net_paid) desc) as rank_within_parent
+ from
+    web_sales
+   ,date_dim       d1
+   ,item
+ where
+    d1.d_month_seq between 1212 and 1212+11
+ and d1.d_date_sk = ws_sold_date_sk
+ and i_item_sk  = ws_item_sk
+ group by rollup(i_category,i_class)
+ order by
+   lochierarchy desc,
+  -- Modified from original because Impala does not allow aliases within order by
+  -- expressions, except at the top level.
+   case when grouping(i_category)+grouping(i_class) = 0 then i_category end,
+   rank_within_parent
+ limit 100;
+---- RESULTS
+335298083.49,'NULL','NULL',2,1
+35668385.24,'Music','NULL',1,1
+34110308.09,'Men','NULL',1,2
+33945859.14,'Shoes','NULL',1,3
+33779396.33,'Home','NULL',1,4
+33660827.11,'Electronics','NULL',1,5
+33471238.87,'Women','NULL',1,6
+33048439.90,'Books','NULL',1,7
+32553196.48,'Sports','NULL',1,8
+32454111.82,'Children','NULL',1,9
+31955775.63,'Jewelry','NULL',1,10
+650544.88,'NULL','NULL',1,11
+2603917.26,'Books','reference',0,1
+2500411.13,'Books','history',0,2
+2392291.39,'Books','computers',0,3
+2278162.58,'Books','home repair',0,4
+2264632.73,'Books','romance',0,5
+2204536.79,'Books','fiction',0,6
+2174686.53,'Books','travel',0,7
+2101508.76,'Books','entertainments',0,8
+2092734.81,'Books','parenting',0,9
+2037140.80,'Books','sports',0,10
+1920062.16,'Books','self-help',0,11
+1791937.78,'Books','mystery',0,12
+1747449.84,'Books','cooking',0,13
+1744147.13,'Books','science',0,14
+1629493.52,'Books','business',0,15
+1535662.83,'Books','arts',0,16
+29663.86,'Books','NULL',0,17
+8427198.29,'Children','school-uniforms',0,1
+8170040.55,'Children','infants',0,2
+8030368.31,'Children','newborn',0,3
+7772368.18,'Children','toddlers',0,4
+54136.49,'Children','NULL',0,5
+2665898.32,'Electronics','karoke',0,1
+2647708.85,'Electronics','dvd/vcr players',0,2
+2327655.83,'Electronics','audio',0,3
+2320597.52,'Electronics','monitors',0,4
+2252767.09,'Electronics','memory',0,5
+2222688.75,'Electronics','personal',0,6
+2094873.27,'Electronics','televisions',0,7
+2060395.64,'Electronics','musical',0,8
+2021383.60,'Electronics','portable',0,9
+1960699.97,'Electronics','stereo',0,10
+1955981.28,'Electronics','wireless',0,11
+1931224.93,'Electronics','disk drives',0,12
+1870348.87,'Electronics','cameras',0,13
+1804127.22,'Electronics','scanners',0,14
+1800889.53,'Electronics','automotive',0,15
+1723586.44,'Electronics','camcorders',0,16
+2678321.52,'Home','bedding',0,1
+2544675.48,'Home','glassware',0,2
+2454928.54,'Home','tables',0,3
+2258540.99,'Home','curtains/drapes',0,4
+2191949.77,'Home','lighting',0,5
+2187564.96,'Home','wallpaper',0,6
+2172959.94,'Home','flatware',0,7
+2159059.95,'Home','decor',0,8
+2056828.93,'Home','paint',0,9
+2053061.41,'Home','rugs',0,10
+1893240.65,'Home','kids',0,11
+1856421.87,'Home','bathroom',0,12
+1856300.04,'Home','furniture',0,13
+1812963.47,'Home','blinds/shades',0,14
+1780974.36,'Home','accent',0,15
+1711990.64,'Home','mattresses',0,16
+109613.81,'Home','NULL',0,17
+2736956.17,'Jewelry','custom',0,1
+2638200.54,'Jewelry','jewelry boxes',0,2
+2279983.95,'Jewelry','bracelets',0,3
+2154865.44,'Jewelry','womens watch',0,4
+2140041.02,'Jewelry','rings',0,5
+2093041.31,'Jewelry','costume',0,6
+2088949.87,'Jewelry','pendants',0,7
+1995895.68,'Jewelry','diamonds',0,8
+1882780.88,'Jewelry','birdal',0,9
+1852053.82,'Jewelry','gold',0,10
+1817697.92,'Jewelry','mens watch',0,11
+1731142.74,'Jewelry','estate',0,12
+1713259.20,'Jewelry','earings',0,13
+1697803.23,'Jewelry','loose stones',0,14
+1606289.52,'Jewelry','semi-precious',0,15
+1494189.90,'Jewelry','consignment',0,16
+32624.44,'Jewelry','NULL',0,17
+9606467.15,'Men','shirts',0,1
+8471403.12,'Men','accessories',0,2
+8354664.10,'Men','pants',0,3
+7636055.87,'Men','sports-apparel',0,4
+41717.85,'Men','NULL',0,5
+9768150.20,'Music','country',0,1
+9247280.12,'Music','classical',0,2
+8872740.98,'Music','pop',0,3
+7628414.40,'Music','rock',0,4
+151799.54,'Music','NULL',0,5
+9322023.00,'Shoes','mens',0,1
+8773114.36,'Shoes','womens',0,2
+8179271.60,'Shoes','athletic',0,3
+7631585.15,'Shoes','kids',0,4
+39865.03,'Shoes','NULL',0,5
+2638814.24,'Sports','pools',0,1
+---- TYPES
+DECIMAL, STRING, STRING, SMALLINT, BIGINT
+====
diff --git a/tests/query_test/test_tpcds_queries.py b/tests/query_test/test_tpcds_queries.py
index 40d9dfe..13d0ea0 100644
--- a/tests/query_test/test_tpcds_queries.py
+++ b/tests/query_test/test_tpcds_queries.py
@@ -414,6 +414,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q26(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q26', vector)
 
+  def test_tpcds_q27(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q27', vector)
+
   def test_tpcds_q29(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q29', vector)
 
@@ -432,6 +435,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q34(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q34', vector)
 
+  def test_tpcds_q36(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q36', vector)
+
   def test_tpcds_q37(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q37', vector)
 
@@ -531,6 +537,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q69(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q69', vector)
 
+  def test_tpcds_q70(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q70', vector)
+
   def test_tpcds_q70a(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q70a', vector)
 
@@ -564,6 +573,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q79(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q79', vector)
 
+  def test_tpcds_q80(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q80', vector)
+
   def test_tpcds_q80a(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q80a', vector)
 
@@ -582,6 +594,9 @@ class TestTpcdsDecimalV2Query(ImpalaTestSuite):
   def test_tpcds_q85(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q85', vector)
 
+  def test_tpcds_q86(self, vector):
+    self.run_test_case(self.get_workload() + '-decimal_v2-q86', vector)
+
   def test_tpcds_q86a(self, vector):
     self.run_test_case(self.get_workload() + '-decimal_v2-q86a', vector)
 
diff --git a/tests/util/parse_util.py b/tests/util/parse_util.py
index c5105ee..57f546e 100644
--- a/tests/util/parse_util.py
+++ b/tests/util/parse_util.py
@@ -22,7 +22,7 @@ from datetime import datetime
 # changed, and the stress test loses the ability to run the full set of queries. Set
 # these constants and assert that when a workload is used, all the queries we expect to
 # use are there.
-EXPECTED_TPCDS_QUERIES_COUNT = 92
+EXPECTED_TPCDS_QUERIES_COUNT = 97
 EXPECTED_TPCH_NESTED_QUERIES_COUNT = 22
 EXPECTED_TPCH_QUERIES_COUNT = 22
 # Add the number of stress test specific queries, i.e. in files like '*-stress-*.test'