gsp-all-latest-2015-03-13 -- rebased
authorAndres Freund <andres@anarazel.de>
Mon, 11 May 2015 17:32:41 +0000 (19:32 +0200)
committerAndres Freund <andres@anarazel.de>
Mon, 11 May 2015 17:32:41 +0000 (19:32 +0200)
59 files changed:
contrib/pg_stat_statements/pg_stat_statements.c
doc/src/sgml/func.sgml
doc/src/sgml/queries.sgml
doc/src/sgml/ref/select.sgml
src/backend/catalog/sql_features.txt
src/backend/commands/explain.c
src/backend/executor/execQual.c
src/backend/executor/execUtils.c
src/backend/executor/nodeAgg.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/list.c
src/backend/nodes/makefuncs.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/plan/analyzejoins.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planagg.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/prep/prepunion.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/pathnode.c
src/backend/optimizer/util/tlist.c
src/backend/optimizer/util/var.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_agg.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_target.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rewriteManip.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/adt/selfuncs.c
src/include/commands/explain.h
src/include/nodes/execnodes.h
src/include/nodes/makefuncs.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/pg_list.h
src/include/nodes/plannodes.h
src/include/nodes/primnodes.h
src/include/nodes/relation.h
src/include/optimizer/planmain.h
src/include/optimizer/tlist.h
src/include/parser/kwlist.h
src/include/parser/parse_agg.h
src/include/parser/parse_clause.h
src/include/utils/selfuncs.h
src/test/regress/expected/groupingsets.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/groupingsets.sql [new file with mode: 0644]

index 6abe3f0770a119e2389c7fdb166872e2486cd0dc..c4d3ee50148efd0455bbf1e9edc1f340264645da 100644 (file)
@@ -2267,6 +2267,7 @@ JumbleQuery(pgssJumbleState *jstate, Query *query)
    JumbleExpr(jstate, (Node *) query->onConflict);
    JumbleExpr(jstate, (Node *) query->returningList);
    JumbleExpr(jstate, (Node *) query->groupClause);
+   JumbleExpr(jstate, (Node *) query->groupingSets);
    JumbleExpr(jstate, query->havingQual);
    JumbleExpr(jstate, (Node *) query->windowClause);
    JumbleExpr(jstate, (Node *) query->distinctClause);
@@ -2397,6 +2398,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
                JumbleExpr(jstate, (Node *) expr->aggfilter);
            }
            break;
+       case T_GroupingFunc:
+           {
+               GroupingFunc *grpnode = (GroupingFunc *) node;
+
+               JumbleExpr(jstate, (Node *) grpnode->refs);
+           }
+           break;
        case T_WindowFunc:
            {
                WindowFunc *expr = (WindowFunc *) node;
@@ -2698,6 +2706,12 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
                JumbleExpr(jstate, (Node *) lfirst(temp));
            }
            break;
+       case T_IntList:
+           foreach(temp, (List *) node)
+           {
+               APP_JUMB(lfirst_int(temp));
+           }
+           break;
        case T_SortGroupClause:
            {
                SortGroupClause *sgc = (SortGroupClause *) node;
@@ -2708,6 +2722,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
                APP_JUMB(sgc->nulls_first);
            }
            break;
+       case T_GroupingSet:
+           {
+               GroupingSet *gsnode = (GroupingSet *) node;
+
+               JumbleExpr(jstate, (Node *) gsnode->content);
+           }
+           break;
        case T_WindowClause:
            {
                WindowClause *wc = (WindowClause *) node;
index fb397316048cca72b6293b466760f08b58e4f207..8d998d1925039cb36f8c343f2fdfa0efaeb3ee27 100644 (file)
@@ -12166,7 +12166,9 @@ NULL baz</literallayout>(3 rows)</entry>
    <xref linkend="functions-aggregate-statistics-table">.
    The built-in ordered-set aggregate functions
    are listed in <xref linkend="functions-orderedset-table"> and
-   <xref linkend="functions-hypothetical-table">.
+   <xref linkend="functions-hypothetical-table">.  Grouping operations,
+   which are closely related to aggregate functions, are listed in
+   <xref linkend="functions-grouping-table">.
    The special syntax considerations for aggregate
    functions are explained in <xref linkend="syntax-aggregates">.
    Consult <xref linkend="tutorial-agg"> for additional introductory
@@ -13264,6 +13266,72 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab;
    to the rule specified in the <literal>ORDER BY</> clause.
   </para>
 
+  <table id="functions-grouping-table">
+   <title>Grouping Operations</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Function</entry>
+      <entry>Return Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>GROUPING</primary>
+       </indexterm>
+       <function>GROUPING(<replaceable class="parameter">args...</replaceable>)</function>
+      </entry>
+      <entry>
+       <type>integer</type>
+      </entry>
+      <entry>
+       Integer bitmask indicating which arguments are not being included in the current
+       grouping set
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+   <para>
+    Grouping operations are used in conjunction with grouping sets (see
+    <xref linkend="queries-grouping-sets">) to distinguish result rows.  The
+    arguments to the <literal>GROUPING</> operation are not actually evaluated,
+    but they must match exactly expressions given in the <literal>GROUP BY</>
+    clause of the current query level.  Bits are assigned with the rightmost
+    argument being the least-significant bit; each bit is 0 if the corresponding
+    expression is included in the grouping criteria of the grouping set generating
+    the result row, and 1 if it is not.  For example:
+<screen>
+<prompt>=&gt;</> <userinput>SELECT * FROM items_sold;</>
+ make  | model | sales
+-------+-------+-------
+ Foo   | GT    |  10
+ Foo   | Tour  |  20
+ Bar   | City  |  15
+ Bar   | Sport |  5
+(4 rows)
+
+<prompt>=&gt;</> <userinput>SELECT make, model, GROUPING(make,model), sum(sales) FROM items_sold GROUP BY ROLLUP(make,model);</>
+ make  | model | grouping | sum
+-------+-------+----------+-----
+ Foo   | GT    |        0 | 10
+ Foo   | Tour  |        0 | 20
+ Bar   | City  |        0 | 15
+ Bar   | Sport |        0 | 5
+ Foo   |       |        1 | 30
+ Bar   |       |        1 | 20
+       |       |        3 | 50
+(7 rows)
+</screen>
+   </para>
+
  </sect1>
 
  <sect1 id="functions-window">
index 7dbad462a5dd5ab01cb3d05e1197446393a93e05..56419c76b139d93a00b6df455cfe6d05eb0579cf 100644 (file)
@@ -1183,6 +1183,184 @@ SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit
    </para>
   </sect2>
 
+  <sect2 id="queries-grouping-sets">
+   <title><literal>GROUPING SETS</>, <literal>CUBE</>, and <literal>ROLLUP</></title>
+
+   <indexterm zone="queries-grouping-sets">
+    <primary>GROUPING SETS</primary>
+   </indexterm>
+   <indexterm zone="queries-grouping-sets">
+    <primary>CUBE</primary>
+   </indexterm>
+   <indexterm zone="queries-grouping-sets">
+    <primary>ROLLUP</primary>
+   </indexterm>
+   <indexterm zone="queries-grouping-sets">
+    <primary>grouping sets</primary>
+   </indexterm>
+
+   <para>
+    More complex grouping operations than those described above are possible
+    using the concept of <firstterm>grouping sets</>.  The data selected by
+    the <literal>FROM</> and <literal>WHERE</> clauses is grouped separately
+    by each specified grouping set, aggregates computed for each group just as
+    for simple <literal>GROUP BY</> clauses, and then the results returned.
+    For example:
+<screen>
+<prompt>=&gt;</> <userinput>SELECT * FROM items_sold;</>
+ brand | size | sales
+-------+------+-------
+ Foo   | L    |  10
+ Foo   | M    |  20
+ Bar   | M    |  15
+ Bar   | L    |  5
+(4 rows)
+
+<prompt>=&gt;</> <userinput>SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());</>
+ brand | size | sum
+-------+------+-----
+ Foo   |      |  30
+ Bar   |      |  20
+       | L    |  15
+       | M    |  35
+       |      |  50
+(5 rows)
+</screen>
+   </para>
+
+   <para>
+    Each sublist of <literal>GROUPING SETS</> may specify zero or more columns
+    or expressions and is interpreted the same way as though it were directly
+    in the <literal>GROUP BY</> clause.  An empty grouping set means that all
+    rows are aggregated down to a single group (which is output even if no
+    input rows were present), as described above for the case of aggregate
+    functions with no <literal>GROUP BY</> clause.
+   </para>
+
+   <para>
+    References to the grouping columns or expressions are replaced
+    by <literal>NULL</> values in result rows for grouping sets in which those
+    columns do not appear.  To distinguish which grouping a particular output
+    row resulted from, see <xref linkend="functions-grouping-table">.
+   </para>
+
+   <para>
+    A shorthand notation is provided for specifying two common types of grouping set.
+    A clause of the form
+<programlisting>
+ROLLUP ( <replaceable>e1</>, <replaceable>e2</>, <replaceable>e3</>, ... )
+</programlisting>
+    represents the given list of expressions and all prefixes of the list including
+    the empty list; thus it is equivalent to
+<programlisting>
+GROUPING SETS (
+    ( <replaceable>e1</>, <replaceable>e2</>, <replaceable>e3</>, ... ),
+    ...
+    ( <replaceable>e1</>, <replaceable>e2</> )
+    ( <replaceable>e1</> )
+    ( )
+)
+</programlisting>
+    This is commonly used for analysis over hierarchical data; e.g. total
+    salary by department, division, and company-wide total.
+   </para>
+
+   <para>
+    A clause of the form
+<programlisting>
+CUBE ( <replaceable>e1</>, <replaceable>e2</>, ... )
+</programlisting>
+    represents the given list and all of its possible subsets (i.e. the power
+    set).  Thus
+<programlisting>
+CUBE ( a, b, c )
+</programlisting>
+    is equivalent to
+<programlisting>
+GROUPING SETS (
+    ( a, b, c ),
+    ( a, b    ),
+    ( a,    c ),
+    ( a       ),
+    (    b, c ),
+    (    b    ),
+    (       c ),
+    (         ),
+)
+</programlisting>
+   </para>
+
+   <para>
+    The individual elements of a <literal>CUBE</> or <literal>ROLLUP</>
+    clause may be either individual expressions, or sub-lists of elements in
+    parentheses.  In the latter case, the sub-lists are treated as single
+    units for the purposes of generating the individual grouping sets.
+    For example:
+<programlisting>
+CUBE ( (a,b), (c,d) )
+</programlisting>
+    is equivalent to
+<programlisting>
+GROUPING SETS (
+    ( a, b, c, d )
+    ( a, b       )
+    (       c, d )
+    (            )
+)
+</programlisting>
+    and
+<programlisting>
+ROLLUP ( a, (b,c), d )
+</programlisting>
+    is equivalent to
+<programlisting>
+GROUPING SETS (
+    ( a, b, c, d )
+    ( a, b, c    )
+    ( a          )
+    (            )
+)
+</programlisting>
+   </para>
+
+   <para>
+    The <literal>CUBE</> and <literal>ROLLUP</> constructs can be used either
+    directly in the <literal>GROUP BY</> clause, or nested inside a
+    <literal>GROUPING SETS</> clause.  If one <literal>GROUPING SETS</> clause
+    is nested inside another, the effect is the same as if all the elements of
+    the inner clause had been written directly in the outer clause.
+   </para>
+
+   <para>
+    If multiple grouping items are specified in a single <literal>GROUP BY</>
+    clause, then the final list of grouping sets is the cross product of the
+    individual items.  For example:
+<programlisting>
+GROUP BY a, CUBE(b,c), GROUPING SETS ((d), (e))
+</programlisting>
+    is equivalent to
+<programlisting>
+GROUP BY GROUPING SETS (
+  (a,b,c,d), (a,b,c,e),
+  (a,b,d),   (a,b,e),
+  (a,c,d),   (a,c,e),
+  (a,d),     (a,e)
+)
+</programlisting>
+   </para>
+
+  <note>
+   <para>
+    The construct <literal>(a,b)</> is normally recognized in expressions as
+    a <link linkend="sql-syntax-row-constructors">row constructor</link>.
+    Within the <literal>GROUP BY</> clause, this does not apply at the top
+    levels of expressions, and <literal>(a,b)</> is parsed as a list of
+    expressions as described above.  If for some reason you <emphasis>need</>
+    a row constructor in a grouping expression, use <literal>ROW(a,b)</>.
+   </para>
+  </note>
+  </sect2>
+
   <sect2 id="queries-window">
    <title>Window Function Processing</title>
 
index 2295f63c1305a90d873e02895aec7f39b2d06354..752d8a16e428718baea90472ffeb127b6144c710 100644 (file)
@@ -37,7 +37,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ * | <replaceable class="parameter">expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
     [ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
     [ WHERE <replaceable class="parameter">condition</replaceable> ]
-    [ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...] ]
+    [ GROUP BY <replaceable class="parameter">grouping_element</replaceable> [, ...] ]
     [ HAVING <replaceable class="parameter">condition</replaceable> [, ...] ]
     [ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...] ]
     [ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] <replaceable class="parameter">select</replaceable> ]
@@ -60,6 +60,15 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
 
+<phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
+
+    ( )
+    <replaceable class="parameter">expression</replaceable>
+    ( <replaceable class="parameter">expression</replaceable> [, ...] )
+    ROLLUP ( { <replaceable class="parameter">expression</replaceable> | ( <replaceable class="parameter">expression</replaceable> [, ...] ) } [, ...] )
+    CUBE ( { <replaceable class="parameter">expression</replaceable> | ( <replaceable class="parameter">expression</replaceable> [, ...] ) } [, ...] )
+    GROUPING SETS ( <replaceable class="parameter">grouping_element</replaceable> [, ...] )
+
 <phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase>
 
     <replaceable class="parameter">with_query_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] AS ( <replaceable class="parameter">select</replaceable> | <replaceable class="parameter">values</replaceable> | <replaceable class="parameter">insert</replaceable> | <replaceable class="parameter">update</replaceable> | <replaceable class="parameter">delete</replaceable> )
@@ -621,22 +630,34 @@ WHERE <replaceable class="parameter">condition</replaceable>
    <para>
     The optional <literal>GROUP BY</literal> clause has the general form
 <synopsis>
-GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
+GROUP BY <replaceable class="parameter">grouping_element</replaceable> [, ...]
 </synopsis>
    </para>
 
    <para>
     <literal>GROUP BY</literal> will condense into a single row all
     selected rows that share the same values for the grouped
-    expressions.  <replaceable
-    class="parameter">expression</replaceable> can be an input column
-    name, or the name or ordinal number of an output column
-    (<command>SELECT</command> list item), or an arbitrary
+    expressions.  An <replaceable
+    class="parameter">expression</replaceable> used inside a
+    <replaceable class="parameter">grouping_element</replaceable>
+    can be an input column name, or the name or ordinal number of an
+    output column (<command>SELECT</command> list item), or an arbitrary
     expression formed from input-column values.  In case of ambiguity,
     a <literal>GROUP BY</literal> name will be interpreted as an
     input-column name rather than an output column name.
    </para>
 
+   <para>
+    If any of <literal>GROUPING SETS</>, <literal>ROLLUP</> or
+    <literal>CUBE</> are present as grouping elements, then the
+    <literal>GROUP BY</> clause as a whole defines some number of
+    independent <replaceable>grouping sets</>.  The effect of this is
+    equivalent to constructing a <literal>UNION ALL</> between
+    subqueries with the individual grouping sets as their
+    <literal>GROUP BY</> clauses.  For further details on the handling
+    of grouping sets see <xref linkend="queries-grouping-sets">.
+   </para>
+
    <para>
     Aggregate functions, if any are used, are computed across all rows
     making up each group, producing a separate value for each group.
index cc0f8c45a6d7451744da69d56dfb277682378616..0207c0f3fb62694d2158a9d9ce663083b46e42ea 100644 (file)
@@ -467,9 +467,9 @@ T331    Basic roles         YES
 T332   Extended roles          NO  mostly supported
 T341   Overloading of SQL-invoked functions and procedures         YES 
 T351   Bracketed SQL comments (/*...*/ comments)           YES 
-T431   Extended grouping capabilities          NO  
-T432   Nested and concatenated GROUPING SETS           NO  
-T433   Multiargument GROUPING function         NO  
+T431   Extended grouping capabilities          YES 
+T432   Nested and concatenated GROUPING SETS           YES 
+T433   Multiargument GROUPING function         YES 
 T434   GROUP BY DISTINCT           NO  
 T441   ABS and MOD functions           YES 
 T461   Symmetric BETWEEN predicate         YES 
index eeb8f19017efcae22937b44a6a07b5a276fa6bf2..28a6787a2a28f66196fff048312d90d918ef3318 100644 (file)
@@ -82,6 +82,9 @@ static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
                       ExplainState *es);
 static void show_agg_keys(AggState *astate, List *ancestors,
              ExplainState *es);
+static void show_grouping_set_keys(PlanState *planstate, const char *qlabel,
+               int nkeys, AttrNumber *keycols, List *gsets,
+               List *ancestors, ExplainState *es);
 static void show_group_keys(GroupState *gstate, List *ancestors,
                ExplainState *es);
 static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
@@ -988,6 +991,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
                    pname = "GroupAggregate";
                    strategy = "Sorted";
                    break;
+               case AGG_CHAINED:
+                   pname = "ChainAggregate";
+                   strategy = "Chained";
+                   break;
                case AGG_HASHED:
                    pname = "HashAggregate";
                    strategy = "Hashed";
@@ -1830,18 +1837,78 @@ show_agg_keys(AggState *astate, List *ancestors,
 {
    Agg        *plan = (Agg *) astate->ss.ps.plan;
 
-   if (plan->numCols > 0)
+   if (plan->numCols > 0 || plan->groupingSets)
    {
        /* The key columns refer to the tlist of the child plan */
        ancestors = lcons(astate, ancestors);
-       show_sort_group_keys(outerPlanState(astate), "Group Key",
-                            plan->numCols, plan->grpColIdx,
-                            NULL, NULL, NULL,
-                            ancestors, es);
+
+       if (plan->groupingSets)
+           show_grouping_set_keys(outerPlanState(astate), "Grouping Sets",
+                                  plan->numCols, plan->grpColIdx,
+                                  plan->groupingSets,
+                                  ancestors, es);
+       else
+           show_sort_group_keys(outerPlanState(astate), "Group Key",
+                                plan->numCols, plan->grpColIdx,
+                                NULL, NULL, NULL,
+                                ancestors, es);
+
        ancestors = list_delete_first(ancestors);
    }
 }
 
+static void
+show_grouping_set_keys(PlanState *planstate, const char *qlabel,
+                      int nkeys, AttrNumber *keycols, List *gsets,
+                      List *ancestors, ExplainState *es)
+{
+   Plan       *plan = planstate->plan;
+   List       *context;
+   bool        useprefix;
+   char       *exprstr;
+   ListCell   *lc;
+
+   if (gsets == NIL)
+       return;
+
+   /* Set up deparsing context */
+   context = set_deparse_context_planstate(es->deparse_cxt,
+                                           (Node *) planstate,
+                                           ancestors);
+   useprefix = (list_length(es->rtable) > 1 || es->verbose);
+
+   ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
+
+   foreach(lc, gsets)
+   {
+       List       *result = NIL;
+       ListCell   *lc2;
+
+       foreach(lc2, (List *) lfirst(lc))
+       {
+           Index       i = lfirst_int(lc2);
+           AttrNumber  keyresno = keycols[i];
+           TargetEntry *target = get_tle_by_resno(plan->targetlist,
+                                                  keyresno);
+
+           if (!target)
+               elog(ERROR, "no tlist entry for key %d", keyresno);
+           /* Deparse the expression, showing any top-level cast */
+           exprstr = deparse_expression((Node *) target->expr, context,
+                                        useprefix, true);
+
+           result = lappend(result, exprstr);
+       }
+
+       if (!result && es->format == EXPLAIN_FORMAT_TEXT)
+           ExplainPropertyText("Group Key", "()", es);
+       else
+           ExplainPropertyListNested("Group Key", result, es);
+   }
+
+   ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
+}
+
 /*
  * Show the grouping keys for a Group node.
  */
@@ -2590,6 +2657,52 @@ ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
    }
 }
 
+/*
+ * Explain a property that takes the form of a list of unlabeled items within
+ * another list.  "data" is a list of C strings.
+ */
+void
+ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
+{
+   ListCell   *lc;
+   bool        first = true;
+
+   switch (es->format)
+   {
+       case EXPLAIN_FORMAT_TEXT:
+       case EXPLAIN_FORMAT_XML:
+           ExplainPropertyList(qlabel, data, es);
+           return;
+
+       case EXPLAIN_FORMAT_JSON:
+           ExplainJSONLineEnding(es);
+           appendStringInfoSpaces(es->str, es->indent * 2);
+           appendStringInfoChar(es->str, '[');
+           foreach(lc, data)
+           {
+               if (!first)
+                   appendStringInfoString(es->str, ", ");
+               escape_json(es->str, (const char *) lfirst(lc));
+               first = false;
+           }
+           appendStringInfoChar(es->str, ']');
+           break;
+
+       case EXPLAIN_FORMAT_YAML:
+           ExplainYAMLLineStarting(es);
+           appendStringInfoString(es->str, "- [");
+           foreach(lc, data)
+           {
+               if (!first)
+                   appendStringInfoString(es->str, ", ");
+               escape_yaml(es->str, (const char *) lfirst(lc));
+               first = false;
+           }
+           appendStringInfoChar(es->str, ']');
+           break;
+   }
+}
+
 /*
  * Explain a simple property.
  *
index d94fe581df366c11385e7f4c61770944db00aab0..97bfbbc3264985d49c425da30080ddf5dc1beb8b 100644 (file)
@@ -74,6 +74,8 @@ static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
                  bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
                      bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalScalarGroupedVarFast(ExprState *exprstate, ExprContext *econtext,
+                     bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate,
                    ExprContext *econtext,
                    bool *isNull, ExprDoneCond *isDone);
@@ -181,6 +183,9 @@ static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
                        bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
                      bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
+                       ExprContext *econtext,
+                       bool *isNull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -558,6 +563,8 @@ ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext,
  * Note: ExecEvalScalarVar is executed only the first time through in a given
  * plan; it changes the ExprState's function pointer to pass control directly
  * to ExecEvalScalarVarFast after making one-time checks.
+ *
+ * We share this code with GroupedVar for simplicity.
  * ----------------------------------------------------------------
  */
 static Datum
@@ -635,8 +642,24 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
        }
    }
 
-   /* Skip the checking on future executions of node */
-   exprstate->evalfunc = ExecEvalScalarVarFast;
+   if (IsA(variable, GroupedVar))
+   {
+       Assert(variable->varno == OUTER_VAR);
+
+       /* Skip the checking on future executions of node */
+       exprstate->evalfunc = ExecEvalScalarGroupedVarFast;
+
+       if (!bms_is_member(attnum, econtext->grouped_cols))
+       {
+           *isNull = true;
+           return (Datum) 0;
+       }
+   }
+   else
+   {
+       /* Skip the checking on future executions of node */
+       exprstate->evalfunc = ExecEvalScalarVarFast;
+   }
 
    /* Fetch the value from the slot */
    return slot_getattr(slot, attnum, isNull);
@@ -684,6 +707,31 @@ ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
    return slot_getattr(slot, attnum, isNull);
 }
 
+static Datum
+ExecEvalScalarGroupedVarFast(ExprState *exprstate, ExprContext *econtext,
+                            bool *isNull, ExprDoneCond *isDone)
+{
+   GroupedVar *variable = (GroupedVar *) exprstate->expr;
+   TupleTableSlot *slot;
+   AttrNumber  attnum;
+
+   if (isDone)
+       *isDone = ExprSingleResult;
+
+   slot = econtext->ecxt_outertuple;
+
+   attnum = variable->varattno;
+
+   if (!bms_is_member(attnum, econtext->grouped_cols))
+   {
+       *isNull = true;
+       return (Datum) 0;
+   }
+
+   /* Fetch the value from the slot */
+   return slot_getattr(slot, attnum, isNull);
+}
+
 /* ----------------------------------------------------------------
  *     ExecEvalWholeRowVar
  *
@@ -3016,6 +3064,44 @@ ExecEvalCaseTestExpr(ExprState *exprstate,
    return econtext->caseValue_datum;
 }
 
+/*
+ * ExecEvalGroupingFuncExpr
+ *
+ * Return a bitmask with a bit for each (unevaluated) argument expression
+ * (rightmost arg is least significant bit).
+ *
+ * A bit is set if the corresponding expression is NOT part of the set of
+ * grouping expressions in the current grouping set.
+ */
+
+static Datum
+ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
+                        ExprContext *econtext,
+                        bool *isNull,
+                        ExprDoneCond *isDone)
+{
+   int result = 0;
+   int attnum = 0;
+   ListCell *lc;
+
+   if (isDone)
+       *isDone = ExprSingleResult;
+
+   *isNull = false;
+
+   foreach(lc, (gstate->clauses))
+   {
+       attnum = lfirst_int(lc);
+
+       result = result << 1;
+
+       if (!bms_is_member(attnum, econtext->grouped_cols))
+           result = result | 1;
+   }
+
+   return (Datum) result;
+}
+
 /* ----------------------------------------------------------------
  *     ExecEvalArray - ARRAY[] expressions
  * ----------------------------------------------------------------
@@ -4418,6 +4504,11 @@ ExecInitExpr(Expr *node, PlanState *parent)
                state->evalfunc = ExecEvalScalarVar;
            }
            break;
+       case T_GroupedVar:
+           Assert(((Var *) node)->varattno != InvalidAttrNumber);
+           state = (ExprState *) makeNode(ExprState);
+           state->evalfunc = ExecEvalScalarVar;
+           break;
        case T_Const:
            state = (ExprState *) makeNode(ExprState);
            state->evalfunc = ExecEvalConst;
@@ -4486,6 +4577,27 @@ ExecInitExpr(Expr *node, PlanState *parent)
                state = (ExprState *) astate;
            }
            break;
+       case T_GroupingFunc:
+           {
+               GroupingFunc *grp_node = (GroupingFunc *) node;
+               GroupingFuncExprState *grp_state = makeNode(GroupingFuncExprState);
+               Agg        *agg = NULL;
+
+               if (!parent
+                   || !IsA(parent->plan, Agg))
+                   elog(ERROR, "Parent of GROUPING is not Agg node");
+
+               agg = (Agg *) (parent->plan);
+
+               if (agg->groupingSets)
+                   grp_state->clauses = grp_node->cols;
+               else
+                   grp_state->clauses = NIL;
+
+               state = (ExprState *) grp_state;
+               state->evalfunc = (ExprStateEvalFunc) ExecEvalGroupingFuncExpr;
+           }
+           break;
        case T_WindowFunc:
            {
                WindowFunc *wfunc = (WindowFunc *) node;
index 88ba16bc6dabbf5c2fb5877e74bf2e7b0f9c1338..4d23e8fcd8a61bac376034b15e46acfbd131b207 100644 (file)
@@ -142,6 +142,7 @@ CreateExecutorState(void)
    estate->es_epqTupleSet = NULL;
    estate->es_epqScanDone = NULL;
 
+   estate->agg_chain_head = NULL;
    /*
     * Return the executor state structure
     */
@@ -642,9 +643,10 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
    /*
     * Don't examine the arguments or filters of Aggrefs or WindowFuncs,
     * because those do not represent expressions to be evaluated within the
-    * overall targetlist's econtext.
+    * overall targetlist's econtext.  GroupingFunc arguments are never
+    * evaluated at all.
     */
-   if (IsA(node, Aggref))
+   if (IsA(node, Aggref) || IsA(node, GroupingFunc))
        return false;
    if (IsA(node, WindowFunc))
        return false;
index 4a8af7b3a7e0ab82ebb3d11efeeb84c0fd66210f..5c86c1f74684d4952b56f129dee49c7445f8ccc6 100644 (file)
  *   needed to allow resolution of a polymorphic aggregate's result type.
  *
  *   We compute aggregate input expressions and run the transition functions
- *   in a temporary econtext (aggstate->tmpcontext).  This is reset at
- *   least once per input tuple, so when the transvalue datatype is
+ *   in a temporary econtext (aggstate->tmpcontext).  This is reset at least
+ *   once per input tuple, so when the transvalue datatype is
  *   pass-by-reference, we have to be careful to copy it into a longer-lived
- *   memory context, and free the prior value to avoid memory leakage.
- *   We store transvalues in the memory context aggstate->aggcontext,
- *   which is also used for the hashtable structures in AGG_HASHED mode.
- *   The node's regular econtext (aggstate->ss.ps.ps_ExprContext)
- *   is used to run finalize functions and compute the output tuple;
- *   this context can be reset once per output tuple.
+ *   memory context, and free the prior value to avoid memory leakage.  We
+ *   store transvalues in another set of econtexts, aggstate->aggcontexts (one
+ *   per grouping set, see below), which are also used for the hashtable
+ *   structures in AGG_HASHED mode.  These econtexts are rescanned, not just
+ *   reset, at group boundaries so that aggregate transition functions can
+ *   register shutdown callbacks via AggRegisterCallback.
+ *
+ *   The node's regular econtext (aggstate->ss.ps.ps_ExprContext) is used to
+ *   run finalize functions and compute the output tuple; this context can be
+ *   reset once per output tuple.
  *
  *   The executor's AggState node is passed as the fmgr "context" value in
  *   all transfunc and finalfunc calls.  It is not recommended that the
  *   need some fallback logic to use this, since there's no Aggref node
  *   for a window function.)
  *
+ *   Grouping sets:
+ *
+ *   A list of grouping sets which is structurally equivalent to a ROLLUP
+ *   clause (e.g. (a,b,c), (a,b), (a)) can be processed in a single pass over
+ *   ordered data.  We do this by keeping a separate set of transition values
+ *   for each grouping set being concurrently processed; for each input tuple
+ *   we update them all, and on group boundaries we reset some initial subset
+ *   of the states (the list of grouping sets is ordered from most specific to
+ *   least specific).  One AGG_SORTED node thus handles any number of grouping
+ *   sets as long as they share a sort order.
+ *
+ *   To handle multiple grouping sets that _don't_ share a sort order, we use
+ *   a different strategy.  An AGG_CHAINED node receives rows in sorted order
+ *   and returns them unchanged, but computes transition values for its own
+ *   list of grouping sets.  At group boundaries, rather than returning the
+ *   aggregated row (which is incompatible with the input rows), it writes it
+ *   to a side-channel in the form of a tuplestore.  Thus, a number of
+ *   AGG_CHAINED nodes are associated with a single AGG_SORTED node (the
+ *   "chain head"), which creates the side channel and, when it has returned
+ *   all of its own data, returns the tuples from the tuplestore to its own
+ *   caller.
+ *
+ *   (Because the AGG_CHAINED node does not project aggregate values into the
+ *   main executor path, its targetlist and qual are dummy, and it gets the
+ *   real aggregate targetlist and qual from the chain head node.)
+ *
+ *   In order to avoid excess memory consumption from a chain of alternating
+ *   Sort and AGG_CHAINED nodes, we reset each child Sort node preemptively,
+ *   allowing us to cap the memory usage for all the sorts in the chain at
+ *   twice the usage for a single node.
+ *
+ *   From the perspective of aggregate transition and final functions, the
+ *   only issue regarding grouping sets is this: a single call site (flinfo)
+ *   of an aggregate function may be used for updating several different
+ *   transition values in turn. So the function must not cache in the flinfo
+ *   anything which logically belongs as part of the transition value (most
+ *   importantly, the memory context in which the transition value exists).
+ *   The support API functions (AggCheckCallContext, AggRegisterCallback) are
+ *   sensitive to the grouping set for which the aggregate function is
+ *   currently being called.
+ *
+ *   TODO: AGG_HASHED doesn't support multiple grouping sets yet.
  *
  * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -241,9 +287,11 @@ typedef struct AggStatePerAggData
     * then at completion of the input tuple group, we scan the sorted values,
     * eliminate duplicates if needed, and run the transition function on the
     * rest.
+    *
+    * We need a separate tuplesort for each grouping set.
     */
 
-   Tuplesortstate *sortstate;  /* sort object, if DISTINCT or ORDER BY */
+   Tuplesortstate **sortstates;    /* sort objects, if DISTINCT or ORDER BY */
 
    /*
     * This field is a pre-initialized FunctionCallInfo struct used for
@@ -304,7 +352,8 @@ typedef struct AggHashEntryData
 
 static void initialize_aggregates(AggState *aggstate,
                      AggStatePerAgg peragg,
-                     AggStatePerGroup pergroup);
+                     AggStatePerGroup pergroup,
+                     int numReset);
 static void advance_transition_function(AggState *aggstate,
                            AggStatePerAgg peraggstate,
                            AggStatePerGroup pergroupstate);
@@ -325,6 +374,7 @@ static void build_hash_table(AggState *aggstate);
 static AggHashEntry lookup_hash_entry(AggState *aggstate,
                  TupleTableSlot *inputslot);
 static TupleTableSlot *agg_retrieve_direct(AggState *aggstate);
+static TupleTableSlot *agg_retrieve_chained(AggState *aggstate);
 static void agg_fill_hash_table(AggState *aggstate);
 static TupleTableSlot *agg_retrieve_hash_table(AggState *aggstate);
 static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
@@ -333,90 +383,109 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 /*
  * Initialize all aggregates for a new group of input values.
  *
+ * If there are multiple grouping sets, we initialize only the first numReset
+ * of them (the grouping sets are ordered so that the most specific one, which
+ * is reset most often, is first). As a convenience, if numReset is < 1, we
+ * reinitialize all sets.
+ *
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
 initialize_aggregates(AggState *aggstate,
                      AggStatePerAgg peragg,
-                     AggStatePerGroup pergroup)
+                     AggStatePerGroup pergroup,
+                     int numReset)
 {
    int         aggno;
+   int         numGroupingSets = Max(aggstate->numsets, 1);
+   int         setno = 0;
+
+   if (numReset < 1)
+       numReset = numGroupingSets;
 
    for (aggno = 0; aggno < aggstate->numaggs; aggno++)
    {
        AggStatePerAgg peraggstate = &peragg[aggno];
-       AggStatePerGroup pergroupstate = &pergroup[aggno];
 
        /*
         * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
         */
        if (peraggstate->numSortCols > 0)
        {
-           /*
-            * In case of rescan, maybe there could be an uncompleted sort
-            * operation?  Clean it up if so.
-            */
-           if (peraggstate->sortstate)
-               tuplesort_end(peraggstate->sortstate);
+           for (setno = 0; setno < numReset; setno++)
+           {
+               /*
+                * In case of rescan, maybe there could be an uncompleted sort
+                * operation?  Clean it up if so.
+                */
+               if (peraggstate->sortstates[setno])
+                   tuplesort_end(peraggstate->sortstates[setno]);
+
+               /*
+                * We use a plain Datum sorter when there's a single input column;
+                * otherwise sort the full tuple.  (See comments for
+                * process_ordered_aggregate_single.)
+                *
+                * In the future, we should consider forcing the
+                * tuplesort_begin_heap() case when the abbreviated key
+                * optimization can thereby be used, even when numInputs is 1.
+                */
+               peraggstate->sortstates[setno] =
+                   (peraggstate->numInputs == 1) ?
+                   tuplesort_begin_datum(peraggstate->evaldesc->attrs[0]->atttypid,
+                                         peraggstate->sortOperators[0],
+                                         peraggstate->sortCollations[0],
+                                         peraggstate->sortNullsFirst[0],
+                                         work_mem, false) :
+                   tuplesort_begin_heap(peraggstate->evaldesc,
+                                        peraggstate->numSortCols,
+                                        peraggstate->sortColIdx,
+                                        peraggstate->sortOperators,
+                                        peraggstate->sortCollations,
+                                        peraggstate->sortNullsFirst,
+                                        work_mem, false);
+           }
+       }
+
+       for (setno = 0; setno < numReset; setno++)
+       {
+           AggStatePerGroup pergroupstate = &pergroup[aggno + (setno * (aggstate->numaggs))];
 
            /*
-            * We use a plain Datum sorter when there's a single input column;
-            * otherwise sort the full tuple.  (See comments for
-            * process_ordered_aggregate_single.)
+            * (Re)set transValue to the initial value.
             *
-            * In the future, we should consider forcing the
-            * tuplesort_begin_heap() case when the abbreviated key
-            * optimization can thereby be used, even when numInputs is 1.
+            * Note that when the initial value is pass-by-ref, we must copy it
+            * (into the aggcontext) since we will pfree the transValue later.
             */
-           peraggstate->sortstate =
-               (peraggstate->numInputs == 1) ?
-               tuplesort_begin_datum(peraggstate->evaldesc->attrs[0]->atttypid,
-                                     peraggstate->sortOperators[0],
-                                     peraggstate->sortCollations[0],
-                                     peraggstate->sortNullsFirst[0],
-                                     work_mem, false) :
-               tuplesort_begin_heap(peraggstate->evaldesc,
-                                    peraggstate->numSortCols,
-                                    peraggstate->sortColIdx,
-                                    peraggstate->sortOperators,
-                                    peraggstate->sortCollations,
-                                    peraggstate->sortNullsFirst,
-                                    work_mem, false);
-       }
+           if (peraggstate->initValueIsNull)
+               pergroupstate->transValue = peraggstate->initValue;
+           else
+           {
+               MemoryContext oldContext;
 
-       /*
-        * (Re)set transValue to the initial value.
-        *
-        * Note that when the initial value is pass-by-ref, we must copy it
-        * (into the aggcontext) since we will pfree the transValue later.
-        */
-       if (peraggstate->initValueIsNull)
-           pergroupstate->transValue = peraggstate->initValue;
-       else
-       {
-           MemoryContext oldContext;
+               oldContext = MemoryContextSwitchTo(aggstate->aggcontexts[setno]->ecxt_per_tuple_memory);
+               pergroupstate->transValue = datumCopy(peraggstate->initValue,
+                                                     peraggstate->transtypeByVal,
+                                                     peraggstate->transtypeLen);
+               MemoryContextSwitchTo(oldContext);
+           }
+           pergroupstate->transValueIsNull = peraggstate->initValueIsNull;
 
-           oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
-           pergroupstate->transValue = datumCopy(peraggstate->initValue,
-                                                 peraggstate->transtypeByVal,
-                                                 peraggstate->transtypeLen);
-           MemoryContextSwitchTo(oldContext);
+           /*
+            * If the initial value for the transition state doesn't exist in the
+            * pg_aggregate table then we will let the first non-NULL value
+            * returned from the outer procNode become the initial value. (This is
+            * useful for aggregates like max() and min().) The noTransValue flag
+            * signals that we still need to do this.
+            */
+           pergroupstate->noTransValue = peraggstate->initValueIsNull;
        }
-       pergroupstate->transValueIsNull = peraggstate->initValueIsNull;
-
-       /*
-        * If the initial value for the transition state doesn't exist in the
-        * pg_aggregate table then we will let the first non-NULL value
-        * returned from the outer procNode become the initial value. (This is
-        * useful for aggregates like max() and min().) The noTransValue flag
-        * signals that we still need to do this.
-        */
-       pergroupstate->noTransValue = peraggstate->initValueIsNull;
    }
 }
 
 /*
- * Given new input value(s), advance the transition function of an aggregate.
+ * Given new input value(s), advance the transition function of one aggregate
+ * within one grouping set only (already set in aggstate->current_set)
  *
  * The new values (and null flags) have been preloaded into argument positions
  * 1 and up in peraggstate->transfn_fcinfo, so that we needn't copy them again
@@ -459,7 +528,7 @@ advance_transition_function(AggState *aggstate,
             * We must copy the datum into aggcontext if it is pass-by-ref. We
             * do not need to pfree the old transValue, since it's NULL.
             */
-           oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
+           oldContext = MemoryContextSwitchTo(aggstate->aggcontexts[aggstate->current_set]->ecxt_per_tuple_memory);
            pergroupstate->transValue = datumCopy(fcinfo->arg[1],
                                                  peraggstate->transtypeByVal,
                                                  peraggstate->transtypeLen);
@@ -507,7 +576,7 @@ advance_transition_function(AggState *aggstate,
    {
        if (!fcinfo->isnull)
        {
-           MemoryContextSwitchTo(aggstate->aggcontext);
+           MemoryContextSwitchTo(aggstate->aggcontexts[aggstate->current_set]->ecxt_per_tuple_memory);
            newVal = datumCopy(newVal,
                               peraggstate->transtypeByVal,
                               peraggstate->transtypeLen);
@@ -534,11 +603,13 @@ static void
 advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 {
    int         aggno;
+   int         setno = 0;
+   int         numGroupingSets = Max(aggstate->numsets, 1);
+   int         numAggs = aggstate->numaggs;
 
-   for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+   for (aggno = 0; aggno < numAggs; aggno++)
    {
        AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
-       AggStatePerGroup pergroupstate = &pergroup[aggno];
        ExprState  *filter = peraggstate->aggrefstate->aggfilter;
        int         numTransInputs = peraggstate->numTransInputs;
        int         i;
@@ -582,13 +653,16 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
                    continue;
            }
 
-           /* OK, put the tuple into the tuplesort object */
-           if (peraggstate->numInputs == 1)
-               tuplesort_putdatum(peraggstate->sortstate,
-                                  slot->tts_values[0],
-                                  slot->tts_isnull[0]);
-           else
-               tuplesort_puttupleslot(peraggstate->sortstate, slot);
+           for (setno = 0; setno < numGroupingSets; setno++)
+           {
+               /* OK, put the tuple into the tuplesort object */
+               if (peraggstate->numInputs == 1)
+                   tuplesort_putdatum(peraggstate->sortstates[setno],
+                                      slot->tts_values[0],
+                                      slot->tts_isnull[0]);
+               else
+                   tuplesort_puttupleslot(peraggstate->sortstates[setno], slot);
+           }
        }
        else
        {
@@ -604,7 +678,14 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
                fcinfo->argnull[i + 1] = slot->tts_isnull[i];
            }
 
-           advance_transition_function(aggstate, peraggstate, pergroupstate);
+           for (setno = 0; setno < numGroupingSets; setno++)
+           {
+               AggStatePerGroup pergroupstate = &pergroup[aggno + (setno * numAggs)];
+
+               aggstate->current_set = setno;
+
+               advance_transition_function(aggstate, peraggstate, pergroupstate);
+           }
        }
    }
 }
@@ -627,6 +708,9 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
  * is around 300% faster.  (The speedup for by-reference types is less
  * but still noticeable.)
  *
+ * This function handles only one grouping set (already set in
+ * aggstate->current_set).
+ *
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
@@ -646,7 +730,7 @@ process_ordered_aggregate_single(AggState *aggstate,
 
    Assert(peraggstate->numDistinctCols < 2);
 
-   tuplesort_performsort(peraggstate->sortstate);
+   tuplesort_performsort(peraggstate->sortstates[aggstate->current_set]);
 
    /* Load the column into argument 1 (arg 0 will be transition value) */
    newVal = fcinfo->arg + 1;
@@ -658,7 +742,7 @@ process_ordered_aggregate_single(AggState *aggstate,
     * pfree them when they are no longer needed.
     */
 
-   while (tuplesort_getdatum(peraggstate->sortstate, true,
+   while (tuplesort_getdatum(peraggstate->sortstates[aggstate->current_set], true,
                              newVal, isNull))
    {
        /*
@@ -702,8 +786,8 @@ process_ordered_aggregate_single(AggState *aggstate,
    if (!oldIsNull && !peraggstate->inputtypeByVal)
        pfree(DatumGetPointer(oldVal));
 
-   tuplesort_end(peraggstate->sortstate);
-   peraggstate->sortstate = NULL;
+   tuplesort_end(peraggstate->sortstates[aggstate->current_set]);
+   peraggstate->sortstates[aggstate->current_set] = NULL;
 }
 
 /*
@@ -713,6 +797,9 @@ process_ordered_aggregate_single(AggState *aggstate,
  * sort, read out the values in sorted order, and run the transition
  * function on each value (applying DISTINCT if appropriate).
  *
+ * This function handles only one grouping set (already set in
+ * aggstate->current_set).
+ *
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
@@ -729,13 +816,13 @@ process_ordered_aggregate_multi(AggState *aggstate,
    bool        haveOldValue = false;
    int         i;
 
-   tuplesort_performsort(peraggstate->sortstate);
+   tuplesort_performsort(peraggstate->sortstates[aggstate->current_set]);
 
    ExecClearTuple(slot1);
    if (slot2)
        ExecClearTuple(slot2);
 
-   while (tuplesort_gettupleslot(peraggstate->sortstate, true, slot1))
+   while (tuplesort_gettupleslot(peraggstate->sortstates[aggstate->current_set], true, slot1))
    {
        /*
         * Extract the first numTransInputs columns as datums to pass to the
@@ -783,13 +870,16 @@ process_ordered_aggregate_multi(AggState *aggstate,
    if (slot2)
        ExecClearTuple(slot2);
 
-   tuplesort_end(peraggstate->sortstate);
-   peraggstate->sortstate = NULL;
+   tuplesort_end(peraggstate->sortstates[aggstate->current_set]);
+   peraggstate->sortstates[aggstate->current_set] = NULL;
 }
 
 /*
  * Compute the final value of one aggregate.
  *
+ * This function handles only one grouping set (already set in
+ * aggstate->current_set).
+ *
  * The finalfunction will be run, and the result delivered, in the
  * output-tuple context; caller's CurrentMemoryContext does not matter.
  */
@@ -836,7 +926,7 @@ finalize_aggregate(AggState *aggstate,
        /* set up aggstate->curperagg for AggGetAggref() */
        aggstate->curperagg = peraggstate;
 
-       InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn),
+       InitFunctionCallInfoData(fcinfo, &peraggstate->finalfn,
                                 numFinalArgs,
                                 peraggstate->aggCollation,
                                 (void *) aggstate, NULL);
@@ -920,7 +1010,8 @@ find_unaggregated_cols_walker(Node *node, Bitmapset **colnos)
        *colnos = bms_add_member(*colnos, var->varattno);
        return false;
    }
-   if (IsA(node, Aggref))      /* do not descend into aggregate exprs */
+   if (IsA(node, Aggref) || IsA(node, GroupingFunc))
+       /* do not descend into aggregate exprs */
        return false;
    return expression_tree_walker(node, find_unaggregated_cols_walker,
                                  (void *) colnos);
@@ -950,7 +1041,7 @@ build_hash_table(AggState *aggstate)
                                              aggstate->hashfunctions,
                                              node->numGroups,
                                              entrysize,
-                                             aggstate->aggcontext,
+                                             aggstate->aggcontexts[0]->ecxt_per_tuple_memory,
                                              tmpmem);
 }
 
@@ -1061,7 +1152,7 @@ lookup_hash_entry(AggState *aggstate, TupleTableSlot *inputslot)
    if (isnew)
    {
        /* initialize aggregates for new tuple group */
-       initialize_aggregates(aggstate, aggstate->peragg, entry->pergroup);
+       initialize_aggregates(aggstate, aggstate->peragg, entry->pergroup, 0);
    }
 
    return entry;
@@ -1083,6 +1174,8 @@ lookup_hash_entry(AggState *aggstate, TupleTableSlot *inputslot)
 TupleTableSlot *
 ExecAgg(AggState *node)
 {
+   TupleTableSlot *result;
+
    /*
     * Check to see if we're still projecting out tuples from a previous agg
     * tuple (because there is a function-returning-set in the projection
@@ -1090,7 +1183,6 @@ ExecAgg(AggState *node)
     */
    if (node->ss.ps.ps_TupFromTlist)
    {
-       TupleTableSlot *result;
        ExprDoneCond isDone;
 
        result = ExecProject(node->ss.ps.ps_ProjInfo, &isDone);
@@ -1101,22 +1193,48 @@ ExecAgg(AggState *node)
    }
 
    /*
-    * Exit if nothing left to do.  (We must do the ps_TupFromTlist check
-    * first, because in some cases agg_done gets set before we emit the final
-    * aggregate tuple, and we have to finish running SRFs for it.)
+    * (We must do the ps_TupFromTlist check first, because in some cases
+    * agg_done gets set before we emit the final aggregate tuple, and we have
+    * to finish running SRFs for it.)
     */
-   if (node->agg_done)
-       return NULL;
+   if (!node->agg_done)
+   {
+       /* Dispatch based on strategy */
+       switch (((Agg *) node->ss.ps.plan)->aggstrategy)
+       {
+           case AGG_HASHED:
+               if (!node->table_filled)
+                   agg_fill_hash_table(node);
+               result = agg_retrieve_hash_table(node);
+               break;
+           case AGG_CHAINED:
+               result = agg_retrieve_chained(node);
+               break;
+           default:
+               result = agg_retrieve_direct(node);
+               break;
+       }
 
-   /* Dispatch based on strategy */
-   if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+       if (!TupIsNull(result))
+           return result;
+   }
+
+   /*
+    * We've completed all locally computed projections, now we drain the side
+    * channel of projections from chained nodes if any.
+    */
+   if (!node->chain_done)
    {
-       if (!node->table_filled)
-           agg_fill_hash_table(node);
-       return agg_retrieve_hash_table(node);
+       Assert(node->chain_tuplestore);
+       result = node->ss.ps.ps_ResultTupleSlot;
+       ExecClearTuple(result);
+       if (tuplestore_gettupleslot(node->chain_tuplestore,
+                                   true, false, result))
+           return result;
+       node->chain_done = true;
    }
-   else
-       return agg_retrieve_direct(node);
+
+   return NULL;
 }
 
 /*
@@ -1136,6 +1254,12 @@ agg_retrieve_direct(AggState *aggstate)
    TupleTableSlot *outerslot;
    TupleTableSlot *firstSlot;
    int         aggno;
+   bool        hasGroupingSets = aggstate->numsets > 0;
+   int         numGroupingSets = Max(aggstate->numsets, 1);
+   int         currentSet = 0;
+   int         nextSetSize = 0;
+   int         numReset = 1;
+   int         i;
 
    /*
     * get state info from node
@@ -1154,39 +1278,20 @@ agg_retrieve_direct(AggState *aggstate)
    /*
     * We loop retrieving groups until we find one matching
     * aggstate->ss.ps.qual
+    *
+    * For grouping sets, we have the invariant that aggstate->projected_set is
+    * either -1 (initial call) or the index (starting from 0) in gset_lengths
+    * for the group we just completed (either by projecting a row or by
+    * discarding it in the qual).
     */
    while (!aggstate->agg_done)
    {
-       /*
-        * If we don't already have the first tuple of the new group, fetch it
-        * from the outer plan.
-        */
-       if (aggstate->grp_firstTuple == NULL)
-       {
-           outerslot = ExecProcNode(outerPlan);
-           if (!TupIsNull(outerslot))
-           {
-               /*
-                * Make a copy of the first input tuple; we will use this for
-                * comparisons (in group mode) and for projection.
-                */
-               aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
-           }
-           else
-           {
-               /* outer plan produced no tuples at all */
-               aggstate->agg_done = true;
-               /* If we are grouping, we should produce no tuples too */
-               if (node->aggstrategy != AGG_PLAIN)
-                   return NULL;
-           }
-       }
-
        /*
         * Clear the per-output-tuple context for each group, as well as
         * aggcontext (which contains any pass-by-ref transvalues of the old
-        * group).  We also clear any child contexts of the aggcontext; some
-        * aggregate functions store working state in such contexts.
+        * group).  Some aggregate functions store working state in child
+        * contexts; those now get reset automatically without us needing to
+        * do anything special.
         *
         * We use ReScanExprContext not just ResetExprContext because we want
         * any registered shutdown callbacks to be called.  That allows
@@ -1195,90 +1300,222 @@ agg_retrieve_direct(AggState *aggstate)
         */
        ReScanExprContext(econtext);
 
-       MemoryContextResetAndDeleteChildren(aggstate->aggcontext);
+       /*
+        * Determine how many grouping sets need to be reset at this boundary.
+        */
+       if (aggstate->projected_set >= 0 && aggstate->projected_set < numGroupingSets)
+           numReset = aggstate->projected_set + 1;
+       else
+           numReset = numGroupingSets;
+
+       for (i = 0; i < numReset; i++)
+       {
+           ReScanExprContext(aggstate->aggcontexts[i]);
+       }
+
+       /* Check if input is complete and there are no more groups to project. */
+       if (aggstate->input_done == true
+           && aggstate->projected_set >= (numGroupingSets - 1))
+       {
+           aggstate->agg_done = true;
+           break;
+       }
 
        /*
-        * Initialize working state for a new input tuple group
+        * Get the number of columns in the next grouping set after the last
+        * projected one (if any). This is the number of columns to compare to
+        * see if we reached the boundary of that set too.
+        */
+       if (aggstate->projected_set >= 0 && aggstate->projected_set < (numGroupingSets - 1))
+           nextSetSize = aggstate->gset_lengths[aggstate->projected_set + 1];
+       else
+           nextSetSize = 0;
+
+       /*-
+        * If a subgroup for the current grouping set is present, project it.
+        *
+        * We have a new group if:
+        *  - we're out of input but haven't projected all grouping sets
+        *    (checked above)
+        * OR
+        *    - we already projected a row that wasn't from the last grouping
+        *      set
+        *    AND
+        *    - the next grouping set has at least one grouping column (since
+        *      empty grouping sets project only once input is exhausted)
+        *    AND
+        *    - the previous and pending rows differ on the grouping columns
+        *      of the next grouping set
         */
-       initialize_aggregates(aggstate, peragg, pergroup);
+       if (aggstate->input_done
+           || (node->aggstrategy == AGG_SORTED
+               && aggstate->projected_set != -1
+               && aggstate->projected_set < (numGroupingSets - 1)
+               && nextSetSize > 0
+               && !execTuplesMatch(econtext->ecxt_outertuple,
+                                   tmpcontext->ecxt_outertuple,
+                                   nextSetSize,
+                                   node->grpColIdx,
+                                   aggstate->eqfunctions,
+                                   tmpcontext->ecxt_per_tuple_memory)))
+       {
+           aggstate->projected_set += 1;
 
-       if (aggstate->grp_firstTuple != NULL)
+           Assert(aggstate->projected_set < numGroupingSets);
+           Assert(nextSetSize > 0 || aggstate->input_done);
+       }
+       else
        {
            /*
-            * Store the copied first input tuple in the tuple table slot
-            * reserved for it.  The tuple will be deleted when it is cleared
-            * from the slot.
+            * We no longer care what group we just projected, the next
+            * projection will always be the first (or only) grouping set
+            * (unless the input proves to be empty).
             */
-           ExecStoreTuple(aggstate->grp_firstTuple,
-                          firstSlot,
-                          InvalidBuffer,
-                          true);
-           aggstate->grp_firstTuple = NULL;    /* don't keep two pointers */
-
-           /* set up for first advance_aggregates call */
-           tmpcontext->ecxt_outertuple = firstSlot;
+           aggstate->projected_set = 0;
 
            /*
-            * Process each outer-plan tuple, and then fetch the next one,
-            * until we exhaust the outer plan or cross a group boundary.
+            * If we don't already have the first tuple of the new group, fetch
+            * it from the outer plan.
             */
-           for (;;)
+           if (aggstate->grp_firstTuple == NULL)
            {
-               advance_aggregates(aggstate, pergroup);
-
-               /* Reset per-input-tuple context after each tuple */
-               ResetExprContext(tmpcontext);
-
                outerslot = ExecProcNode(outerPlan);
-               if (TupIsNull(outerslot))
+               if (!TupIsNull(outerslot))
+               {
+                   /*
+                    * Make a copy of the first input tuple; we will use this for
+                    * comparisons (in group mode) and for projection.
+                    */
+                   aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
+               }
+               else
                {
-                   /* no more outer-plan tuples available */
-                   aggstate->agg_done = true;
-                   break;
+                   /* outer plan produced no tuples at all */
+                   if (hasGroupingSets)
+                   {
+                       /*
+                        * If there was no input at all, we need to project
+                        * rows only if there are grouping sets of size 0.
+                        * Note that this implies that there can't be any
+                        * references to ungrouped Vars, which would otherwise
+                        * cause issues with the empty output slot.
+                        */
+                       aggstate->input_done = true;
+
+                       while (aggstate->gset_lengths[aggstate->projected_set] > 0)
+                       {
+                           aggstate->projected_set += 1;
+                           if (aggstate->projected_set >= numGroupingSets)
+                           {
+                               aggstate->agg_done = true;
+                               return NULL;
+                           }
+                       }
+                   }
+                   else
+                   {
+                       aggstate->agg_done = true;
+                       /* If we are grouping, we should produce no tuples too */
+                       if (node->aggstrategy != AGG_PLAIN)
+                           return NULL;
+                   }
                }
-               /* set up for next advance_aggregates call */
-               tmpcontext->ecxt_outertuple = outerslot;
+           }
+
+           /*
+            * Initialize working state for a new input tuple group.
+            */
+           initialize_aggregates(aggstate, peragg, pergroup, numReset);
 
+           if (aggstate->grp_firstTuple != NULL)
+           {
                /*
-                * If we are grouping, check whether we've crossed a group
-                * boundary.
+                * Store the copied first input tuple in the tuple table slot
+                * reserved for it.  The tuple will be deleted when it is cleared
+                * from the slot.
                 */
-               if (node->aggstrategy == AGG_SORTED)
+               ExecStoreTuple(aggstate->grp_firstTuple,
+                              firstSlot,
+                              InvalidBuffer,
+                              true);
+               aggstate->grp_firstTuple = NULL;    /* don't keep two pointers */
+
+               /* set up for first advance_aggregates call */
+               tmpcontext->ecxt_outertuple = firstSlot;
+
+               /*
+                * Process each outer-plan tuple, and then fetch the next one,
+                * until we exhaust the outer plan or cross a group boundary.
+                */
+               for (;;)
                {
-                   if (!execTuplesMatch(firstSlot,
-                                        outerslot,
-                                        node->numCols, node->grpColIdx,
-                                        aggstate->eqfunctions,
-                                        tmpcontext->ecxt_per_tuple_memory))
+                   advance_aggregates(aggstate, pergroup);
+
+                   /* Reset per-input-tuple context after each tuple */
+                   ResetExprContext(tmpcontext);
+
+                   outerslot = ExecProcNode(outerPlan);
+                   if (TupIsNull(outerslot))
                    {
-                       /*
-                        * Save the first input tuple of the next group.
-                        */
-                       aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
-                       break;
+                       /* no more outer-plan tuples available */
+                       if (hasGroupingSets)
+                       {
+                           aggstate->input_done = true;
+                           break;
+                       }
+                       else
+                       {
+                           aggstate->agg_done = true;
+                           break;
+                       }
+                   }
+                   /* set up for next advance_aggregates call */
+                   tmpcontext->ecxt_outertuple = outerslot;
+
+                   /*
+                    * If we are grouping, check whether we've crossed a group
+                    * boundary.
+                    */
+                   if (node->aggstrategy == AGG_SORTED)
+                   {
+                       if (!execTuplesMatch(firstSlot,
+                                            outerslot,
+                                            node->numCols,
+                                            node->grpColIdx,
+                                            aggstate->eqfunctions,
+                                            tmpcontext->ecxt_per_tuple_memory))
+                       {
+                           aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
+                           break;
+                       }
                    }
                }
            }
+
+           /*
+            * Use the representative input tuple for any references to
+            * non-aggregated input columns in aggregate direct args, the node
+            * qual, and the tlist.  (If we are not grouping, and there are no
+            * input rows at all, we will come here with an empty firstSlot ...
+            * but if not grouping, there can't be any references to
+            * non-aggregated input columns, so no problem.)
+            */
+           econtext->ecxt_outertuple = firstSlot;
        }
 
-       /*
-        * Use the representative input tuple for any references to
-        * non-aggregated input columns in aggregate direct args, the node
-        * qual, and the tlist.  (If we are not grouping, and there are no
-        * input rows at all, we will come here with an empty firstSlot ...
-        * but if not grouping, there can't be any references to
-        * non-aggregated input columns, so no problem.)
-        */
-       econtext->ecxt_outertuple = firstSlot;
+       Assert(aggstate->projected_set >= 0);
+
+       aggstate->current_set = currentSet = aggstate->projected_set;
+
+       if (hasGroupingSets)
+           econtext->grouped_cols = aggstate->grouped_cols[currentSet];
 
-       /*
-        * Done scanning input tuple group. Finalize each aggregate
-        * calculation, and stash results in the per-output-tuple context.
-        */
        for (aggno = 0; aggno < aggstate->numaggs; aggno++)
        {
            AggStatePerAgg peraggstate = &peragg[aggno];
-           AggStatePerGroup pergroupstate = &pergroup[aggno];
+           AggStatePerGroup pergroupstate;
+
+           pergroupstate = &pergroup[aggno + (currentSet * (aggstate->numaggs))];
 
            if (peraggstate->numSortCols > 0)
            {
@@ -1326,6 +1563,174 @@ agg_retrieve_direct(AggState *aggstate)
    return NULL;
 }
 
+
+/*
+ * ExecAgg for chained case (pullthrough mode)
+ */
+static TupleTableSlot *
+agg_retrieve_chained(AggState *aggstate)
+{
+   Agg        *node = (Agg *) aggstate->ss.ps.plan;
+   ExprContext *econtext = aggstate->ss.ps.ps_ExprContext;
+   ExprContext *tmpcontext = aggstate->tmpcontext;
+   Datum      *aggvalues = econtext->ecxt_aggvalues;
+   bool       *aggnulls = econtext->ecxt_aggnulls;
+   AggStatePerAgg peragg = aggstate->peragg;
+   AggStatePerGroup pergroup = aggstate->pergroup;
+   TupleTableSlot *outerslot;
+   TupleTableSlot *firstSlot = aggstate->ss.ss_ScanTupleSlot;
+   int            aggno;
+   int            numGroupingSets = Max(aggstate->numsets, 1);
+   int            currentSet = 0;
+
+   /*
+    * The invariants here are:
+    *
+    *  - when called, we've already projected every result that might have
+    * been generated by previous rows, and if this is not the first row, then
+    * grp_firsttuple has the representative input row.
+    *
+    *  - we must pull the outer plan exactly once and return that tuple. If
+    * the outer plan ends, we project whatever needs projecting.
+    */
+
+   outerslot = ExecProcNode(outerPlanState(aggstate));
+
+   /*
+    * If this is the first row and it's empty, nothing to do.
+    */
+
+   if (TupIsNull(firstSlot) && TupIsNull(outerslot))
+   {
+       aggstate->agg_done = true;
+       return outerslot;
+   }
+
+   /*
+    * See if we need to project anything. (We don't need to worry about
+    * grouping sets of size 0, the planner doesn't give us those.)
+    */
+
+   econtext->ecxt_outertuple = firstSlot;
+
+   while (!TupIsNull(firstSlot)
+          && (TupIsNull(outerslot)
+              || !execTuplesMatch(firstSlot,
+                                  outerslot,
+                                  aggstate->gset_lengths[currentSet],
+                                  node->grpColIdx,
+                                  aggstate->eqfunctions,
+                                  tmpcontext->ecxt_per_tuple_memory)))
+   {
+       aggstate->current_set = aggstate->projected_set = currentSet;
+
+       econtext->grouped_cols = aggstate->grouped_cols[currentSet];
+
+       for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+       {
+           AggStatePerAgg peraggstate = &peragg[aggno];
+           AggStatePerGroup pergroupstate;
+
+           pergroupstate = &pergroup[aggno + (currentSet * (aggstate->numaggs))];
+
+           if (peraggstate->numSortCols > 0)
+           {
+               if (peraggstate->numInputs == 1)
+                   process_ordered_aggregate_single(aggstate,
+                                                    peraggstate,
+                                                    pergroupstate);
+               else
+                   process_ordered_aggregate_multi(aggstate,
+                                                   peraggstate,
+                                                   pergroupstate);
+           }
+
+           finalize_aggregate(aggstate, peraggstate, pergroupstate,
+                              &aggvalues[aggno], &aggnulls[aggno]);
+       }
+
+       /*
+        * Check the qual (HAVING clause); if the group does not match, ignore
+        * it.
+        */
+       if (ExecQual(aggstate->ss.ps.qual, econtext, false))
+       {
+           /*
+            * Form a projection tuple using the aggregate results
+            * and the representative input tuple.
+            */
+           TupleTableSlot *result;
+           ExprDoneCond isDone;
+
+           do
+           {
+               result = ExecProject(aggstate->ss.ps.ps_ProjInfo, &isDone);
+
+               if (isDone != ExprEndResult)
+               {
+                   tuplestore_puttupleslot(aggstate->chain_tuplestore,
+                                           result);
+               }
+           }
+           while (isDone == ExprMultipleResult);
+       }
+       else
+           InstrCountFiltered1(aggstate, 1);
+
+       ReScanExprContext(tmpcontext);
+       ReScanExprContext(econtext);
+       ReScanExprContext(aggstate->aggcontexts[currentSet]);
+       if (++currentSet >= numGroupingSets)
+           break;
+   }
+
+   if (TupIsNull(outerslot))
+   {
+       aggstate->agg_done = true;
+
+       /*
+        * We're out of input, so the calling node has all the data it needs
+        * and (if it's a Sort) is about to sort it. We preemptively request a
+        * rescan of our input plan here, so that Sort nodes containing data
+        * that is no longer needed will free their memory.  The intention here
+        * is to bound the peak memory requirement for the whole chain to
+        * 2*work_mem if REWIND was not requested, or 3*work_mem if REWIND was
+        * requested and we had to supply a Sort node for the original data
+        * source plan.
+        */
+
+       ExecReScan(outerPlanState(aggstate));
+
+       return NULL;
+   }
+
+   /*
+    * If this is the first tuple, store it and initialize everything.
+    * Otherwise re-init any aggregates we projected above.
+    */
+
+   if (TupIsNull(firstSlot))
+   {
+       ExecCopySlot(firstSlot, outerslot);
+       initialize_aggregates(aggstate, peragg, pergroup, numGroupingSets);
+   }
+   else if (currentSet > 0)
+   {
+       ExecCopySlot(firstSlot, outerslot);
+       initialize_aggregates(aggstate, peragg, pergroup, currentSet);
+   }
+
+   tmpcontext->ecxt_outertuple = outerslot;
+
+   /* Actually accumulate the current tuple. */
+   advance_aggregates(aggstate, pergroup);
+
+   /* Reset per-input-tuple context after each tuple */
+   ResetExprContext(tmpcontext);
+
+   return outerslot;
+}
+
 /*
  * ExecAgg for hashed case: phase 1, read input and build hash table
  */
@@ -1493,12 +1898,17 @@ AggState *
 ExecInitAgg(Agg *node, EState *estate, int eflags)
 {
    AggState   *aggstate;
+   AggState   *save_chain_head = NULL;
    AggStatePerAgg peragg;
    Plan       *outerPlan;
    ExprContext *econtext;
    int         numaggs,
                aggno;
    ListCell   *l;
+   int         numGroupingSets = 1;
+   int         currentsortno = 0;
+   int         i = 0;
+   int         j = 0;
 
    /* check for unsupported flags */
    Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1512,38 +1922,76 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
    aggstate->aggs = NIL;
    aggstate->numaggs = 0;
+   aggstate->numsets = 0;
    aggstate->eqfunctions = NULL;
    aggstate->hashfunctions = NULL;
+   aggstate->projected_set = -1;
+   aggstate->current_set = 0;
    aggstate->peragg = NULL;
    aggstate->curperagg = NULL;
    aggstate->agg_done = false;
+   aggstate->input_done = false;
+   aggstate->chain_done = true;
    aggstate->pergroup = NULL;
    aggstate->grp_firstTuple = NULL;
    aggstate->hashtable = NULL;
+   aggstate->chain_depth = 0;
+   aggstate->chain_rescan = 0;
+   aggstate->chain_eflags = eflags & EXEC_FLAG_REWIND;
+   aggstate->chain_top = false;
+   aggstate->chain_head = NULL;
+   aggstate->chain_tuplestore = NULL;
+
+   if (node->groupingSets)
+   {
+       Assert(node->aggstrategy != AGG_HASHED);
+
+       numGroupingSets = list_length(node->groupingSets);
+       aggstate->numsets = numGroupingSets;
+       aggstate->gset_lengths = palloc(numGroupingSets * sizeof(int));
+       aggstate->grouped_cols = palloc(numGroupingSets * sizeof(Bitmapset *));
+
+       i = 0;
+       foreach(l, node->groupingSets)
+       {
+           int current_length = list_length(lfirst(l));
+           Bitmapset *cols = NULL;
+
+           /* planner forces this to be correct */
+           for (j = 0; j < current_length; ++j)
+               cols = bms_add_member(cols, node->grpColIdx[j]);
+
+           aggstate->grouped_cols[i] = cols;
+           aggstate->gset_lengths[i] = current_length;
+           ++i;
+       }
+   }
+
+   aggstate->aggcontexts = (ExprContext **) palloc0(sizeof(ExprContext *) * numGroupingSets);
 
    /*
-    * Create expression contexts.  We need two, one for per-input-tuple
-    * processing and one for per-output-tuple processing.  We cheat a little
-    * by using ExecAssignExprContext() to build both.
+    * Create expression contexts.  We need three or more, one for
+    * per-input-tuple processing, one for per-output-tuple processing, and one
+    * for each grouping set.  The per-tuple memory context of the
+    * per-grouping-set ExprContexts (aggcontexts) replaces the standalone
+    * memory context formerly used to hold transition values.  We cheat a
+    * little by using ExecAssignExprContext() to build all of them.
+    *
+    * NOTE: the details of what is stored in aggcontexts and what is stored in
+    * the regular per-query memory context are driven by a simple decision: we
+    * want to reset the aggcontext at group boundaries (if not hashing) and in
+    * ExecReScanAgg to recover no-longer-wanted space.
     */
    ExecAssignExprContext(estate, &aggstate->ss.ps);
    aggstate->tmpcontext = aggstate->ss.ps.ps_ExprContext;
-   ExecAssignExprContext(estate, &aggstate->ss.ps);
 
-   /*
-    * We also need a long-lived memory context for holding hashtable data
-    * structures and transition values.  NOTE: the details of what is stored
-    * in aggcontext and what is stored in the regular per-query memory
-    * context are driven by a simple decision: we want to reset the
-    * aggcontext at group boundaries (if not hashing) and in ExecReScanAgg to
-    * recover no-longer-wanted space.
-    */
-   aggstate->aggcontext =
-       AllocSetContextCreate(CurrentMemoryContext,
-                             "AggContext",
-                             ALLOCSET_DEFAULT_MINSIZE,
-                             ALLOCSET_DEFAULT_INITSIZE,
-                             ALLOCSET_DEFAULT_MAXSIZE);
+   for (i = 0; i < numGroupingSets; ++i)
+   {
+       ExecAssignExprContext(estate, &aggstate->ss.ps);
+       aggstate->aggcontexts[i] = aggstate->ss.ps.ps_ExprContext;
+   }
+
+   ExecAssignExprContext(estate, &aggstate->ss.ps);
 
    /*
     * tuple table initialization
@@ -1561,24 +2009,78 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
     * that is true, we don't need to worry about evaluating the aggs in any
     * particular order.
     */
-   aggstate->ss.ps.targetlist = (List *)
-       ExecInitExpr((Expr *) node->plan.targetlist,
-                    (PlanState *) aggstate);
-   aggstate->ss.ps.qual = (List *)
-       ExecInitExpr((Expr *) node->plan.qual,
-                    (PlanState *) aggstate);
+   if (node->aggstrategy == AGG_CHAINED)
+   {
+       AggState   *chain_head = estate->agg_chain_head;
+       Agg        *chain_head_plan;
+
+       Assert(chain_head);
+
+       aggstate->chain_head = chain_head;
+       chain_head->chain_depth++;
+
+       chain_head_plan = (Agg *) chain_head->ss.ps.plan;
+
+       /*
+        * If we reached the originally declared depth, we must be the "top"
+        * (furthest from plan root) node in the chain.
+        */
+       if (chain_head_plan->chain_depth == chain_head->chain_depth)
+           aggstate->chain_top = true;
+
+       /*
+        * Snarf the real targetlist and qual from the chain head node
+        */
+       aggstate->ss.ps.targetlist = (List *)
+           ExecInitExpr((Expr *) chain_head_plan->plan.targetlist,
+                        (PlanState *) aggstate);
+       aggstate->ss.ps.qual = (List *)
+           ExecInitExpr((Expr *) chain_head_plan->plan.qual,
+                        (PlanState *) aggstate);
+   }
+   else
+   {
+       aggstate->ss.ps.targetlist = (List *)
+           ExecInitExpr((Expr *) node->plan.targetlist,
+                        (PlanState *) aggstate);
+       aggstate->ss.ps.qual = (List *)
+           ExecInitExpr((Expr *) node->plan.qual,
+                        (PlanState *) aggstate);
+   }
+
+   if (node->chain_depth > 0)
+   {
+       save_chain_head = estate->agg_chain_head;
+       estate->agg_chain_head = aggstate;
+       aggstate->chain_tuplestore = tuplestore_begin_heap(false, false, work_mem);
+       aggstate->chain_done = false;
+   }
 
    /*
-    * initialize child nodes
+    * Initialize child nodes.
     *
     * If we are doing a hashed aggregation then the child plan does not need
     * to handle REWIND efficiently; see ExecReScanAgg.
+    *
+    * If we have more than one associated ChainAggregate node, then we turn
+    * off REWIND and restore it in the chain top, so that the intermediate
+    * Sort nodes will discard their data on rescan.  This lets us put an upper
+    * bound on the memory usage, even when we have a long chain of sorts (at
+    * the cost of having to re-sort on rewind, which is why we don't do it
+    * for only one node where no memory would be saved).
     */
-   if (node->aggstrategy == AGG_HASHED)
+   if (aggstate->chain_top)
+       eflags |= aggstate->chain_head->chain_eflags;
+   else if (node->aggstrategy == AGG_HASHED || node->chain_depth > 1)
        eflags &= ~EXEC_FLAG_REWIND;
    outerPlan = outerPlan(node);
    outerPlanState(aggstate) = ExecInitNode(outerPlan, estate, eflags);
 
+   if (node->chain_depth > 0)
+   {
+       estate->agg_chain_head = save_chain_head;
+   }
+
    /*
     * initialize source tuple type.
     */
@@ -1587,8 +2089,35 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
    /*
     * Initialize result tuple type and projection info.
     */
-   ExecAssignResultTypeFromTL(&aggstate->ss.ps);
-   ExecAssignProjectionInfo(&aggstate->ss.ps, NULL);
+   if (node->aggstrategy == AGG_CHAINED)
+   {
+       PlanState  *head_ps = &aggstate->chain_head->ss.ps;
+       bool        hasoid;
+
+       /*
+        * We must calculate this the same way that the chain head does,
+        * regardless of intermediate nodes, for consistency
+        */
+       if (!ExecContextForcesOids(head_ps, &hasoid))
+           hasoid = false;
+
+       ExecAssignResultType(&aggstate->ss.ps, ExecGetScanType(&aggstate->ss));
+       ExecSetSlotDescriptor(aggstate->hashslot,
+                             ExecTypeFromTL(head_ps->plan->targetlist, hasoid));
+       aggstate->ss.ps.ps_ProjInfo =
+           ExecBuildProjectionInfo(aggstate->ss.ps.targetlist,
+                                   aggstate->ss.ps.ps_ExprContext,
+                                   aggstate->hashslot,
+                                   NULL);
+
+       aggstate->chain_tuplestore = aggstate->chain_head->chain_tuplestore;
+       Assert(aggstate->chain_tuplestore);
+   }
+   else
+   {
+       ExecAssignResultTypeFromTL(&aggstate->ss.ps);
+       ExecAssignProjectionInfo(&aggstate->ss.ps, NULL);
+   }
 
    aggstate->ss.ps.ps_TupFromTlist = false;
 
@@ -1649,7 +2178,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
    {
        AggStatePerGroup pergroup;
 
-       pergroup = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData) * numaggs);
+       pergroup = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData)
+                                             * numaggs
+                                             * numGroupingSets);
+
        aggstate->pergroup = pergroup;
    }
 
@@ -1712,7 +2244,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
        /* Begin filling in the peraggstate data */
        peraggstate->aggrefstate = aggrefstate;
        peraggstate->aggref = aggref;
-       peraggstate->sortstate = NULL;
+       peraggstate->sortstates = (Tuplesortstate**) palloc0(sizeof(Tuplesortstate*) * numGroupingSets);
+
+       for (currentsortno = 0; currentsortno < numGroupingSets; currentsortno++)
+           peraggstate->sortstates[currentsortno] = NULL;
 
        /* Fetch the pg_aggregate row */
        aggTuple = SearchSysCache1(AGGFNOID,
@@ -2020,31 +2555,38 @@ ExecEndAgg(AggState *node)
 {
    PlanState  *outerPlan;
    int         aggno;
+   int         numGroupingSets = Max(node->numsets, 1);
+   int         setno;
 
    /* Make sure we have closed any open tuplesorts */
    for (aggno = 0; aggno < node->numaggs; aggno++)
    {
        AggStatePerAgg peraggstate = &node->peragg[aggno];
 
-       if (peraggstate->sortstate)
-           tuplesort_end(peraggstate->sortstate);
+       for (setno = 0; setno < numGroupingSets; setno++)
+       {
+           if (peraggstate->sortstates[setno])
+               tuplesort_end(peraggstate->sortstates[setno]);
+       }
    }
 
    /* And ensure any agg shutdown callbacks have been called */
-   ReScanExprContext(node->ss.ps.ps_ExprContext);
+   for (setno = 0; setno < numGroupingSets; setno++)
+       ReScanExprContext(node->aggcontexts[setno]);
+
+   if (node->chain_tuplestore && node->chain_depth > 0)
+       tuplestore_end(node->chain_tuplestore);
 
    /*
-    * Free both the expr contexts.
+    * We don't actually free any ExprContexts here (see comment in
+    * ExecFreeExprContext), just unlinking the output one from the plan node
+    * suffices.
     */
    ExecFreeExprContext(&node->ss.ps);
-   node->ss.ps.ps_ExprContext = node->tmpcontext;
-   ExecFreeExprContext(&node->ss.ps);
 
    /* clean up tuple table */
    ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
-   MemoryContextDelete(node->aggcontext);
-
    outerPlan = outerPlanState(node);
    ExecEndNode(outerPlan);
 }
@@ -2054,13 +2596,16 @@ ExecReScanAgg(AggState *node)
 {
    ExprContext *econtext = node->ss.ps.ps_ExprContext;
    PlanState   *outerPlan = outerPlanState(node);
+   Agg        *aggnode = (Agg *) node->ss.ps.plan;
    int         aggno;
+   int         numGroupingSets = Max(node->numsets, 1);
+   int         setno;
 
    node->agg_done = false;
 
    node->ss.ps.ps_TupFromTlist = false;
 
-   if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+   if (aggnode->aggstrategy == AGG_HASHED)
    {
        /*
         * In the hashed case, if we haven't yet built the hash table then we
@@ -2086,14 +2631,34 @@ ExecReScanAgg(AggState *node)
    /* Make sure we have closed any open tuplesorts */
    for (aggno = 0; aggno < node->numaggs; aggno++)
    {
-       AggStatePerAgg peraggstate = &node->peragg[aggno];
+       for (setno = 0; setno < numGroupingSets; setno++)
+       {
+           AggStatePerAgg peraggstate = &node->peragg[aggno];
 
-       if (peraggstate->sortstate)
-           tuplesort_end(peraggstate->sortstate);
-       peraggstate->sortstate = NULL;
+           if (peraggstate->sortstates[setno])
+           {
+               tuplesort_end(peraggstate->sortstates[setno]);
+               peraggstate->sortstates[setno] = NULL;
+           }
+       }
    }
 
-   /* We don't need to ReScanExprContext here; ExecReScan already did it */
+   /*
+    * We don't need to ReScanExprContext the output tuple context here;
+    * ExecReScan already did it. But we do need to reset our per-grouping-set
+    * contexts, which may have transvalues stored in them. (We use rescan
+    * rather than just reset because transfns may have registered callbacks
+    * that need to be run now.)
+    *
+    * Note that with AGG_HASHED, the hash table is allocated in a sub-context
+    * of the aggcontext. This used to be an issue, but now, resetting a
+    * context automatically deletes sub-contexts too.
+    */
+
+   for (setno = 0; setno < numGroupingSets; setno++)
+   {
+       ReScanExprContext(node->aggcontexts[setno]);
+   }
 
    /* Release first tuple of group, if we have made a copy */
    if (node->grp_firstTuple != NULL)
@@ -2101,21 +2666,13 @@ ExecReScanAgg(AggState *node)
        heap_freetuple(node->grp_firstTuple);
        node->grp_firstTuple = NULL;
    }
+   ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
    /* Forget current agg values */
    MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numaggs);
    MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * node->numaggs);
 
-   /*
-    * Release all temp storage. Note that with AGG_HASHED, the hash table is
-    * allocated in a sub-context of the aggcontext. We're going to rebuild
-    * the hash table from scratch, so we need to use
-    * MemoryContextResetAndDeleteChildren() to avoid leaking the old hash
-    * table's memory context header.
-    */
-   MemoryContextResetAndDeleteChildren(node->aggcontext);
-
-   if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+   if (aggnode->aggstrategy == AGG_HASHED)
    {
        /* Rebuild an empty hash table */
        build_hash_table(node);
@@ -2127,15 +2684,54 @@ ExecReScanAgg(AggState *node)
         * Reset the per-group state (in particular, mark transvalues null)
         */
        MemSet(node->pergroup, 0,
-              sizeof(AggStatePerGroupData) * node->numaggs);
+              sizeof(AggStatePerGroupData) * node->numaggs * numGroupingSets);
+
+       node->input_done = false;
    }
 
    /*
-    * if chgParam of subnode is not null then plan will be re-scanned by
-    * first ExecProcNode.
+    * If we're in a chain, let the chain head know whether we
+    * rescanned. (This is nonsense if it happens as a result of chgParam,
+    * but the chain head only cares about this when rescanning explicitly
+    * when chgParam is empty.)
+    */
+
+   if (aggnode->aggstrategy == AGG_CHAINED)
+       node->chain_head->chain_rescan++;
+
+   /*
+    * If we're a chain head, we reset the tuplestore if parameters changed,
+    * and let subplans repopulate it.
+    *
+    * If we're a chain head and the subplan parameters did NOT change, then
+    * whether we need to reset the tuplestore depends on whether anything
+    * (specifically the Sort nodes) protects the child ChainAggs from rescan.
+    * Since this is hard to know in advance, we have the ChainAggs signal us
+    * as to whether the reset is needed.  Since we're preempting the rescan
+    * in some cases, we only check whether any ChainAgg node was reached in
+    * the rescan; the others may have already been reset.
     */
-   if (outerPlan->chgParam == NULL)
+   if (aggnode->chain_depth > 0)
+   {
+       if (outerPlan->chgParam)
+           tuplestore_clear(node->chain_tuplestore);
+       else
+       {
+           node->chain_rescan = 0;
+
+           ExecReScan(outerPlan);
+
+           if (node->chain_rescan > 0)
+               tuplestore_clear(node->chain_tuplestore);
+           else
+               tuplestore_rescan(node->chain_tuplestore);
+       }
+       node->chain_done = false;
+   }
+   else if (outerPlan->chgParam == NULL)
+   {
        ExecReScan(outerPlan);
+   }
 }
 
 
@@ -2155,8 +2751,11 @@ ExecReScanAgg(AggState *node)
  * values could conceivably appear in future.)
  *
  * If aggcontext isn't NULL, the function also stores at *aggcontext the
- * identity of the memory context that aggregate transition values are
- * being stored in.
+ * identity of the memory context that aggregate transition values are being
+ * stored in.  Note that the same aggregate call site (flinfo) may be called
+ * interleaved on different transition values in different contexts, so it's
+ * not kosher to cache aggcontext under fn_extra.  It is, however, kosher to
+ * cache it in the transvalue itself (for internal-type transvalues).
  */
 int
 AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
@@ -2164,7 +2763,11 @@ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
    if (fcinfo->context && IsA(fcinfo->context, AggState))
    {
        if (aggcontext)
-           *aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+       {
+           AggState    *aggstate = ((AggState *) fcinfo->context);
+           ExprContext *cxt  = aggstate->aggcontexts[aggstate->current_set];
+           *aggcontext = cxt->ecxt_per_tuple_memory;
+       }
        return AGG_CONTEXT_AGGREGATE;
    }
    if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
@@ -2248,8 +2851,9 @@ AggRegisterCallback(FunctionCallInfo fcinfo,
    if (fcinfo->context && IsA(fcinfo->context, AggState))
    {
        AggState   *aggstate = (AggState *) fcinfo->context;
+       ExprContext *cxt  = aggstate->aggcontexts[aggstate->current_set];
 
-       RegisterExprContextCallback(aggstate->ss.ps.ps_ExprContext, func, arg);
+       RegisterExprContextCallback(cxt, func, arg);
 
        return;
    }
index ed8fa72621ece43657d7d647600946f7d0e3ec2d..bcf6f791fe55515cffed9c439d5cd368a053193d 100644 (file)
@@ -816,6 +816,7 @@ _copyAgg(const Agg *from)
    CopyPlanFields((const Plan *) from, (Plan *) newnode);
 
    COPY_SCALAR_FIELD(aggstrategy);
+   COPY_SCALAR_FIELD(chain_depth);
    COPY_SCALAR_FIELD(numCols);
    if (from->numCols > 0)
    {
@@ -823,6 +824,7 @@ _copyAgg(const Agg *from)
        COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
    }
    COPY_SCALAR_FIELD(numGroups);
+   COPY_NODE_FIELD(groupingSets);
 
    return newnode;
 }
@@ -1110,6 +1112,27 @@ _copyVar(const Var *from)
    return newnode;
 }
 
+/*
+ * _copyGroupedVar
+ */
+static GroupedVar *
+_copyGroupedVar(const GroupedVar *from)
+{
+   GroupedVar         *newnode = makeNode(GroupedVar);
+
+   COPY_SCALAR_FIELD(varno);
+   COPY_SCALAR_FIELD(varattno);
+   COPY_SCALAR_FIELD(vartype);
+   COPY_SCALAR_FIELD(vartypmod);
+   COPY_SCALAR_FIELD(varcollid);
+   COPY_SCALAR_FIELD(varlevelsup);
+   COPY_SCALAR_FIELD(varnoold);
+   COPY_SCALAR_FIELD(varoattno);
+   COPY_LOCATION_FIELD(location);
+
+   return newnode;
+}
+
 /*
  * _copyConst
  */
@@ -1192,6 +1215,23 @@ _copyAggref(const Aggref *from)
    return newnode;
 }
 
+/*
+ * _copyGroupingFunc
+ */
+static GroupingFunc *
+_copyGroupingFunc(const GroupingFunc *from)
+{
+   GroupingFunc       *newnode = makeNode(GroupingFunc);
+
+   COPY_NODE_FIELD(args);
+   COPY_NODE_FIELD(refs);
+   COPY_NODE_FIELD(cols);
+   COPY_SCALAR_FIELD(agglevelsup);
+   COPY_LOCATION_FIELD(location);
+
+   return newnode;
+}
+
 /*
  * _copyWindowFunc
  */
@@ -2135,6 +2175,18 @@ _copySortGroupClause(const SortGroupClause *from)
    return newnode;
 }
 
+static GroupingSet *
+_copyGroupingSet(const GroupingSet *from)
+{
+   GroupingSet        *newnode = makeNode(GroupingSet);
+
+   COPY_SCALAR_FIELD(kind);
+   COPY_NODE_FIELD(content);
+   COPY_LOCATION_FIELD(location);
+
+   return newnode;
+}
+
 static WindowClause *
 _copyWindowClause(const WindowClause *from)
 {
@@ -2625,6 +2677,7 @@ _copyQuery(const Query *from)
    COPY_NODE_FIELD(onConflict);
    COPY_NODE_FIELD(returningList);
    COPY_NODE_FIELD(groupClause);
+   COPY_NODE_FIELD(groupingSets);
    COPY_NODE_FIELD(havingQual);
    COPY_NODE_FIELD(windowClause);
    COPY_NODE_FIELD(distinctClause);
@@ -4244,6 +4297,9 @@ copyObject(const void *from)
        case T_Var:
            retval = _copyVar(from);
            break;
+       case T_GroupedVar:
+           retval = _copyGroupedVar(from);
+           break;
        case T_Const:
            retval = _copyConst(from);
            break;
@@ -4253,6 +4309,9 @@ copyObject(const void *from)
        case T_Aggref:
            retval = _copyAggref(from);
            break;
+       case T_GroupingFunc:
+           retval = _copyGroupingFunc(from);
+           break;
        case T_WindowFunc:
            retval = _copyWindowFunc(from);
            break;
@@ -4822,6 +4881,9 @@ copyObject(const void *from)
        case T_SortGroupClause:
            retval = _copySortGroupClause(from);
            break;
+       case T_GroupingSet:
+           retval = _copyGroupingSet(from);
+           break;
        case T_WindowClause:
            retval = _copyWindowClause(from);
            break;
index 7c86e919a493a72d5ee5e753450fa433c4c3ad7b..d3868ab4490d5bb3874a1b7fbb49812feda58fb9 100644 (file)
@@ -152,6 +152,22 @@ _equalVar(const Var *a, const Var *b)
    return true;
 }
 
+static bool
+_equalGroupedVar(const GroupedVar *a, const GroupedVar *b)
+{
+   COMPARE_SCALAR_FIELD(varno);
+   COMPARE_SCALAR_FIELD(varattno);
+   COMPARE_SCALAR_FIELD(vartype);
+   COMPARE_SCALAR_FIELD(vartypmod);
+   COMPARE_SCALAR_FIELD(varcollid);
+   COMPARE_SCALAR_FIELD(varlevelsup);
+   COMPARE_SCALAR_FIELD(varnoold);
+   COMPARE_SCALAR_FIELD(varoattno);
+   COMPARE_LOCATION_FIELD(location);
+
+   return true;
+}
+
 static bool
 _equalConst(const Const *a, const Const *b)
 {
@@ -207,6 +223,21 @@ _equalAggref(const Aggref *a, const Aggref *b)
    return true;
 }
 
+static bool
+_equalGroupingFunc(const GroupingFunc *a, const GroupingFunc *b)
+{
+   COMPARE_NODE_FIELD(args);
+
+   /*
+    * We must not compare the refs or cols field
+    */
+
+   COMPARE_SCALAR_FIELD(agglevelsup);
+   COMPARE_LOCATION_FIELD(location);
+
+   return true;
+}
+
 static bool
 _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
 {
@@ -896,6 +927,7 @@ _equalQuery(const Query *a, const Query *b)
    COMPARE_NODE_FIELD(onConflict);
    COMPARE_NODE_FIELD(returningList);
    COMPARE_NODE_FIELD(groupClause);
+   COMPARE_NODE_FIELD(groupingSets);
    COMPARE_NODE_FIELD(havingQual);
    COMPARE_NODE_FIELD(windowClause);
    COMPARE_NODE_FIELD(distinctClause);
@@ -2423,6 +2455,16 @@ _equalSortGroupClause(const SortGroupClause *a, const SortGroupClause *b)
    return true;
 }
 
+static bool
+_equalGroupingSet(const GroupingSet *a, const GroupingSet *b)
+{
+   COMPARE_SCALAR_FIELD(kind);
+   COMPARE_NODE_FIELD(content);
+   COMPARE_LOCATION_FIELD(location);
+
+   return true;
+}
+
 static bool
 _equalWindowClause(const WindowClause *a, const WindowClause *b)
 {
@@ -2651,6 +2693,9 @@ equal(const void *a, const void *b)
        case T_Var:
            retval = _equalVar(a, b);
            break;
+       case T_GroupedVar:
+           retval = _equalGroupedVar(a, b);
+           break;
        case T_Const:
            retval = _equalConst(a, b);
            break;
@@ -2660,6 +2705,9 @@ equal(const void *a, const void *b)
        case T_Aggref:
            retval = _equalAggref(a, b);
            break;
+       case T_GroupingFunc:
+           retval = _equalGroupingFunc(a, b);
+           break;
        case T_WindowFunc:
            retval = _equalWindowFunc(a, b);
            break;
@@ -3216,6 +3264,9 @@ equal(const void *a, const void *b)
        case T_SortGroupClause:
            retval = _equalSortGroupClause(a, b);
            break;
+       case T_GroupingSet:
+           retval = _equalGroupingSet(a, b);
+           break;
        case T_WindowClause:
            retval = _equalWindowClause(a, b);
            break;
index 94cab476eb01f698924b995eceec34eb62c87c83..a6737514ef176cc1cecce235c4d177fde6249fbf 100644 (file)
@@ -822,6 +822,32 @@ list_intersection(const List *list1, const List *list2)
    return result;
 }
 
+/*
+ * As list_intersection but operates on lists of integers.
+ */
+List *
+list_intersection_int(const List *list1, const List *list2)
+{
+   List       *result;
+   const ListCell *cell;
+
+   if (list1 == NIL || list2 == NIL)
+       return NIL;
+
+   Assert(IsIntegerList(list1));
+   Assert(IsIntegerList(list2));
+
+   result = NIL;
+   foreach(cell, list1)
+   {
+       if (list_member_int(list2, lfirst_int(cell)))
+           result = lappend_int(result, lfirst_int(cell));
+   }
+
+   check_list_invariants(result);
+   return result;
+}
+
 /*
  * Return a list that contains all the cells in list1 that are not in
  * list2. The returned list is freshly allocated via palloc(), but the
index 6fdf44d57367e4b60bb7cb966bee72a6d89310f3..a9b58eb31fc7085533e7240343893660ba395b77 100644 (file)
@@ -554,3 +554,18 @@ makeFuncCall(List *name, List *args, int location)
    n->location = location;
    return n;
 }
+
+/*
+ * makeGroupingSet
+ *
+ */
+GroupingSet *
+makeGroupingSet(GroupingSetKind kind, List *content, int location)
+{
+   GroupingSet    *n = makeNode(GroupingSet);
+
+   n->kind = kind;
+   n->content = content;
+   n->location = location;
+   return n;
+}
index 4135f9c3cfc17f662034e60de4a09647a03824fe..20f2ebb3b0740aaf2a579874462ae4427bd4d1d3 100644 (file)
@@ -45,6 +45,9 @@ exprType(const Node *expr)
        case T_Var:
            type = ((const Var *) expr)->vartype;
            break;
+       case T_GroupedVar:
+           type = ((const GroupedVar *) expr)->vartype;
+           break;
        case T_Const:
            type = ((const Const *) expr)->consttype;
            break;
@@ -54,6 +57,9 @@ exprType(const Node *expr)
        case T_Aggref:
            type = ((const Aggref *) expr)->aggtype;
            break;
+       case T_GroupingFunc:
+           type = INT4OID;
+           break;
        case T_WindowFunc:
            type = ((const WindowFunc *) expr)->wintype;
            break;
@@ -268,6 +274,8 @@ exprTypmod(const Node *expr)
    {
        case T_Var:
            return ((const Var *) expr)->vartypmod;
+       case T_GroupedVar:
+           return ((const GroupedVar *) expr)->vartypmod;
        case T_Const:
            return ((const Const *) expr)->consttypmod;
        case T_Param:
@@ -741,6 +749,9 @@ exprCollation(const Node *expr)
        case T_Var:
            coll = ((const Var *) expr)->varcollid;
            break;
+       case T_GroupedVar:
+           coll = ((const GroupedVar *) expr)->varcollid;
+           break;
        case T_Const:
            coll = ((const Const *) expr)->constcollid;
            break;
@@ -750,6 +761,9 @@ exprCollation(const Node *expr)
        case T_Aggref:
            coll = ((const Aggref *) expr)->aggcollid;
            break;
+       case T_GroupingFunc:
+           coll = InvalidOid;
+           break;
        case T_WindowFunc:
            coll = ((const WindowFunc *) expr)->wincollid;
            break;
@@ -977,6 +991,9 @@ exprSetCollation(Node *expr, Oid collation)
        case T_Var:
            ((Var *) expr)->varcollid = collation;
            break;
+       case T_GroupedVar:
+           ((GroupedVar *) expr)->varcollid = collation;
+           break;
        case T_Const:
            ((Const *) expr)->constcollid = collation;
            break;
@@ -986,6 +1003,9 @@ exprSetCollation(Node *expr, Oid collation)
        case T_Aggref:
            ((Aggref *) expr)->aggcollid = collation;
            break;
+       case T_GroupingFunc:
+           Assert(!OidIsValid(collation));
+           break;
        case T_WindowFunc:
            ((WindowFunc *) expr)->wincollid = collation;
            break;
@@ -1192,6 +1212,9 @@ exprLocation(const Node *expr)
        case T_Var:
            loc = ((const Var *) expr)->location;
            break;
+       case T_GroupedVar:
+           loc = ((const GroupedVar *) expr)->location;
+           break;
        case T_Const:
            loc = ((const Const *) expr)->location;
            break;
@@ -1202,6 +1225,9 @@ exprLocation(const Node *expr)
            /* function name should always be the first thing */
            loc = ((const Aggref *) expr)->location;
            break;
+       case T_GroupingFunc:
+           loc = ((const GroupingFunc *) expr)->location;
+           break;
        case T_WindowFunc:
            /* function name should always be the first thing */
            loc = ((const WindowFunc *) expr)->location;
@@ -1491,6 +1517,9 @@ exprLocation(const Node *expr)
            /* XMLSERIALIZE keyword should always be the first thing */
            loc = ((const XmlSerialize *) expr)->location;
            break;
+       case T_GroupingSet:
+           loc = ((const GroupingSet *) expr)->location;
+           break;
        case T_WithClause:
            loc = ((const WithClause *) expr)->location;
            break;
@@ -1652,6 +1681,7 @@ expression_tree_walker(Node *node,
    switch (nodeTag(node))
    {
        case T_Var:
+       case T_GroupedVar:
        case T_Const:
        case T_Param:
        case T_CoerceToDomainValue:
@@ -1685,6 +1715,15 @@ expression_tree_walker(Node *node,
                    return true;
            }
            break;
+       case T_GroupingFunc:
+           {
+               GroupingFunc *grouping = (GroupingFunc *) node;
+
+               if (expression_tree_walker((Node *) grouping->args,
+                                          walker, context))
+                   return true;
+           }
+           break;
        case T_WindowFunc:
            {
                WindowFunc *expr = (WindowFunc *) node;
@@ -2192,6 +2231,15 @@ expression_tree_mutator(Node *node,
                return (Node *) newnode;
            }
            break;
+       case T_GroupedVar:
+           {
+               GroupedVar         *groupedvar = (GroupedVar *) node;
+               GroupedVar         *newnode;
+
+               FLATCOPY(newnode, groupedvar, GroupedVar);
+               return (Node *) newnode;
+           }
+           break;
        case T_Const:
            {
                Const      *oldnode = (Const *) node;
@@ -2233,6 +2281,29 @@ expression_tree_mutator(Node *node,
                return (Node *) newnode;
            }
            break;
+       case T_GroupingFunc:
+           {
+               GroupingFunc   *grouping = (GroupingFunc *) node;
+               GroupingFunc   *newnode;
+
+               FLATCOPY(newnode, grouping, GroupingFunc);
+               MUTATE(newnode->args, grouping->args, List *);
+
+               /*
+                * We assume here that mutating the arguments does not change
+                * the semantics, i.e. that the arguments are not mutated in a
+                * way that makes them semantically different from their
+                * previously matching expressions in the GROUP BY clause.
+                *
+                * If a mutator somehow wanted to do this, it would have to
+                * handle the refs and cols lists itself as appropriate.
+                */
+               newnode->refs = list_copy(grouping->refs);
+               newnode->cols = list_copy(grouping->cols);
+
+               return (Node *) newnode;
+           }
+           break;
        case T_WindowFunc:
            {
                WindowFunc *wfunc = (WindowFunc *) node;
@@ -2943,6 +3014,8 @@ raw_expression_tree_walker(Node *node,
            break;
        case T_RangeVar:
            return walker(((RangeVar *) node)->alias, context);
+       case T_GroupingFunc:
+           return walker(((GroupingFunc *) node)->args, context);
        case T_SubLink:
            {
                SubLink    *sublink = (SubLink *) node;
@@ -3268,6 +3341,8 @@ raw_expression_tree_walker(Node *node,
                /* for now, constraints are ignored */
            }
            break;
+       case T_GroupingSet:
+           return walker(((GroupingSet *) node)->content, context);
        case T_LockingClause:
            return walker(((LockingClause *) node)->lockedRels, context);
        case T_XmlSerialize:
index fe868b889d96a93c025ae6871a6dec3f7726fbc3..e985ed68f9c8b54a1196ab676cefd738cffeefc3 100644 (file)
@@ -660,6 +660,7 @@ _outAgg(StringInfo str, const Agg *node)
    _outPlanInfo(str, (const Plan *) node);
 
    WRITE_ENUM_FIELD(aggstrategy, AggStrategy);
+   WRITE_INT_FIELD(chain_depth);
    WRITE_INT_FIELD(numCols);
 
    appendStringInfoString(str, " :grpColIdx");
@@ -671,6 +672,8 @@ _outAgg(StringInfo str, const Agg *node)
        appendStringInfo(str, " %u", node->grpOperators[i]);
 
    WRITE_LONG_FIELD(numGroups);
+
+   WRITE_NODE_FIELD(groupingSets);
 }
 
 static void
@@ -941,6 +944,22 @@ _outVar(StringInfo str, const Var *node)
    WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outGroupedVar(StringInfo str, const GroupedVar *node)
+{
+   WRITE_NODE_TYPE("GROUPEDVAR");
+
+   WRITE_UINT_FIELD(varno);
+   WRITE_INT_FIELD(varattno);
+   WRITE_OID_FIELD(vartype);
+   WRITE_INT_FIELD(vartypmod);
+   WRITE_OID_FIELD(varcollid);
+   WRITE_UINT_FIELD(varlevelsup);
+   WRITE_UINT_FIELD(varnoold);
+   WRITE_INT_FIELD(varoattno);
+   WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outConst(StringInfo str, const Const *node)
 {
@@ -995,6 +1014,18 @@ _outAggref(StringInfo str, const Aggref *node)
    WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outGroupingFunc(StringInfo str, const GroupingFunc *node)
+{
+   WRITE_NODE_TYPE("GROUPINGFUNC");
+
+   WRITE_NODE_FIELD(args);
+   WRITE_NODE_FIELD(refs);
+   WRITE_NODE_FIELD(cols);
+   WRITE_INT_FIELD(agglevelsup);
+   WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outWindowFunc(StringInfo str, const WindowFunc *node)
 {
@@ -2356,6 +2387,7 @@ _outQuery(StringInfo str, const Query *node)
    WRITE_NODE_FIELD(onConflict);
    WRITE_NODE_FIELD(returningList);
    WRITE_NODE_FIELD(groupClause);
+   WRITE_NODE_FIELD(groupingSets);
    WRITE_NODE_FIELD(havingQual);
    WRITE_NODE_FIELD(windowClause);
    WRITE_NODE_FIELD(distinctClause);
@@ -2390,6 +2422,16 @@ _outSortGroupClause(StringInfo str, const SortGroupClause *node)
    WRITE_BOOL_FIELD(hashable);
 }
 
+static void
+_outGroupingSet(StringInfo str, const GroupingSet *node)
+{
+   WRITE_NODE_TYPE("GROUPINGSET");
+
+   WRITE_ENUM_FIELD(kind, GroupingSetKind);
+   WRITE_NODE_FIELD(content);
+   WRITE_LOCATION_FIELD(location);
+}
+
 static void
 _outWindowClause(StringInfo str, const WindowClause *node)
 {
@@ -3036,6 +3078,9 @@ _outNode(StringInfo str, const void *obj)
            case T_Var:
                _outVar(str, obj);
                break;
+           case T_GroupedVar:
+               _outGroupedVar(str, obj);
+               break;
            case T_Const:
                _outConst(str, obj);
                break;
@@ -3045,6 +3090,9 @@ _outNode(StringInfo str, const void *obj)
            case T_Aggref:
                _outAggref(str, obj);
                break;
+           case T_GroupingFunc:
+               _outGroupingFunc(str, obj);
+               break;
            case T_WindowFunc:
                _outWindowFunc(str, obj);
                break;
@@ -3307,6 +3355,9 @@ _outNode(StringInfo str, const void *obj)
            case T_SortGroupClause:
                _outSortGroupClause(str, obj);
                break;
+           case T_GroupingSet:
+               _outGroupingSet(str, obj);
+               break;
            case T_WindowClause:
                _outWindowClause(str, obj);
                break;
index 8136306e1e55dd9e2471ec2204df5c7860cb8ac0..09707c7eed0e5570a6fae9259d32196ead485ef4 100644 (file)
@@ -217,6 +217,7 @@ _readQuery(void)
    READ_NODE_FIELD(onConflict);
    READ_NODE_FIELD(returningList);
    READ_NODE_FIELD(groupClause);
+   READ_NODE_FIELD(groupingSets);
    READ_NODE_FIELD(havingQual);
    READ_NODE_FIELD(windowClause);
    READ_NODE_FIELD(distinctClause);
@@ -292,6 +293,21 @@ _readSortGroupClause(void)
    READ_DONE();
 }
 
+/*
+ * _readGroupingSet
+ */
+static GroupingSet *
+_readGroupingSet(void)
+{
+   READ_LOCALS(GroupingSet);
+
+   READ_ENUM_FIELD(kind, GroupingSetKind);
+   READ_NODE_FIELD(content);
+   READ_LOCATION_FIELD(location);
+
+   READ_DONE();
+}
+
 /*
  * _readWindowClause
  */
@@ -442,6 +458,27 @@ _readVar(void)
    READ_DONE();
 }
 
+/*
+ * _readGroupedVar
+ */
+static GroupedVar *
+_readGroupedVar(void)
+{
+   READ_LOCALS(GroupedVar);
+
+   READ_UINT_FIELD(varno);
+   READ_INT_FIELD(varattno);
+   READ_OID_FIELD(vartype);
+   READ_INT_FIELD(vartypmod);
+   READ_OID_FIELD(varcollid);
+   READ_UINT_FIELD(varlevelsup);
+   READ_UINT_FIELD(varnoold);
+   READ_INT_FIELD(varoattno);
+   READ_LOCATION_FIELD(location);
+
+   READ_DONE();
+}
+
 /*
  * _readConst
  */
@@ -511,6 +548,23 @@ _readAggref(void)
    READ_DONE();
 }
 
+/*
+ * _readGroupingFunc
+ */
+static GroupingFunc *
+_readGroupingFunc(void)
+{
+   READ_LOCALS(GroupingFunc);
+
+   READ_NODE_FIELD(args);
+   READ_NODE_FIELD(refs);
+   READ_NODE_FIELD(cols);
+   READ_INT_FIELD(agglevelsup);
+   READ_LOCATION_FIELD(location);
+
+   READ_DONE();
+}
+
 /*
  * _readWindowFunc
  */
@@ -1345,6 +1399,8 @@ parseNodeString(void)
        return_value = _readWithCheckOption();
    else if (MATCH("SORTGROUPCLAUSE", 15))
        return_value = _readSortGroupClause();
+   else if (MATCH("GROUPINGSET", 11))
+       return_value = _readGroupingSet();
    else if (MATCH("WINDOWCLAUSE", 12))
        return_value = _readWindowClause();
    else if (MATCH("ROWMARKCLAUSE", 13))
@@ -1361,12 +1417,16 @@ parseNodeString(void)
        return_value = _readIntoClause();
    else if (MATCH("VAR", 3))
        return_value = _readVar();
+   else if (MATCH("GROUPEDVAR", 10))
+       return_value = _readGroupedVar();
    else if (MATCH("CONST", 5))
        return_value = _readConst();
    else if (MATCH("PARAM", 5))
        return_value = _readParam();
    else if (MATCH("AGGREF", 6))
        return_value = _readAggref();
+   else if (MATCH("GROUPINGFUNC", 12))
+       return_value = _readGroupingFunc();
    else if (MATCH("WINDOWFUNC", 10))
        return_value = _readWindowFunc();
    else if (MATCH("ARRAYREF", 8))
index 9caca94f64b1cc5a62ed7314066f972ebdd0eb71..0ee0a7020f39f15b5e6fa43db5b5447ed418226c 100644 (file)
@@ -1241,6 +1241,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
     */
    if (parse->hasAggs ||
        parse->groupClause ||
+       parse->groupingSets ||
        parse->havingQual ||
        parse->distinctClause ||
        parse->sortClause ||
@@ -2101,7 +2102,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
         * subquery uses grouping or aggregation, put it in HAVING (since the
         * qual really refers to the group-result rows).
         */
-       if (subquery->hasAggs || subquery->groupClause || subquery->havingQual)
+       if (subquery->hasAggs || subquery->groupClause || subquery->groupingSets || subquery->havingQual)
            subquery->havingQual = make_and_qual(subquery->havingQual, qual);
        else
            subquery->jointree->quals =
index fdd6baba6c674c8a8a909804af5d6cfc6209f8c6..ea5e6f71150358f351477737a3bd9940d2502637 100644 (file)
@@ -1954,7 +1954,8 @@ adjust_rowcount_for_semijoins(PlannerInfo *root,
            nraw = approximate_joinrel_size(root, sjinfo->syn_righthand);
            nunique = estimate_num_groups(root,
                                          sjinfo->semi_rhs_exprs,
-                                         nraw);
+                                         nraw,
+                                         NULL);
            if (rowcount > nunique)
                rowcount = nunique;
        }
index 11d39336b1a66172545c5f99d0203c6df742fd43..fa1de6aad0a17fad4dd16e290dcd0547940e9f8e 100644 (file)
@@ -581,6 +581,7 @@ query_supports_distinctness(Query *query)
 {
    if (query->distinctClause != NIL ||
        query->groupClause != NIL ||
+       query->groupingSets != NIL ||
        query->hasAggs ||
        query->havingQual ||
        query->setOperations)
@@ -649,10 +650,10 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
    }
 
    /*
-    * Similarly, GROUP BY guarantees uniqueness if all the grouped columns
-    * appear in colnos and operator semantics match.
+    * Similarly, GROUP BY without GROUPING SETS guarantees uniqueness if all
+    * the grouped columns appear in colnos and operator semantics match.
     */
-   if (query->groupClause)
+   if (query->groupClause && !query->groupingSets)
    {
        foreach(l, query->groupClause)
        {
@@ -668,6 +669,27 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
        if (l == NULL)          /* had matches for all? */
            return true;
    }
+   else if (query->groupingSets)
+   {
+       /*
+        * If we have grouping sets with expressions, we probably
+        * don't have uniqueness and analysis would be hard. Punt.
+        */
+       if (query->groupClause)
+           return false;
+
+       /*
+        * If we have no groupClause (therefore no grouping expressions),
+        * we might have one or many empty grouping sets. If there's just
+        * one, then we're returning only one row and are certainly unique.
+        * But otherwise, we know we're certainly not unique.
+        */
+       if (list_length(query->groupingSets) == 1
+           && ((GroupingSet *)linitial(query->groupingSets))->kind == GROUPING_SET_EMPTY)
+           return true;
+       else
+           return false;
+   }
    else
    {
        /*
index c809237283198ad4d29e6ddce1acd057371cf6bb..600f77fce51d7ec63bf0985765cd5d46678312eb 100644 (file)
@@ -1029,6 +1029,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
                                 numGroupCols,
                                 groupColIdx,
                                 groupOperators,
+                                NIL,
+                                NULL,
                                 numGroups,
                                 subplan);
    }
@@ -4380,6 +4382,7 @@ Agg *
 make_agg(PlannerInfo *root, List *tlist, List *qual,
         AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
         int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
+        List *groupingSets, int *chain_depth_p,
         long numGroups,
         Plan *lefttree)
 {
@@ -4389,6 +4392,7 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
    QualCost    qual_cost;
 
    node->aggstrategy = aggstrategy;
+   node->chain_depth = chain_depth_p ? *chain_depth_p : 0;
    node->numCols = numGroupCols;
    node->grpColIdx = grpColIdx;
    node->grpOperators = grpOperators;
@@ -4409,10 +4413,12 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
     * group otherwise.
     */
    if (aggstrategy == AGG_PLAIN)
-       plan->plan_rows = 1;
+       plan->plan_rows = groupingSets ? list_length(groupingSets) : 1;
    else
        plan->plan_rows = numGroups;
 
+   node->groupingSets = groupingSets;
+
    /*
     * We also need to account for the cost of evaluation of the qual (ie, the
     * HAVING clause) and the tlist.  Note that cost_qual_eval doesn't charge
@@ -4431,8 +4437,21 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
    }
    add_tlist_costs_to_plan(root, plan, tlist);
 
-   plan->qual = qual;
-   plan->targetlist = tlist;
+   if (aggstrategy == AGG_CHAINED)
+   {
+       Assert(!chain_depth_p);
+       plan->plan_rows = lefttree->plan_rows;
+       plan->plan_width = lefttree->plan_width;
+
+       /* supplied tlist is ignored, this is dummy */
+       plan->targetlist = lefttree->targetlist;
+       plan->qual = NULL;
+   }
+   else
+   {
+       plan->qual = qual;
+       plan->targetlist = tlist;
+   }
    plan->lefttree = lefttree;
    plan->righttree = NULL;
 
index af772a2c99333a8a9bf4ecf079122c500f0ef36c..f0e9c05a4520355a0592e954edbab68b96ec53fa 100644 (file)
@@ -96,7 +96,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
     * performs assorted processing related to these features between calling
     * preprocess_minmax_aggregates and optimize_minmax_aggregates.)
     */
-   if (parse->groupClause || parse->hasWindowFuncs)
+   if (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs)
        return;
 
    /*
index c80d45acaa95d1f75f287c8e6b19d6a106517da3..442e8e80260cf072df0581813ba7b4f1158ef718 100644 (file)
 #include "postgres.h"
 
 #include <limits.h>
+#include <math.h>
 
 #include "access/htup_details.h"
 #include "executor/executor.h"
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
 #endif
@@ -37,6 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
+#include "parser/parse_agg.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
@@ -65,6 +68,7 @@ typedef struct
 {
    List       *tlist;          /* preprocessed query targetlist */
    List       *activeWindows;  /* active windows, if any */
+   List       *groupClause;    /* overrides parse->groupClause */
 } standard_qp_extra;
 
 /* Local functions */
@@ -77,7 +81,9 @@ static double preprocess_limit(PlannerInfo *root,
                 double tuple_fraction,
                 int64 *offset_est, int64 *count_est);
 static bool limit_needed(Query *parse);
-static void preprocess_groupclause(PlannerInfo *root);
+static List *preprocess_groupclause(PlannerInfo *root, List *force);
+static List *extract_rollup_sets(List *groupingSets);
+static List *reorder_grouping_sets(List *groupingSets, List *sortclause);
 static void standard_qp_callback(PlannerInfo *root, void *extra);
 static bool choose_hashed_grouping(PlannerInfo *root,
                       double tuple_fraction, double limit_tuples,
@@ -319,6 +325,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
    root->append_rel_list = NIL;
    root->rowMarks = NIL;
    root->hasInheritedTarget = false;
+   root->groupColIdx = NULL;
+   root->grouping_map = NULL;
 
    root->hasRecursion = hasRecursion;
    if (hasRecursion)
@@ -545,7 +553,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 
        if (contain_agg_clause(havingclause) ||
            contain_volatile_functions(havingclause) ||
-           contain_subplans(havingclause))
+           contain_subplans(havingclause) ||
+           parse->groupingSets)
        {
            /* keep it in HAVING */
            newHaving = lappend(newHaving, havingclause);
@@ -1234,11 +1243,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        List       *sub_tlist;
        AttrNumber *groupColIdx = NULL;
        bool        need_tlist_eval = true;
-       standard_qp_extra qp_extra;
-       RelOptInfo *final_rel;
-       Path       *cheapest_path;
-       Path       *sorted_path;
-       Path       *best_path;
        long        numGroups = 0;
        AggClauseCosts agg_costs;
        int         numGroupCols;
@@ -1248,15 +1252,90 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        WindowFuncLists *wflists = NULL;
        List       *activeWindows = NIL;
        OnConflictExpr *onconfl;
+       int         maxref = 0;
+       List       *refmaps = NIL;
+       List       *rollup_lists = NIL;
+       List       *rollup_groupclauses = NIL;
+       standard_qp_extra qp_extra;
+       RelOptInfo *final_rel;
+       Path       *cheapest_path;
+       Path       *sorted_path;
+       Path       *best_path;
 
        MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
 
        /* A recursive query should always have setOperations */
        Assert(!root->hasRecursion);
 
-       /* Preprocess GROUP BY clause, if any */
+       /* Preprocess Grouping set, if any */
+       if (parse->groupingSets)
+           parse->groupingSets = expand_grouping_sets(parse->groupingSets, -1);
+
        if (parse->groupClause)
-           preprocess_groupclause(root);
+       {
+           ListCell   *lc;
+
+           foreach(lc, parse->groupClause)
+           {
+               SortGroupClause *gc = lfirst(lc);
+               if (gc->tleSortGroupRef > maxref)
+                   maxref = gc->tleSortGroupRef;
+           }
+       }
+
+       if (parse->groupingSets)
+       {
+           ListCell   *lc;
+           ListCell   *lc2;
+           ListCell   *lc_set;
+           List       *sets = extract_rollup_sets(parse->groupingSets);
+
+           foreach(lc_set, sets)
+           {
+               List   *current_sets = reorder_grouping_sets(lfirst(lc_set),
+                                                   (list_length(sets) == 1
+                                                    ? parse->sortClause
+                                                    : NIL));
+               List   *groupclause = preprocess_groupclause(root, linitial(current_sets));
+               int     ref = 0;
+               int    *refmap;
+
+               /*
+                * Now that we've pinned down an order for the groupClause for this
+                * list of grouping sets, remap the entries in the grouping sets
+                * from sortgrouprefs to plain indices into the groupClause.
+                */
+
+               refmap = palloc0(sizeof(int) * (maxref + 1));
+
+               foreach(lc, groupclause)
+               {
+                   SortGroupClause *gc = lfirst(lc);
+                   refmap[gc->tleSortGroupRef] = ++ref;
+               }
+
+               foreach(lc, current_sets)
+               {
+                   foreach(lc2, (List *) lfirst(lc))
+                   {
+                       Assert(refmap[lfirst_int(lc2)] > 0);
+                       lfirst_int(lc2) = refmap[lfirst_int(lc2)] - 1;
+                   }
+               }
+
+               rollup_lists = lcons(current_sets, rollup_lists);
+               rollup_groupclauses = lcons(groupclause, rollup_groupclauses);
+               refmaps = lcons(refmap, refmaps);
+           }
+       }
+       else
+       {
+           /* Preprocess GROUP BY clause, if any */
+           if (parse->groupClause)
+               parse->groupClause = preprocess_groupclause(root, NIL);
+           rollup_groupclauses = list_make1(parse->groupClause);
+       }
+
        numGroupCols = list_length(parse->groupClause);
 
        /* Preprocess targetlist */
@@ -1336,6 +1415,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
         * grouping/aggregation operations.
         */
        if (parse->groupClause ||
+           parse->groupingSets ||
            parse->distinctClause ||
            parse->hasAggs ||
            parse->hasWindowFuncs ||
@@ -1347,6 +1427,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        /* Set up data needed by standard_qp_callback */
        qp_extra.tlist = tlist;
        qp_extra.activeWindows = activeWindows;
+       qp_extra.groupClause = linitial(rollup_groupclauses);
 
        /*
         * Generate the best unsorted and presorted paths for this Query (but
@@ -1373,15 +1454,46 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
         * to describe the fraction of the underlying un-aggregated tuples
         * that will be fetched.
         */
+
        dNumGroups = 1;         /* in case not grouping */
 
        if (parse->groupClause)
        {
            List       *groupExprs;
 
-           groupExprs = get_sortgrouplist_exprs(parse->groupClause,
-                                                parse->targetList);
-           dNumGroups = estimate_num_groups(root, groupExprs, path_rows);
+           if (parse->groupingSets)
+           {
+               ListCell   *lc,
+                          *lc2;
+
+               dNumGroups = 0;
+
+               forboth(lc, rollup_groupclauses, lc2, rollup_lists)
+               {
+                   ListCell   *lc3;
+
+                   groupExprs = get_sortgrouplist_exprs(lfirst(lc),
+                                                        parse->targetList);
+
+                   foreach(lc3, lfirst(lc2))
+                   {
+                       List   *gset = lfirst(lc3);
+
+                       dNumGroups += estimate_num_groups(root,
+                                                         groupExprs,
+                                                         path_rows,
+                                                         &gset);
+                   }
+               }
+           }
+           else
+           {
+               groupExprs = get_sortgrouplist_exprs(parse->groupClause,
+                                                    parse->targetList);
+
+               dNumGroups = estimate_num_groups(root, groupExprs, path_rows,
+                                                NULL);
+           }
 
            /*
             * In GROUP BY mode, an absolute LIMIT is relative to the number
@@ -1392,6 +1504,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
            if (tuple_fraction >= 1.0)
                tuple_fraction /= dNumGroups;
 
+           if (list_length(rollup_lists) > 1)
+               tuple_fraction = 0.0;
+
            /*
             * If both GROUP BY and ORDER BY are specified, we will need two
             * levels of sort --- and, therefore, certainly need to read all
@@ -1407,14 +1522,17 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                                       root->group_pathkeys))
                tuple_fraction = 0.0;
        }
-       else if (parse->hasAggs || root->hasHavingQual)
+       else if (parse->hasAggs || root->hasHavingQual || parse->groupingSets)
        {
            /*
             * Ungrouped aggregate will certainly want to read all the tuples,
-            * and it will deliver a single result row (so leave dNumGroups
-            * set to 1).
+            * and it will deliver a single result row per grouping set (or 1
+            * if no grouping sets were explicitly given, in which case leave
+            * dNumGroups as-is)
             */
            tuple_fraction = 0.0;
+           if (parse->groupingSets)
+               dNumGroups = list_length(parse->groupingSets);
        }
        else if (parse->distinctClause)
        {
@@ -1429,7 +1547,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 
            distinctExprs = get_sortgrouplist_exprs(parse->distinctClause,
                                                    parse->targetList);
-           dNumGroups = estimate_num_groups(root, distinctExprs, path_rows);
+           dNumGroups = estimate_num_groups(root, distinctExprs, path_rows, NULL);
 
            /*
             * Adjust tuple_fraction the same way as for GROUP BY, too.
@@ -1512,13 +1630,24 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        {
            /*
             * If grouping, decide whether to use sorted or hashed grouping.
+            * If grouping sets are present, we can currently do only sorted
+            * grouping.
             */
-           use_hashed_grouping =
-               choose_hashed_grouping(root,
-                                      tuple_fraction, limit_tuples,
-                                      path_rows, path_width,
-                                      cheapest_path, sorted_path,
-                                      dNumGroups, &agg_costs);
+
+           if (parse->groupingSets)
+           {
+               use_hashed_grouping = false;
+           }
+           else
+           {
+               use_hashed_grouping =
+                   choose_hashed_grouping(root,
+                                          tuple_fraction, limit_tuples,
+                                          path_rows, path_width,
+                                          cheapest_path, sorted_path,
+                                          dNumGroups, &agg_costs);
+           }
+
            /* Also convert # groups to long int --- but 'ware overflow! */
            numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
        }
@@ -1584,7 +1713,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 
            /* Detect if we'll need an explicit sort for grouping */
            if (parse->groupClause && !use_hashed_grouping &&
-             !pathkeys_contained_in(root->group_pathkeys, current_pathkeys))
+               !pathkeys_contained_in(root->group_pathkeys, current_pathkeys))
            {
                need_sort_for_grouping = true;
 
@@ -1659,52 +1788,118 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                                                &agg_costs,
                                                numGroupCols,
                                                groupColIdx,
-                                   extract_grouping_ops(parse->groupClause),
+                                               extract_grouping_ops(parse->groupClause),
+                                               NIL,
+                                               NULL,
                                                numGroups,
                                                result_plan);
                /* Hashed aggregation produces randomly-ordered results */
                current_pathkeys = NIL;
            }
-           else if (parse->hasAggs)
+           else if (parse->hasAggs || (parse->groupingSets && parse->groupClause))
            {
-               /* Plain aggregate plan --- sort if needed */
-               AggStrategy aggstrategy;
+               int         chain_depth = 0;
 
-               if (parse->groupClause)
+               /*
+                * If we need multiple grouping nodes, start stacking them up;
+                * all except the last are chained.
+                */
+
+               do
                {
-                   if (need_sort_for_grouping)
+                   List       *groupClause = linitial(rollup_groupclauses);
+                   List       *gsets = rollup_lists ? linitial(rollup_lists) : NIL;
+                   int        *refmap = refmaps ? linitial(refmaps) : NULL;
+                   AttrNumber *new_grpColIdx = groupColIdx;
+                   ListCell   *lc;
+                   int         i;
+                   AggStrategy aggstrategy = AGG_CHAINED;
+
+                   if (groupClause)
+                   {
+                       if (gsets)
+                       {
+                           Assert(refmap);
+
+                           /*
+                            * need to remap groupColIdx, which has the column
+                            * indices for every clause in parse->groupClause
+                            * indexed by list position, to a local version for
+                            * this node which lists only the clauses included in
+                            * groupClause by position in that list. The refmap for
+                            * this node (indexed by sortgroupref) contains 0 for
+                            * clauses not present in this node's groupClause.
+                            */
+
+                           new_grpColIdx = palloc0(sizeof(AttrNumber) * list_length(linitial(gsets)));
+
+                           i = 0;
+                           foreach(lc, parse->groupClause)
+                           {
+                               int j = refmap[((SortGroupClause *)lfirst(lc))->tleSortGroupRef];
+                               if (j > 0)
+                                   new_grpColIdx[j - 1] = groupColIdx[i];
+                               ++i;
+                           }
+                       }
+
+                       if (need_sort_for_grouping)
+                       {
+                           result_plan = (Plan *)
+                               make_sort_from_groupcols(root,
+                                                        groupClause,
+                                                        new_grpColIdx,
+                                                        result_plan);
+                       }
+                       else
+                           need_sort_for_grouping = true;
+
+                       if (list_length(rollup_groupclauses) == 1)
+                       {
+                           aggstrategy = AGG_SORTED;
+
+                           /*
+                            * If there aren't any other chained aggregates, then
+                            * we didn't disturb the originally required input
+                            * sort order.
+                            */
+                           if (chain_depth == 0)
+                               current_pathkeys = root->group_pathkeys;
+                       }
+                       else
+                           current_pathkeys = NIL;
+                   }
+                   else
                    {
-                       result_plan = (Plan *)
-                           make_sort_from_groupcols(root,
-                                                    parse->groupClause,
-                                                    groupColIdx,
-                                                    result_plan);
-                       current_pathkeys = root->group_pathkeys;
+                       aggstrategy = AGG_PLAIN;
+                       current_pathkeys = NIL;
                    }
-                   aggstrategy = AGG_SORTED;
 
-                   /*
-                    * The AGG node will not change the sort ordering of its
-                    * groups, so current_pathkeys describes the result too.
-                    */
+                   result_plan = (Plan *) make_agg(root,
+                                                   tlist,
+                                                   (List *) parse->havingQual,
+                                                   aggstrategy,
+                                                   &agg_costs,
+                                                   gsets ? list_length(linitial(gsets)) : numGroupCols,
+                                                   new_grpColIdx,
+                                                   extract_grouping_ops(groupClause),
+                                                   gsets,
+                                                   (aggstrategy != AGG_CHAINED) ? &chain_depth : NULL,
+                                                   numGroups,
+                                                   result_plan);
+
+                   chain_depth += 1;
+
+                   if (refmap)
+                       pfree(refmap);
+                   if (rollup_lists)
+                       rollup_lists = list_delete_first(rollup_lists);
+                   if (refmaps)
+                       refmaps = list_delete_first(refmaps);
+
+                   rollup_groupclauses = list_delete_first(rollup_groupclauses);
                }
-               else
-               {
-                   aggstrategy = AGG_PLAIN;
-                   /* Result will be only one row anyway; no sort order */
-                   current_pathkeys = NIL;
-               }
-
-               result_plan = (Plan *) make_agg(root,
-                                               tlist,
-                                               (List *) parse->havingQual,
-                                               aggstrategy,
-                                               &agg_costs,
-                                               numGroupCols,
-                                               groupColIdx,
-                                   extract_grouping_ops(parse->groupClause),
-                                               numGroups,
-                                               result_plan);
+               while (rollup_groupclauses);
            }
            else if (parse->groupClause)
            {
@@ -1735,27 +1930,66 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                                                  result_plan);
                /* The Group node won't change sort ordering */
            }
-           else if (root->hasHavingQual)
+           else if (root->hasHavingQual || parse->groupingSets)
            {
+               int     nrows = list_length(parse->groupingSets);
+
                /*
-                * No aggregates, and no GROUP BY, but we have a HAVING qual.
+                * No aggregates, and no GROUP BY, but we have a HAVING qual or
+                * grouping sets (which by elimination of cases above must
+                * consist solely of empty grouping sets, since otherwise
+                * groupClause will be non-empty).
+                *
                 * This is a degenerate case in which we are supposed to emit
-                * either 0 or 1 row depending on whether HAVING succeeds.
-                * Furthermore, there cannot be any variables in either HAVING
-                * or the targetlist, so we actually do not need the FROM
-                * table at all!  We can just throw away the plan-so-far and
-                * generate a Result node.  This is a sufficiently unusual
-                * corner case that it's not worth contorting the structure of
-                * this routine to avoid having to generate the plan in the
-                * first place.
+                * either 0 or 1 row for each grouping set depending on whether
+                * HAVING succeeds.  Furthermore, there cannot be any variables
+                * in either HAVING or the targetlist, so we actually do not
+                * need the FROM table at all!  We can just throw away the
+                * plan-so-far and generate a Result node.  This is a
+                * sufficiently unusual corner case that it's not worth
+                * contorting the structure of this routine to avoid having to
+                * generate the plan in the first place.
                 */
                result_plan = (Plan *) make_result(root,
                                                   tlist,
                                                   parse->havingQual,
                                                   NULL);
+
+               /*
+                * Doesn't seem worthwhile writing code to cons up a
+                * generate_series or a values scan to emit multiple rows.
+                * Instead just clone the result in an Append.
+                */
+               if (nrows > 1)
+               {
+                   List   *plans = list_make1(result_plan);
+
+                   while (--nrows > 0)
+                       plans = lappend(plans, copyObject(result_plan));
+
+                   result_plan = (Plan *) make_append(plans, tlist);
+               }
            }
        }                       /* end of non-minmax-aggregate case */
 
+       /* Record grouping_map based on final groupColIdx, for setrefs */
+
+       if (parse->groupingSets)
+       {
+           AttrNumber *grouping_map = palloc0(sizeof(AttrNumber) * (maxref + 1));
+           ListCell   *lc;
+           int         i = 0;
+
+           foreach(lc, parse->groupClause)
+           {
+               SortGroupClause *gc = lfirst(lc);
+               grouping_map[gc->tleSortGroupRef] = groupColIdx[i++];
+           }
+
+           root->groupColIdx = groupColIdx;
+           root->grouping_map = grouping_map;
+       }
+
        /*
         * Since each window function could require a different sort order, we
         * stack up a WindowAgg node for each window, with sort steps between
@@ -1918,7 +2152,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
         * result was already mostly unique).  If not, use the number of
         * distinct-groups calculated previously.
         */
-       if (parse->groupClause || root->hasHavingQual || parse->hasAggs)
+       if (parse->groupClause || parse->groupingSets || root->hasHavingQual || parse->hasAggs)
            dNumDistinctRows = result_plan->plan_rows;
        else
            dNumDistinctRows = dNumGroups;
@@ -1959,6 +2193,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                                 extract_grouping_cols(parse->distinctClause,
                                                    result_plan->targetlist),
                                 extract_grouping_ops(parse->distinctClause),
+                                           NIL,
+                                           NULL,
                                            numDistinctRows,
                                            result_plan);
            /* Hashed aggregation produces randomly-ordered results */
@@ -2623,19 +2859,38 @@ limit_needed(Query *parse)
  *
  * Note: we need no comparable processing of the distinctClause because
  * the parser already enforced that that matches ORDER BY.
+ *
+ * For grouping sets, the order of items is instead forced to agree with that
+ * of the grouping set (and items not in the grouping set are skipped). The
+ * work of sorting the order of grouping set elements to match the ORDER BY if
+ * possible is done elsewhere.
  */
-static void
-preprocess_groupclause(PlannerInfo *root)
+static List *
+preprocess_groupclause(PlannerInfo *root, List *force)
 {
    Query      *parse = root->parse;
-   List       *new_groupclause;
+   List       *new_groupclause = NIL;
    bool        partial_match;
    ListCell   *sl;
    ListCell   *gl;
 
+   /* For grouping sets, we need to force the ordering */
+   if (force)
+   {
+       foreach(sl, force)
+       {
+           Index ref = lfirst_int(sl);
+           SortGroupClause *cl = get_sortgroupref_clause(ref, parse->groupClause);
+
+           new_groupclause = lappend(new_groupclause, cl);
+       }
+
+       return new_groupclause;
+   }
+
    /* If no ORDER BY, nothing useful to do here */
    if (parse->sortClause == NIL)
-       return;
+       return parse->groupClause;
 
    /*
     * Scan the ORDER BY clause and construct a list of matching GROUP BY
@@ -2643,7 +2898,6 @@ preprocess_groupclause(PlannerInfo *root)
     *
     * This code assumes that the sortClause contains no duplicate items.
     */
-   new_groupclause = NIL;
    foreach(sl, parse->sortClause)
    {
        SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
@@ -2667,7 +2921,7 @@ preprocess_groupclause(PlannerInfo *root)
 
    /* If no match at all, no point in reordering GROUP BY */
    if (new_groupclause == NIL)
-       return;
+       return parse->groupClause;
 
    /*
     * Add any remaining GROUP BY items to the new list, but only if we were
@@ -2684,15 +2938,446 @@ preprocess_groupclause(PlannerInfo *root)
        if (list_member_ptr(new_groupclause, gc))
            continue;           /* it matched an ORDER BY item */
        if (partial_match)
-           return;             /* give up, no common sort possible */
+           return parse->groupClause;  /* give up, no common sort possible */
        if (!OidIsValid(gc->sortop))
-           return;             /* give up, GROUP BY can't be sorted */
+           return parse->groupClause;  /* give up, GROUP BY can't be sorted */
        new_groupclause = lappend(new_groupclause, gc);
    }
 
    /* Success --- install the rearranged GROUP BY list */
    Assert(list_length(parse->groupClause) == list_length(new_groupclause));
-   parse->groupClause = new_groupclause;
+   return new_groupclause;
+}
+
+
+/*
+ * We want to produce the absolute minimum possible number of lists here to
+ * avoid excess sorts. Fortunately, there is an algorithm for this; the problem
+ * of finding the minimal partition of a poset into chains (which is what we
+ * need, taking the list of grouping sets as a poset ordered by set inclusion)
+ * can be mapped to the problem of finding the maximum cardinality matching on
+ * a bipartite graph, which is solvable in polynomial time with a worst case of
+ * no worse than O(n^2.5) and usually much better. Since our N is at most 4096,
+ * we don't need to consider fallbacks to heuristic or approximate methods.
+ * (Planning time for a 12-d cube is under half a second on my modest system
+ * even with optimization off and assertions on.)
+ *
+ * We use the Hopcroft-Karp algorithm for the graph matching; it seems to work
+ * well enough for our purposes.  This implementation is based on pseudocode
+ * found at:
+ *
+ * http://en.wikipedia.org/w/index.php?title=Hopcroft%E2%80%93Karp_algorithm&oldid=593898016
+ *
+ * This implementation uses the same indices for elements of U and V (the two
+ * halves of the graph) because in our case they are always the same size, and
+ * we always know whether an index represents a u or a v. Index 0 is reserved
+ * for the NIL node.
+ */
+
+struct hk_state
+{
+   int         graph_size;     /* size of half the graph plus NIL node */
+   int         matching;
+   short     **adjacency;      /* adjacency[u] = [n, v1,v2,v3,...,vn] */
+   short      *pair_uv;        /* pair_uv[u] -> v */
+   short      *pair_vu;        /* pair_vu[v] -> u */
+   float      *distance;       /* distance[u], float so we can have +inf */
+   short      *queue;          /* queue storage for breadth search */
+};
+
+static bool
+hk_breadth_search(struct hk_state *state)
+{
+   int         gsize = state->graph_size;
+   short      *queue = state->queue;
+   float      *distance = state->distance;
+   int         qhead = 0;      /* we never enqueue any node more than once */
+   int         qtail = 0;      /* so don't have to worry about wrapping */
+   int         u;
+
+   distance[0] = INFINITY;
+
+   for (u = 1; u < gsize; ++u)
+   {
+       if (state->pair_uv[u] == 0)
+       {
+           distance[u] = 0;
+           queue[qhead++] = u;
+       }
+       else
+           distance[u] = INFINITY;
+   }
+
+   while (qtail < qhead)
+   {
+       u = queue[qtail++];
+
+       if (distance[u] < distance[0])
+       {
+           short  *u_adj = state->adjacency[u];
+           int     i = u_adj ? u_adj[0] : 0;
+
+           for (; i > 0; --i)
+           {
+               int u_next = state->pair_vu[u_adj[i]];
+
+               if (isinf(distance[u_next]))
+               {
+                   distance[u_next] = 1 + distance[u];
+                   queue[qhead++] = u_next;
+                   Assert(qhead <= gsize+1);
+               }
+           }
+       }
+   }
+
+   return !isinf(distance[0]);
+}
+
+static bool
+hk_depth_search(struct hk_state *state, int u, int depth)
+{
+   float      *distance = state->distance;
+   short      *pair_uv = state->pair_uv;
+   short      *pair_vu = state->pair_vu;
+   short      *u_adj = state->adjacency[u];
+   int         i = u_adj ? u_adj[0] : 0;
+
+   if (u == 0)
+       return true;
+
+   if ((depth % 8) == 0)
+       check_stack_depth();
+
+   for (; i > 0; --i)
+   {
+       int     v = u_adj[i];
+
+       if (distance[pair_vu[v]] == distance[u] + 1)
+       {
+           if (hk_depth_search(state, pair_vu[v], depth+1))
+           {
+               pair_vu[v] = u;
+               pair_uv[u] = v;
+               return true;
+           }
+       }
+   }
+
+   distance[u] = INFINITY;
+   return false;
+}
+
+static struct hk_state *
+hk_match(int graph_size, short **adjacency)
+{
+   struct hk_state *state = palloc(sizeof(struct hk_state));
+
+   state->graph_size = graph_size;
+   state->matching = 0;
+   state->adjacency = adjacency;
+   state->pair_uv = palloc0(graph_size * sizeof(short));
+   state->pair_vu = palloc0(graph_size * sizeof(short));
+   state->distance = palloc(graph_size * sizeof(float));
+   state->queue = palloc((graph_size + 2) * sizeof(short));
+
+   while (hk_breadth_search(state))
+   {
+       int     u;
+
+       for (u = 1; u < graph_size; ++u)
+           if (state->pair_uv[u] == 0)
+               if (hk_depth_search(state, u, 1))
+                   state->matching++;
+
+       CHECK_FOR_INTERRUPTS();     /* just in case */
+   }
+
+   return state;
+}
+
+static void
+hk_free(struct hk_state *state)
+{
+   /* adjacency matrix is treated as owned by the caller */
+   pfree(state->pair_uv);
+   pfree(state->pair_vu);
+   pfree(state->distance);
+   pfree(state->queue);
+   pfree(state);
+}
+
+/*
+ * Extract lists of grouping sets that can be implemented using a single
+ * rollup-type aggregate pass each. Returns a list of lists of grouping sets.
+ *
+ * Input must be sorted with smallest sets first. Result has each sublist
+ * sorted with smallest sets first.
+ */
+
+static List *
+extract_rollup_sets(List *groupingSets)
+{
+   int         num_sets_raw = list_length(groupingSets);
+   int         num_empty = 0;
+   int         num_sets = 0;       /* distinct sets */
+   int         num_chains = 0;
+   List       *result = NIL;
+   List      **results;
+   List      **orig_sets;
+   Bitmapset **set_masks;
+   int        *chains;
+   short     **adjacency;
+   short      *adjacency_buf;
+   struct hk_state *state;
+   int         i;
+   int         j;
+   int         j_size;
+   ListCell   *lc1 = list_head(groupingSets);
+   ListCell   *lc;
+
+   /*
+    * Start by stripping out empty sets.  The algorithm doesn't require this,
+    * but the planner currently needs all empty sets to be returned in the
+    * first list, so we strip them here and add them back after.
+    */
+
+   while (lc1 && lfirst(lc1) == NIL)
+   {
+       ++num_empty;
+       lc1 = lnext(lc1);
+   }
+
+   /* bail out now if it turns out that all we had were empty sets. */
+
+   if (!lc1)
+       return list_make1(groupingSets);
+
+   /*
+    * We don't strictly need to remove duplicate sets here, but if we
+    * don't, they tend to become scattered through the result, which is
+    * a bit confusing (and irritating if we ever decide to optimize them
+    * out). So we remove them here and add them back after.
+    *
+    * For each non-duplicate set, we fill in the following:
+    *
+    * orig_sets[i] = list of the original set lists
+    * set_masks[i] = bitmapset for testing inclusion
+    * adjacency[i] = array [n, v1, v2, ... vn] of adjacency indices
+    *
+    * chains[i] will be the result group this set is assigned to.
+    *
+    * We index all of these from 1 rather than 0 because it is convenient
+    * to leave 0 free for the NIL node in the graph algorithm.
+    */
+
+   orig_sets = palloc0((num_sets_raw + 1) * sizeof(List*));
+   set_masks = palloc0((num_sets_raw + 1) * sizeof(Bitmapset *));
+   adjacency = palloc0((num_sets_raw + 1) * sizeof(short *));
+   adjacency_buf = palloc((num_sets_raw + 1) * sizeof(short));
+
+   j_size = 0;
+   j = 0;
+   i = 1;
+
+   for_each_cell(lc, lc1)
+   {
+       List       *candidate = lfirst(lc);
+       Bitmapset  *candidate_set = NULL;
+       ListCell   *lc2;
+       int         dup_of = 0;
+
+       foreach(lc2, candidate)
+       {
+           candidate_set = bms_add_member(candidate_set, lfirst_int(lc2));
+       }
+
+       /* we can only be a dup if we're the same length as a previous set */
+       if (j_size == list_length(candidate))
+       {
+           int     k;
+           for (k = j; k < i; ++k)
+           {
+               if (bms_equal(set_masks[k], candidate_set))
+               {
+                   dup_of = k;
+                   break;
+               }
+           }
+       }
+       else if (j_size < list_length(candidate))
+       {
+           j_size = list_length(candidate);
+           j = i;
+       }
+
+       if (dup_of > 0)
+       {
+           orig_sets[dup_of] = lappend(orig_sets[dup_of], candidate);
+           bms_free(candidate_set);
+       }
+       else
+       {
+           int     k;
+           int     n_adj = 0;
+
+           orig_sets[i] = list_make1(candidate);
+           set_masks[i] = candidate_set;
+
+           /* fill in adjacency list; no need to compare equal-size sets */
+
+           for (k = j - 1; k > 0; --k)
+           {
+               if (bms_is_subset(set_masks[k], candidate_set))
+                   adjacency_buf[++n_adj] = k;
+           }
+
+           if (n_adj > 0)
+           {
+               adjacency_buf[0] = n_adj;
+               adjacency[i] = palloc((n_adj + 1) * sizeof(short));
+               memcpy(adjacency[i], adjacency_buf, (n_adj + 1) * sizeof(short));
+           }
+           else
+               adjacency[i] = NULL;
+
+           ++i;
+       }
+   }
+
+   num_sets = i - 1;
+
+   /*
+    * Apply the matching algorithm to do the work.
+    */
+
+   state = hk_match(num_sets + 1, adjacency);
+
+   /*
+    * Now, the state->pair* fields have the info we need to assign sets to
+    * chains. Two sets (u,v) belong to the same chain if pair_uv[u] = v or
+    * pair_vu[v] = u (both will be true, but we check both so that we can do
+    * it in one pass)
+    */
+
+   chains = palloc0((num_sets + 1) * sizeof(int));
+
+   for (i = 1; i <= num_sets; ++i)
+   {
+       int u = state->pair_vu[i];
+       int v = state->pair_uv[i];
+
+       if (u > 0 && u < i)
+           chains[i] = chains[u];
+       else if (v > 0 && v < i)
+           chains[i] = chains[v];
+       else
+           chains[i] = ++num_chains;
+   }
+
+   /* build result lists. */
+
+   results = palloc0((num_chains + 1) * sizeof(List*));
+
+   for (i = 1; i <= num_sets; ++i)
+   {
+       int c = chains[i];
+
+       Assert(c > 0);
+
+       results[c] = list_concat(results[c], orig_sets[i]);
+   }
+
+   /* push any empty sets back on the first list. */
+
+   while (num_empty-- > 0)
+       results[1] = lcons(NIL, results[1]);
+
+   /* make result list */
+
+   for (i = 1; i <= num_chains; ++i)
+       result = lappend(result, results[i]);
+
+   /*
+    * Free all the things.
+    *
+    * (This is over-fussy for small sets but for large sets we could have tied
+    * up a nontrivial amount of memory.)
+    */
+
+   hk_free(state);
+   pfree(results);
+   pfree(chains);
+   for (i = 1; i <= num_sets; ++i)
+       if (adjacency[i])
+           pfree(adjacency[i]);
+   pfree(adjacency);
+   pfree(adjacency_buf);
+   pfree(orig_sets);
+   for (i = 1; i <= num_sets; ++i)
+       bms_free(set_masks[i]);
+   pfree(set_masks);
+
+   return result;
+}
+
+/*
+ * Reorder the elements of a list of grouping sets such that they have correct
+ * prefix relationships.
+ *
+ * The input must be ordered with smallest sets first; the result is returned
+ * with largest sets first.
+ *
+ * If we're passed in a sortclause, we follow its order of columns to the
+ * extent possible, to minimize the chance that we add unnecessary sorts.
+ * (We're trying here to ensure that GROUPING SETS ((a,b,c),(c)) ORDER BY c,b,a
+ * gets implemented in one pass.)
+ */
+static List *
+reorder_grouping_sets(List *groupingsets, List *sortclause)
+{
+   ListCell   *lc;
+   ListCell   *lc2;
+   List       *previous = NIL;
+   List       *result = NIL;
+
+   foreach(lc, groupingsets)
+   {
+       List   *candidate = lfirst(lc);
+       List   *new_elems = list_difference_int(candidate, previous);
+
+       if (list_length(new_elems) > 0)
+       {
+           while (list_length(sortclause) > list_length(previous))
+           {
+               SortGroupClause *sc = list_nth(sortclause, list_length(previous));
+               int ref = sc->tleSortGroupRef;
+               if (list_member_int(new_elems, ref))
+               {
+                   previous = lappend_int(previous, ref);
+                   new_elems = list_delete_int(new_elems, ref);
+               }
+               else
+               {
+                   /* diverged from the sortclause; give up on it */
+                   sortclause = NIL;
+                   break;
+               }
+           }
+
+           foreach(lc2, new_elems)
+           {
+               previous = lappend_int(previous, lfirst_int(lc2));
+           }
+       }
+
+       result = lcons(list_copy(previous), result);
+       list_free(new_elems);
+   }
+
+   list_free(previous);
+
+   return result;
 }
 
 /*
@@ -2711,11 +3396,11 @@ standard_qp_callback(PlannerInfo *root, void *extra)
     * sortClause is certainly sort-able, but GROUP BY and DISTINCT might not
     * be, in which case we just leave their pathkeys empty.
     */
-   if (parse->groupClause &&
-       grouping_is_sortable(parse->groupClause))
+   if (qp_extra->groupClause &&
+       grouping_is_sortable(qp_extra->groupClause))
        root->group_pathkeys =
            make_pathkeys_for_sortclauses(root,
-                                         parse->groupClause,
+                                         qp_extra->groupClause,
                                          tlist);
    else
        root->group_pathkeys = NIL;
@@ -3140,7 +3825,7 @@ make_subplanTargetList(PlannerInfo *root,
     * If we're not grouping or aggregating, there's nothing to do here;
     * query_planner should receive the unmodified target list.
     */
-   if (!parse->hasAggs && !parse->groupClause && !root->hasHavingQual &&
+   if (!parse->hasAggs && !parse->groupClause && !parse->groupingSets && !root->hasHavingQual &&
        !parse->hasWindowFuncs)
    {
        *need_tlist_eval = true;
index fac51c9147470556bf23a063fc375e5f52f715ce..73a9168e57bac012dd6583d9794bb55680d1f439 100644 (file)
@@ -67,6 +67,12 @@ typedef struct
    int         rtoffset;
 } fix_upper_expr_context;
 
+typedef struct
+{
+   PlannerInfo *root;
+   Bitmapset   *groupedcols;
+} set_group_vars_context;
+
 /*
  * Check if a Const node is a regclass value.  We accept plain OID too,
  * since a regclass Const will get folded to that type if it's an argument
@@ -139,6 +145,8 @@ static List *set_returning_clause_references(PlannerInfo *root,
 static bool fix_opfuncids_walker(Node *node, void *context);
 static bool extract_query_dependencies_walker(Node *node,
                                  PlannerInfo *context);
+static void set_group_vars(PlannerInfo *root, Agg *agg);
+static Node *set_group_vars_mutator(Node *node, set_group_vars_context *context);
 
 
 /*****************************************************************************
@@ -645,6 +653,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
            }
            break;
        case T_Agg:
+           if (((Agg *) plan)->aggstrategy == AGG_CHAINED)
+           {
+               /* chained agg does not evaluate tlist */
+               set_dummy_tlist_references(plan, rtoffset);
+           }
+           else
+           {
+               set_upper_references(root, plan, rtoffset);
+               set_group_vars(root, (Agg *) plan);
+           }
+           break;
        case T_Group:
            set_upper_references(root, plan, rtoffset);
            break;
@@ -1215,6 +1234,7 @@ copyVar(Var *var)
  * We must look up operator opcode info for OpExpr and related nodes,
  * add OIDs from regclass Const nodes into root->glob->relationOids, and
  * add catalog TIDs for user-defined functions into root->glob->invalItems.
+ * We also fill in column index lists for GROUPING() expressions.
  *
  * We assume it's okay to update opcode info in-place.  So this could possibly
  * scribble on the planner's input data structures, but it's OK.
@@ -1278,6 +1298,31 @@ fix_expr_common(PlannerInfo *root, Node *node)
                lappend_oid(root->glob->relationOids,
                            DatumGetObjectId(con->constvalue));
    }
+   else if (IsA(node, GroupingFunc))
+   {
+       GroupingFunc *g = (GroupingFunc *) node;
+       AttrNumber *refmap = root->grouping_map;
+
+       /* If there are no grouping sets, we don't need this. */
+
+       Assert(refmap || g->cols == NIL);
+
+       if (refmap)
+       {
+           ListCell   *lc;
+           List       *cols = NIL;
+
+           foreach(lc, g->refs)
+           {
+               cols = lappend_int(cols, refmap[lfirst_int(lc)]);
+           }
+
+           Assert(!g->cols || equal(cols, g->cols));
+
+           if (!g->cols)
+               g->cols = cols;
+       }
+   }
 }
 
 /*
@@ -1405,6 +1450,98 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
                                  (void *) context);
 }
 
+
+/*
+ * set_group_vars
+ *    Modify any Var references in the target list of a non-trivial
+ *    (i.e. contains grouping sets) Agg node to use GroupedVar instead,
+ *    which will conditionally replace them with nulls at runtime.
+ */
+static void
+set_group_vars(PlannerInfo *root, Agg *agg)
+{
+   set_group_vars_context context;
+   AttrNumber *groupColIdx = root->groupColIdx;
+   int         numCols = list_length(root->parse->groupClause);
+   int         i;
+   Bitmapset  *cols = NULL;
+
+   if (!agg->groupingSets)
+       return;
+
+   if (!groupColIdx)
+   {
+       Assert(numCols == agg->numCols);
+       groupColIdx = agg->grpColIdx;
+   }
+
+   context.root = root;
+
+   for (i = 0; i < numCols; ++i)
+       cols = bms_add_member(cols, groupColIdx[i]);
+
+   context.groupedcols = cols;
+
+   agg->plan.targetlist = (List *) set_group_vars_mutator((Node *) agg->plan.targetlist,
+                                                          &context);
+   agg->plan.qual = (List *) set_group_vars_mutator((Node *) agg->plan.qual,
+                                                    &context);
+}
+
+static Node *
+set_group_vars_mutator(Node *node, set_group_vars_context *context)
+{
+   if (node == NULL)
+       return NULL;
+   if (IsA(node, Var))
+   {
+       Var *var = (Var *) node;
+
+       if (var->varno == OUTER_VAR
+           && bms_is_member(var->varattno, context->groupedcols))
+       {
+           var = copyVar(var);
+           var->xpr.type = T_GroupedVar;
+       }
+
+       return (Node *) var;
+   }
+   else if (IsA(node, Aggref))
+   {
+       /*
+        * don't recurse into the arguments or filter of Aggrefs, since they
+        * see the values prior to grouping.  But do recurse into direct args
+        * if any.
+        */
+
+       if (((Aggref *)node)->aggdirectargs != NIL)
+       {
+           Aggref *newnode = palloc(sizeof(Aggref));
+
+           memcpy(newnode, node, sizeof(Aggref));
+
+           newnode->aggdirectargs
+               = (List *) expression_tree_mutator((Node *) newnode->aggdirectargs,
+                                                  set_group_vars_mutator,
+                                                  (void *) context);
+
+           return (Node *) newnode;
+       }
+
+       return node;
+   }
+   else if (IsA(node, GroupingFunc))
+   {
+       /*
+        * GroupingFuncs don't see the values at all.
+        */
+       return node;
+   }
+   return expression_tree_mutator(node, set_group_vars_mutator,
+                                  (void *) context);
+}
+
+
 /*
  * set_join_references
  *   Modify the target list and quals of a join node to reference its
index afccee53acf562bf5006f7c63ed89ff6dd192ac5..cac73b436b5214a9b049c3776cf2510eb3e7aa7c 100644 (file)
@@ -79,7 +79,8 @@ static Node *process_sublinks_mutator(Node *node,
 static Bitmapset *finalize_plan(PlannerInfo *root,
              Plan *plan,
              Bitmapset *valid_params,
-             Bitmapset *scan_params);
+             Bitmapset *scan_params,
+             Agg *agg_chain_head);
 static bool finalize_primnode(Node *node, finalize_primnode_context *context);
 
 
@@ -335,6 +336,48 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg)
    return retval;
 }
 
+/*
+ * Generate a Param node to replace the given GroupingFunc expression which is
+ * expected to have agglevelsup > 0 (ie, it is not local).
+ */
+static Param *
+replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
+{
+   Param      *retval;
+   PlannerParamItem *pitem;
+   Index       levelsup;
+
+   Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level);
+
+   /* Find the query level the GroupingFunc belongs to */
+   for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--)
+       root = root->parent_root;
+
+   /*
+    * It does not seem worthwhile to try to match duplicate outer aggs. Just
+    * make a new slot every time.
+    */
+   grp = (GroupingFunc *) copyObject(grp);
+   IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0);
+   Assert(grp->agglevelsup == 0);
+
+   pitem = makeNode(PlannerParamItem);
+   pitem->item = (Node *) grp;
+   pitem->paramId = root->glob->nParamExec++;
+
+   root->plan_params = lappend(root->plan_params, pitem);
+
+   retval = makeNode(Param);
+   retval->paramkind = PARAM_EXEC;
+   retval->paramid = pitem->paramId;
+   retval->paramtype = exprType((Node *) grp);
+   retval->paramtypmod = -1;
+   retval->paramcollid = InvalidOid;
+   retval->location = grp->location;
+
+   return retval;
+}
+
 /*
  * Generate a new Param node that will not conflict with any other.
  *
@@ -1494,14 +1537,16 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query)
 {
    /*
     * We don't try to simplify at all if the query uses set operations,
-    * aggregates, modifying CTEs, HAVING, OFFSET, or FOR UPDATE/SHARE; none
-    * of these seem likely in normal usage and their possible effects are
-    * complex.  (Note: we could ignore an "OFFSET 0" clause, but that
-    * traditionally is used as an optimization fence, so we don't.)
+    * aggregates, grouping sets, modifying CTEs, HAVING, OFFSET, or FOR
+    * UPDATE/SHARE; none of these seem likely in normal usage and their
+    * possible effects are complex.  (Note: we could ignore an "OFFSET 0"
+    * clause, but that traditionally is used as an optimization fence, so we
+    * don't.)
     */
    if (query->commandType != CMD_SELECT ||
        query->setOperations ||
        query->hasAggs ||
+       query->groupingSets ||
        query->hasWindowFuncs ||
        query->hasModifyingCTE ||
        query->havingQual ||
@@ -1851,6 +1896,11 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
        if (((Aggref *) node)->agglevelsup > 0)
            return (Node *) replace_outer_agg(root, (Aggref *) node);
    }
+   if (IsA(node, GroupingFunc))
+   {
+       if (((GroupingFunc *) node)->agglevelsup > 0)
+           return (Node *) replace_outer_grouping(root, (GroupingFunc *) node);
+   }
    return expression_tree_mutator(node,
                                   replace_correlation_vars_mutator,
                                   (void *) root);
@@ -2081,7 +2131,7 @@ SS_finalize_plan(PlannerInfo *root, Plan *plan, bool attach_initplans)
    /*
     * Now recurse through plan tree.
     */
-   (void) finalize_plan(root, plan, valid_params, NULL);
+   (void) finalize_plan(root, plan, valid_params, NULL, NULL);
 
    bms_free(valid_params);
 
@@ -2132,7 +2182,7 @@ SS_finalize_plan(PlannerInfo *root, Plan *plan, bool attach_initplans)
  */
 static Bitmapset *
 finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
-             Bitmapset *scan_params)
+             Bitmapset *scan_params, Agg *agg_chain_head)
 {
    finalize_primnode_context context;
    int         locally_added_param;
@@ -2353,7 +2403,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
                                        finalize_plan(root,
                                                      (Plan *) lfirst(l),
                                                      valid_params,
-                                                     scan_params));
+                                                     scan_params,
+                                                     NULL));
                }
            }
            break;
@@ -2369,7 +2420,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
                                        finalize_plan(root,
                                                      (Plan *) lfirst(l),
                                                      valid_params,
-                                                     scan_params));
+                                                     scan_params,
+                                                     NULL));
                }
            }
            break;
@@ -2385,7 +2437,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
                                        finalize_plan(root,
                                                      (Plan *) lfirst(l),
                                                      valid_params,
-                                                     scan_params));
+                                                     scan_params,
+                                                     NULL));
                }
            }
            break;
@@ -2401,7 +2454,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
                                        finalize_plan(root,
                                                      (Plan *) lfirst(l),
                                                      valid_params,
-                                                     scan_params));
+                                                     scan_params,
+                                                     NULL));
                }
            }
            break;
@@ -2417,7 +2471,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
                                        finalize_plan(root,
                                                      (Plan *) lfirst(l),
                                                      valid_params,
-                                                     scan_params));
+                                                     scan_params,
+                                                     NULL));
                }
            }
            break;
@@ -2484,8 +2539,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
                              &context);
            break;
 
-       case T_Hash:
        case T_Agg:
+           {
+               Agg    *agg = (Agg *) plan;
+
+               if (agg->aggstrategy == AGG_CHAINED)
+               {
+                   Assert(agg_chain_head);
+
+                   /*
+                    * our real tlist and qual are the ones in the chain head,
+                    * not the local ones which are dummy for passthrough.
+                    * Fortunately we can call finalize_primnode more than
+                    * once.
+                    */
+
+                   finalize_primnode((Node *) agg_chain_head->plan.targetlist, &context);
+                   finalize_primnode((Node *) agg_chain_head->plan.qual, &context);
+               }
+               else if (agg->chain_depth > 0)
+                   agg_chain_head = agg;
+           }
+           break;
+
+       case T_Hash:
        case T_Material:
        case T_Sort:
        case T_Unique:
@@ -2502,7 +2579,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
    child_params = finalize_plan(root,
                                 plan->lefttree,
                                 valid_params,
-                                scan_params);
+                                scan_params,
+                                agg_chain_head);
    context.paramids = bms_add_members(context.paramids, child_params);
 
    if (nestloop_params)
@@ -2511,7 +2589,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
        child_params = finalize_plan(root,
                                     plan->righttree,
                                     bms_union(nestloop_params, valid_params),
-                                    scan_params);
+                                    scan_params,
+                                    agg_chain_head);
        /* ... and they don't count as parameters used at my level */
        child_params = bms_difference(child_params, nestloop_params);
        bms_free(nestloop_params);
@@ -2522,7 +2601,8 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
        child_params = finalize_plan(root,
                                     plan->righttree,
                                     valid_params,
-                                    scan_params);
+                                    scan_params,
+                                    agg_chain_head);
    }
    context.paramids = bms_add_members(context.paramids, child_params);
 
index 4f0dc80d025341bb69755ab5eea75db95be8224e..92b0562843458b403517d2c008c9db5cd26a1f79 100644 (file)
@@ -1412,6 +1412,7 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
    if (subquery->hasAggs ||
        subquery->hasWindowFuncs ||
        subquery->groupClause ||
+       subquery->groupingSets ||
        subquery->havingQual ||
        subquery->sortClause ||
        subquery->distinctClause ||
index 5859748df8eeda5812ad9674a3146cca13d08846..2a85c46ddf33bae3278ed933e9e20068a27ecc2f 100644 (file)
@@ -268,13 +268,15 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
         */
        if (pNumGroups)
        {
-           if (subquery->groupClause || subquery->distinctClause ||
+           if (subquery->groupClause || subquery->groupingSets ||
+               subquery->distinctClause ||
                subroot->hasHavingQual || subquery->hasAggs)
                *pNumGroups = subplan->plan_rows;
            else
                *pNumGroups = estimate_num_groups(subroot,
                                get_tlist_exprs(subquery->targetList, false),
-                                                 subplan->plan_rows);
+                                                 subplan->plan_rows,
+                                                 NULL);
        }
 
        /*
@@ -771,6 +773,8 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
                                 extract_grouping_cols(groupList,
                                                       plan->targetlist),
                                 extract_grouping_ops(groupList),
+                                NIL,
+                                NULL,
                                 numGroups,
                                 plan);
        /* Hashed aggregation produces randomly-ordered results */
index 480114d92b68cd1a481173bd8764ab1746294d2f..86585c58ee7b1876919ed9d48a6beb1772a27c16 100644 (file)
@@ -4353,6 +4353,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
        querytree->jointree->fromlist ||
        querytree->jointree->quals ||
        querytree->groupClause ||
+       querytree->groupingSets ||
        querytree->havingQual ||
        querytree->windowClause ||
        querytree->distinctClause ||
index faca30b322d530b512c4404bfdff0a9fd5cdca16..9190f8497a3cf234c4402537a8e737b17856499b 100644 (file)
@@ -1194,7 +1194,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
    /* Estimate number of output rows */
    pathnode->path.rows = estimate_num_groups(root,
                                              sjinfo->semi_rhs_exprs,
-                                             rel->rows);
+                                             rel->rows,
+                                             NULL);
    numCols = list_length(sjinfo->semi_rhs_exprs);
 
    if (sjinfo->semi_can_btree)
index a1a504b24d6c15408831d93818362c22205a9ef3..f702b8c442dd16a78a6129b6338bb74632883631 100644 (file)
@@ -394,6 +394,28 @@ get_sortgrouplist_exprs(List *sgClauses, List *targetList)
  * functions just above, and they don't seem to deserve their own file.
  *****************************************************************************/
 
+/*
+ * get_sortgroupref_clause
+ *     Find the SortGroupClause matching the given SortGroupRef index,
+ *     and return it.
+ */
+SortGroupClause *
+get_sortgroupref_clause(Index sortref, List *clauses)
+{
+   ListCell   *l;
+
+   foreach(l, clauses)
+   {
+       SortGroupClause *cl = (SortGroupClause *) lfirst(l);
+
+       if (cl->tleSortGroupRef == sortref)
+           return cl;
+   }
+
+   elog(ERROR, "ORDER/GROUP BY expression not found in list");
+   return NULL;                /* keep compiler quiet */
+}
+
 /*
  * extract_grouping_ops - make an array of the equality operator OIDs
  *     for a SortGroupClause list
index 8f864323611d2ce15e0c1a8ca5bb25a1705db8de..0f25539d124086467535de117658827a66fb2c7a 100644 (file)
@@ -564,6 +564,30 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
                break;
        }
    }
+   else if (IsA(node, GroupingFunc))
+   {
+       if (((GroupingFunc *) node)->agglevelsup != 0)
+           elog(ERROR, "Upper-level GROUPING found where not expected");
+       switch (context->aggbehavior)
+       {
+           case PVC_REJECT_AGGREGATES:
+               elog(ERROR, "GROUPING found where not expected");
+               break;
+           case PVC_INCLUDE_AGGREGATES:
+               context->varlist = lappend(context->varlist, node);
+               /* we do NOT descend into the contained expression */
+               return false;
+           case PVC_RECURSE_AGGREGATES:
+               /*
+                * we do NOT descend into the contained expression,
+                * even if the caller asked for it, because we never
+                * actually evaluate it - the result is driven entirely
+                * off the associated GROUP BY clause, so we never need
+                * to extract the actual Vars here.
+                */
+               return false;
+       }
+   }
    else if (IsA(node, PlaceHolderVar))
    {
        if (((PlaceHolderVar *) node)->phlevelsup != 0)
index 3eb4feabfd6b15ae1983914f49c9660ca11285a9..82c9abfa9157d9083e70ea8653d63d08fcadacf2 100644 (file)
@@ -1060,6 +1060,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
    qry->groupClause = transformGroupClause(pstate,
                                            stmt->groupClause,
+                                           &qry->groupingSets,
                                            &qry->targetList,
                                            qry->sortClause,
                                            EXPR_KIND_GROUP_BY,
@@ -1106,7 +1107,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
    qry->hasSubLinks = pstate->p_hasSubLinks;
    qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
    qry->hasAggs = pstate->p_hasAggs;
-   if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
+   if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
        parseCheckAggregates(pstate, qry);
 
    foreach(l, stmt->lockingClause)
@@ -1566,7 +1567,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
    qry->hasSubLinks = pstate->p_hasSubLinks;
    qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
    qry->hasAggs = pstate->p_hasAggs;
-   if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
+   if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
        parseCheckAggregates(pstate, qry);
 
    foreach(l, lockingClause)
index 7a4c07365c1d3222be563c7a7695a7c06cfa4bad..1477dd02053baf5c0907ed9d8fc7599ac97381de 100644 (file)
@@ -371,6 +371,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                relation_expr_list dostmt_opt_list
                transform_element_list transform_type_list
 
+%type <list>   group_by_list
+%type <node>   group_by_item empty_grouping_set rollup_clause cube_clause
+%type <node>   grouping_sets_clause
+
 %type <list>   opt_fdw_options fdw_options
 %type <defelt> fdw_option
 
@@ -438,7 +442,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>   ExclusionConstraintList ExclusionConstraintElem
 %type <list>   func_arg_list
 %type <node>   func_arg_expr
-%type <list>   row type_list array_expr_list
+%type <list>   row explicit_row implicit_row type_list array_expr_list
 %type <node>   case_expr case_arg when_clause case_default
 %type <list>   when_clause_list
 %type <ival>   sub_type
@@ -563,7 +567,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
    COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
    CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-   CROSS CSV CURRENT_P
+   CROSS CSV CUBE CURRENT_P
    CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
    CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -578,7 +582,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
    FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
-   GLOBAL GRANT GRANTED GREATEST GROUP_P
+   GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING
 
    HANDLER HAVING HEADER_P HOLD HOUR_P
 
@@ -612,12 +616,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
    RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
    RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
-   RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
+   RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
    ROW ROWS RULE
 
    SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-   SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
-   SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
+   SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
+   SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
    STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
    SYMMETRIC SYSID SYSTEM_P
 
@@ -677,6 +681,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * and for NULL so that it can follow b_expr in ColQualList without creating
  * postfix-operator problems.
  *
+ * To support CUBE and ROLLUP in GROUP BY without reserving them, we give them
+ * an explicit priority lower than '(', so that a rule with CUBE '(' will shift
+ * rather than reducing a conflicting rule that takes CUBE as a function name.
+ * Using the same precedence as IDENT seems right for the reasons given above.
+ *
  * The frame_bound productions UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING
  * are even messier: since UNBOUNDED is an unreserved keyword (per spec!),
  * there is no principled way to distinguish these from the productions
@@ -687,7 +696,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * blame any funny behavior of UNBOUNDED on the SQL standard, though.
  */
 %nonassoc  UNBOUNDED       /* ideally should have same precedence as IDENT */
-%nonassoc  IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING
+%nonassoc  IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
 %left      Op OPERATOR     /* multi-character ops and user-defined operators */
 %left      '+' '-'
 %left      '*' '/' '%'
@@ -10274,11 +10283,79 @@ first_or_next: FIRST_P                                { $$ = 0; }
        ;
 
 
+/*
+ * This syntax for group_clause tries to follow the spec quite closely.
+ * However, the spec allows only column references, not expressions,
+ * which introduces an ambiguity between implicit row constructors
+ * (a,b) and lists of column references.
+ *
+ * We handle this by using the a_expr production for what the spec calls
+ * <ordinary grouping set>, which in the spec represents either one column
+ * reference or a parenthesized list of column references. Then, we check the
+ * top node of the a_expr to see if it's an implicit RowExpr, and if so, just
+ * grab and use the list, discarding the node. (this is done in parse analysis,
+ * not here)
+ *
+ * (we abuse the row_format field of RowExpr to distinguish implicit and
+ * explicit row constructors; it's debatable if anyone sanely wants to use them
+ * in a group clause, but if they have a reason to, we make it possible.)
+ *
+ * Each item in the group_clause list is either an expression tree or a
+ * GroupingSet node of some type.
+ */
+
 group_clause:
-           GROUP_P BY expr_list                    { $$ = $3; }
+           GROUP_P BY group_by_list                { $$ = $3; }
            | /*EMPTY*/                             { $$ = NIL; }
        ;
 
+group_by_list:
+           group_by_item                           { $$ = list_make1($1); }
+           | group_by_list ',' group_by_item       { $$ = lappend($1,$3); }
+       ;
+
+group_by_item:
+           a_expr                                  { $$ = $1; }
+           | empty_grouping_set                    { $$ = $1; }
+           | cube_clause                           { $$ = $1; }
+           | rollup_clause                         { $$ = $1; }
+           | grouping_sets_clause                  { $$ = $1; }
+       ;
+
+empty_grouping_set:
+           '(' ')'
+               {
+                   $$ = (Node *) makeGroupingSet(GROUPING_SET_EMPTY, NIL, @1);
+               }
+       ;
+
+/*
+ * These hacks rely on setting precedence of CUBE and ROLLUP below that of '(',
+ * so that they shift in these rules rather than reducing the conflicting
+ * unreserved_keyword rule.
+ */
+
+rollup_clause:
+           ROLLUP '(' expr_list ')'
+               {
+                   $$ = (Node *) makeGroupingSet(GROUPING_SET_ROLLUP, $3, @1);
+               }
+       ;
+
+cube_clause:
+           CUBE '(' expr_list ')'
+               {
+                   $$ = (Node *) makeGroupingSet(GROUPING_SET_CUBE, $3, @1);
+               }
+       ;
+
+grouping_sets_clause:
+           GROUPING SETS '(' group_by_list ')'
+               {
+                   $$ = (Node *) makeGroupingSet(GROUPING_SET_SETS, $4, @1);
+               }
+       ;
+
 having_clause:
            HAVING a_expr                           { $$ = $2; }
            | /*EMPTY*/                             { $$ = NULL; }
@@ -11901,15 +11978,33 @@ c_expr:       columnref                               { $$ = $1; }
                    n->location = @1;
                    $$ = (Node *)n;
                }
-           | row
+           | explicit_row
                {
                    RowExpr *r = makeNode(RowExpr);
                    r->args = $1;
                    r->row_typeid = InvalidOid; /* not analyzed yet */
                    r->colnames = NIL;  /* to be filled in during analysis */
+                   r->row_format = COERCE_EXPLICIT_CALL; /* abuse */
                    r->location = @1;
                    $$ = (Node *)r;
                }
+           | implicit_row
+               {
+                   RowExpr *r = makeNode(RowExpr);
+                   r->args = $1;
+                   r->row_typeid = InvalidOid; /* not analyzed yet */
+                   r->colnames = NIL;  /* to be filled in during analysis */
+                   r->row_format = COERCE_IMPLICIT_CAST; /* abuse */
+                   r->location = @1;
+                   $$ = (Node *)r;
+               }
+           | GROUPING '(' expr_list ')'
+             {
+                 GroupingFunc *g = makeNode(GroupingFunc);
+                 g->args = $3;
+                 g->location = @1;
+                 $$ = (Node *)g;
+             }
        ;
 
 func_application: func_name '(' ')'
@@ -12659,6 +12754,13 @@ row:       ROW '(' expr_list ')'                   { $$ = $3; }
            | '(' expr_list ',' a_expr ')'          { $$ = lappend($2, $4); }
        ;
 
+explicit_row:  ROW '(' expr_list ')'               { $$ = $3; }
+           | ROW '(' ')'                           { $$ = NIL; }
+       ;
+
+implicit_row:  '(' expr_list ',' a_expr ')'        { $$ = lappend($2, $4); }
+       ;
+
 sub_type:  ANY                                     { $$ = ANY_SUBLINK; }
            | SOME                                  { $$ = ANY_SUBLINK; }
            | ALL                                   { $$ = ALL_SUBLINK; }
@@ -13468,6 +13570,7 @@ unreserved_keyword:
            | COPY
            | COST
            | CSV
+           | CUBE
            | CURRENT_P
            | CURSOR
            | CYCLE
@@ -13616,6 +13719,7 @@ unreserved_keyword:
            | REVOKE
            | ROLE
            | ROLLBACK
+           | ROLLUP
            | ROWS
            | RULE
            | SAVEPOINT
@@ -13630,6 +13734,7 @@ unreserved_keyword:
            | SERVER
            | SESSION
            | SET
+           | SETS
            | SHARE
            | SHOW
            | SIMPLE
@@ -13715,6 +13820,7 @@ col_name_keyword:
            | EXTRACT
            | FLOAT_P
            | GREATEST
+           | GROUPING
            | INOUT
            | INT_P
            | INTEGER
index 7b0e66807d46b7130334e99df12b6e7418faeb02..19391d0f619ae9c27defebab4e3758ea8624e70a 100644 (file)
@@ -42,7 +42,9 @@ typedef struct
 {
    ParseState *pstate;
    Query      *qry;
+   PlannerInfo *root;
    List       *groupClauses;
+   List       *groupClauseCommonVars;
    bool        have_non_var_grouping;
    List      **func_grouped_rels;
    int         sublevels_up;
@@ -56,11 +58,18 @@ static int check_agg_arguments(ParseState *pstate,
 static bool check_agg_arguments_walker(Node *node,
                           check_agg_arguments_context *context);
 static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
-                       List *groupClauses, bool have_non_var_grouping,
+                       List *groupClauses, List *groupClauseVars,
+                       bool have_non_var_grouping,
                        List **func_grouped_rels);
 static bool check_ungrouped_columns_walker(Node *node,
                               check_ungrouped_columns_context *context);
-
+static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
+                                   List *groupClauses, PlannerInfo *root,
+                                   bool have_non_var_grouping);
+static bool finalize_grouping_exprs_walker(Node *node,
+                              check_ungrouped_columns_context *context);
+static void check_agglevels_and_constraints(ParseState *pstate,Node *expr);
+static List *expand_groupingset_node(GroupingSet *gs);
 
 /*
  * transformAggregateCall -
@@ -96,10 +105,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
    List       *tdistinct = NIL;
    AttrNumber  attno = 1;
    int         save_next_resno;
-   int         min_varlevel;
    ListCell   *lc;
-   const char *err;
-   bool        errkind;
 
    if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
    {
@@ -214,15 +220,96 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
    agg->aggorder = torder;
    agg->aggdistinct = tdistinct;
 
+   check_agglevels_and_constraints(pstate, (Node *) agg);
+}
+
+/* transformGroupingFunc
+ * Transform a GROUPING expression
+ *
+ * GROUPING() behaves very like an aggregate.  Processing of levels and nesting
+ * is done as for aggregates.  We set p_hasAggs for these expressions too.
+ */
+Node *
+transformGroupingFunc(ParseState *pstate, GroupingFunc *p)
+{
+   ListCell   *lc;
+   List       *args = p->args;
+   List       *result_list = NIL;
+   GroupingFunc *result = makeNode(GroupingFunc);
+
+   if (list_length(args) > 31)
+       ereport(ERROR,
+               (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+                errmsg("GROUPING must have fewer than 32 arguments"),
+                parser_errposition(pstate, p->location)));
+
+   foreach(lc, args)
+   {
+       Node *current_result;
+
+       current_result = transformExpr(pstate, (Node*) lfirst(lc), pstate->p_expr_kind);
+
+       /* acceptability of expressions is checked later */
+
+       result_list = lappend(result_list, current_result);
+   }
+
+   result->args = result_list;
+   result->location = p->location;
+
+   check_agglevels_and_constraints(pstate, (Node *) result);
+
+   return (Node *) result;
+}
+
+/*
+ * Aggregate functions and grouping operations (which are combined in the spec
+ * as <set function specification>) are very similar with regard to level and
+ * nesting restrictions (though we allow a lot more things than the spec does).
+ * Centralise those restrictions here.
+ */
+static void
+check_agglevels_and_constraints(ParseState *pstate, Node *expr)
+{
+   List       *directargs = NIL;
+   List       *args = NIL;
+   Expr       *filter = NULL;
+   int         min_varlevel;
+   int         location = -1;
+   Index      *p_levelsup;
+   const char *err;
+   bool        errkind;
+   bool        isAgg = IsA(expr, Aggref);
+
+   if (isAgg)
+   {
+       Aggref *agg = (Aggref *) expr;
+
+       directargs = agg->aggdirectargs;
+       args = agg->args;
+       filter = agg->aggfilter;
+       location = agg->location;
+       p_levelsup = &agg->agglevelsup;
+   }
+   else
+   {
+       GroupingFunc *grp = (GroupingFunc *) expr;
+
+       args = grp->args;
+       location = grp->location;
+       p_levelsup = &grp->agglevelsup;
+   }
+
    /*
     * Check the arguments to compute the aggregate's level and detect
     * improper nesting.
     */
    min_varlevel = check_agg_arguments(pstate,
-                                      agg->aggdirectargs,
-                                      agg->args,
-                                      agg->aggfilter);
-   agg->agglevelsup = min_varlevel;
+                                      directargs,
+                                      args,
+                                      filter);
+
+   *p_levelsup = min_varlevel;
 
    /* Mark the correct pstate level as having aggregates */
    while (min_varlevel-- > 0)
@@ -247,20 +334,32 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
            Assert(false);      /* can't happen */
            break;
        case EXPR_KIND_OTHER:
-           /* Accept aggregate here; caller must throw error if wanted */
+           /* Accept aggregate/grouping here; caller must throw error if wanted */
            break;
        case EXPR_KIND_JOIN_ON:
        case EXPR_KIND_JOIN_USING:
-           err = _("aggregate functions are not allowed in JOIN conditions");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in JOIN conditions");
+           else
+               err = _("grouping operations are not allowed in JOIN conditions");
+
            break;
        case EXPR_KIND_FROM_SUBSELECT:
            /* Should only be possible in a LATERAL subquery */
            Assert(pstate->p_lateral_active);
-           /* Aggregate scope rules make it worth being explicit here */
-           err = _("aggregate functions are not allowed in FROM clause of their own query level");
+           /* Aggregate/grouping scope rules make it worth being explicit here */
+           if (isAgg)
+               err = _("aggregate functions are not allowed in FROM clause of their own query level");
+           else
+               err = _("grouping operations are not allowed in FROM clause of their own query level");
+
            break;
        case EXPR_KIND_FROM_FUNCTION:
-           err = _("aggregate functions are not allowed in functions in FROM");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in functions in FROM");
+           else
+               err = _("grouping operations are not allowed in functions in FROM");
+
            break;
        case EXPR_KIND_WHERE:
            errkind = true;
@@ -278,10 +377,18 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
            /* okay */
            break;
        case EXPR_KIND_WINDOW_FRAME_RANGE:
-           err = _("aggregate functions are not allowed in window RANGE");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in window RANGE");
+           else
+               err = _("grouping operations are not allowed in window RANGE");
+
            break;
        case EXPR_KIND_WINDOW_FRAME_ROWS:
-           err = _("aggregate functions are not allowed in window ROWS");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in window ROWS");
+           else
+               err = _("grouping operations are not allowed in window ROWS");
+
            break;
        case EXPR_KIND_SELECT_TARGET:
            /* okay */
@@ -312,26 +419,55 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
            break;
        case EXPR_KIND_CHECK_CONSTRAINT:
        case EXPR_KIND_DOMAIN_CHECK:
-           err = _("aggregate functions are not allowed in check constraints");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in check constraints");
+           else
+               err = _("grouping operations are not allowed in check constraints");
+
            break;
        case EXPR_KIND_COLUMN_DEFAULT:
        case EXPR_KIND_FUNCTION_DEFAULT:
-           err = _("aggregate functions are not allowed in DEFAULT expressions");
+
+           if (isAgg)
+               err = _("aggregate functions are not allowed in DEFAULT expressions");
+           else
+               err = _("grouping operations are not allowed in DEFAULT expressions");
+
            break;
        case EXPR_KIND_INDEX_EXPRESSION:
-           err = _("aggregate functions are not allowed in index expressions");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in index expressions");
+           else
+               err = _("grouping operations are not allowed in index expressions");
+
            break;
        case EXPR_KIND_INDEX_PREDICATE:
-           err = _("aggregate functions are not allowed in index predicates");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in index predicates");
+           else
+               err = _("grouping operations are not allowed in index predicates");
+
            break;
        case EXPR_KIND_ALTER_COL_TRANSFORM:
-           err = _("aggregate functions are not allowed in transform expressions");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in transform expressions");
+           else
+               err = _("grouping operations are not allowed in transform expressions");
+
            break;
        case EXPR_KIND_EXECUTE_PARAMETER:
-           err = _("aggregate functions are not allowed in EXECUTE parameters");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in EXECUTE parameters");
+           else
+               err = _("grouping operations are not allowed in EXECUTE parameters");
+
            break;
        case EXPR_KIND_TRIGGER_WHEN:
-           err = _("aggregate functions are not allowed in trigger WHEN conditions");
+           if (isAgg)
+               err = _("aggregate functions are not allowed in trigger WHEN conditions");
+           else
+               err = _("grouping operations are not allowed in trigger WHEN conditions");
+
            break;
 
            /*
@@ -342,18 +478,22 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
             * which is sane anyway.
             */
    }
+
    if (err)
        ereport(ERROR,
                (errcode(ERRCODE_GROUPING_ERROR),
                 errmsg_internal("%s", err),
-                parser_errposition(pstate, agg->location)));
+                parser_errposition(pstate, location)));
+
    if (errkind)
        ereport(ERROR,
                (errcode(ERRCODE_GROUPING_ERROR),
-       /* translator: %s is name of a SQL construct, eg GROUP BY */
-                errmsg("aggregate functions are not allowed in %s",
+                /* translator: %s is name of a SQL construct, eg GROUP BY */
+                errmsg(isAgg
+                       ? "aggregate functions are not allowed in %s"
+                       : "grouping operations are not allowed in %s",
                        ParseExprKindName(pstate->p_expr_kind)),
-                parser_errposition(pstate, agg->location)));
+                parser_errposition(pstate, location)));
 }
 
 /*
@@ -507,6 +647,21 @@ check_agg_arguments_walker(Node *node,
        /* no need to examine args of the inner aggregate */
        return false;
    }
+   if (IsA(node, GroupingFunc))
+   {
+       int         agglevelsup = ((GroupingFunc *) node)->agglevelsup;
+
+       /* convert levelsup to frame of reference of original query */
+       agglevelsup -= context->sublevels_up;
+       /* ignore local aggs of subqueries */
+       if (agglevelsup >= 0)
+       {
+           if (context->min_agglevel < 0 ||
+               context->min_agglevel > agglevelsup)
+               context->min_agglevel = agglevelsup;
+       }
+       /* Continue and descend into subtree */
+   }
    /* We can throw error on sight for a window function */
    if (IsA(node, WindowFunc))
        ereport(ERROR,
@@ -527,6 +682,7 @@ check_agg_arguments_walker(Node *node,
        context->sublevels_up--;
        return result;
    }
+
    return expression_tree_walker(node,
                                  check_agg_arguments_walker,
                                  (void *) context);
@@ -770,17 +926,67 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 void
 parseCheckAggregates(ParseState *pstate, Query *qry)
 {
+   List       *gset_common = NIL;
    List       *groupClauses = NIL;
+   List       *groupClauseCommonVars = NIL;
    bool        have_non_var_grouping;
    List       *func_grouped_rels = NIL;
    ListCell   *l;
    bool        hasJoinRTEs;
    bool        hasSelfRefRTEs;
-   PlannerInfo *root;
+   PlannerInfo *root = NULL;
    Node       *clause;
 
    /* This should only be called if we found aggregates or grouping */
-   Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual);
+   Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual || qry->groupingSets);
+
+   /*
+    * If we have grouping sets, expand them and find the intersection of all
+    * sets.
+    */
+   if (qry->groupingSets)
+   {
+       /*
+        * The limit of 4096 is arbitrary and exists simply to avoid resource
+        * issues from pathological constructs.
+        */
+       List *gsets = expand_grouping_sets(qry->groupingSets, 4096);
+
+       if (!gsets)
+           ereport(ERROR,
+                   (errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
+                    errmsg("Too many grouping sets present (max 4096)"),
+                    parser_errposition(pstate,
+                                       qry->groupClause
+                                       ? exprLocation((Node *) qry->groupClause)
+                                       : exprLocation((Node *) qry->groupingSets))));
+
+       /*
+        * The intersection will often be empty, so help things along by
+        * seeding the intersect with the smallest set.
+        */
+       gset_common = linitial(gsets);
+
+       if (gset_common)
+       {
+           for_each_cell(l, lnext(list_head(gsets)))
+           {
+               gset_common = list_intersection_int(gset_common, lfirst(l));
+               if (!gset_common)
+                   break;
+           }
+       }
+
+       /*
+        * If there was only one grouping set in the expansion, AND if the
+        * groupClause is non-empty (meaning that the grouping set is not empty
+        * either), then we can ditch the grouping set and pretend we just had
+        * a normal GROUP BY.
+        */
+
+       if (list_length(gsets) == 1 && qry->groupClause)
+           qry->groupingSets = NIL;
+   }
 
    /*
     * Scan the range table to see if there are JOIN or self-reference CTE
@@ -800,15 +1006,19 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
    /*
     * Build a list of the acceptable GROUP BY expressions for use by
     * check_ungrouped_columns().
+    *
+    * We get the TLE, not just the expr, because GROUPING wants to know
+    * the sortgroupref.
     */
    foreach(l, qry->groupClause)
    {
        SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
-       Node       *expr;
+       TargetEntry    *expr;
 
-       expr = get_sortgroupclause_expr(grpcl, qry->targetList);
+       expr = get_sortgroupclause_tle(grpcl, qry->targetList);
        if (expr == NULL)
            continue;           /* probably cannot happen */
+
        groupClauses = lcons(expr, groupClauses);
    }
 
@@ -830,21 +1040,28 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
        groupClauses = (List *) flatten_join_alias_vars(root,
                                                      (Node *) groupClauses);
    }
-   else
-       root = NULL;            /* keep compiler quiet */
 
    /*
     * Detect whether any of the grouping expressions aren't simple Vars; if
     * they're all Vars then we don't have to work so hard in the recursive
     * scans.  (Note we have to flatten aliases before this.)
+    *
+    * Track Vars that are included in all grouping sets separately in
+    * groupClauseCommonVars, since these are the only ones we can use to check
+    * for functional dependencies.
     */
    have_non_var_grouping = false;
    foreach(l, groupClauses)
    {
-       if (!IsA((Node *) lfirst(l), Var))
+       TargetEntry *tle = lfirst(l);
+       if (!IsA(tle->expr, Var))
        {
            have_non_var_grouping = true;
-           break;
+       }
+       else if (!qry->groupingSets
+                || list_member_int(gset_common, tle->ressortgroupref))
+       {
+           groupClauseCommonVars = lappend(groupClauseCommonVars, tle->expr);
        }
    }
 
@@ -855,19 +1072,30 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
     * this will also find ungrouped variables that came from ORDER BY and
     * WINDOW clauses.  For that matter, it's also going to examine the
     * grouping expressions themselves --- but they'll all pass the test ...
+    *
+    * We also finalize GROUPING expressions, but for that we need to traverse
+    * the original (unflattened) clause in order to modify nodes.
     */
    clause = (Node *) qry->targetList;
+   finalize_grouping_exprs(clause, pstate, qry,
+                           groupClauses, root,
+                           have_non_var_grouping);
    if (hasJoinRTEs)
        clause = flatten_join_alias_vars(root, clause);
    check_ungrouped_columns(clause, pstate, qry,
-                           groupClauses, have_non_var_grouping,
+                           groupClauses, groupClauseCommonVars,
+                           have_non_var_grouping,
                            &func_grouped_rels);
 
    clause = (Node *) qry->havingQual;
+   finalize_grouping_exprs(clause, pstate, qry,
+                           groupClauses, root,
+                           have_non_var_grouping);
    if (hasJoinRTEs)
        clause = flatten_join_alias_vars(root, clause);
    check_ungrouped_columns(clause, pstate, qry,
-                           groupClauses, have_non_var_grouping,
+                           groupClauses, groupClauseCommonVars,
+                           have_non_var_grouping,
                            &func_grouped_rels);
 
    /*
@@ -904,14 +1132,17 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
  */
 static void
 check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
-                       List *groupClauses, bool have_non_var_grouping,
+                       List *groupClauses, List *groupClauseCommonVars,
+                       bool have_non_var_grouping,
                        List **func_grouped_rels)
 {
    check_ungrouped_columns_context context;
 
    context.pstate = pstate;
    context.qry = qry;
+   context.root = NULL;
    context.groupClauses = groupClauses;
+   context.groupClauseCommonVars = groupClauseCommonVars;
    context.have_non_var_grouping = have_non_var_grouping;
    context.func_grouped_rels = func_grouped_rels;
    context.sublevels_up = 0;
@@ -965,6 +1196,16 @@ check_ungrouped_columns_walker(Node *node,
            return false;
    }
 
+   if (IsA(node, GroupingFunc))
+   {
+       GroupingFunc *grp = (GroupingFunc *) node;
+
+       /* handled GroupingFunc separately, no need to recheck at this level */
+
+       if ((int) grp->agglevelsup >= context->sublevels_up)
+           return false;
+   }
+
    /*
     * If we have any GROUP BY items that are not simple Vars, check to see if
     * subexpression as a whole matches any GROUP BY item. We need to do this
@@ -976,7 +1217,9 @@ check_ungrouped_columns_walker(Node *node,
    {
        foreach(gl, context->groupClauses)
        {
-           if (equal(node, lfirst(gl)))
+           TargetEntry *tle = lfirst(gl);
+
+           if (equal(node, tle->expr))
                return false;   /* acceptable, do not descend more */
        }
    }
@@ -1003,13 +1246,15 @@ check_ungrouped_columns_walker(Node *node,
        {
            foreach(gl, context->groupClauses)
            {
-               Var        *gvar = (Var *) lfirst(gl);
+               Var        *gvar = (Var *) ((TargetEntry *)lfirst(gl))->expr;
 
                if (IsA(gvar, Var) &&
                    gvar->varno == var->varno &&
                    gvar->varattno == var->varattno &&
                    gvar->varlevelsup == 0)
+               {
                    return false;       /* acceptable, we're okay */
+               }
            }
        }
 
@@ -1040,7 +1285,7 @@ check_ungrouped_columns_walker(Node *node,
            if (check_functional_grouping(rte->relid,
                                          var->varno,
                                          0,
-                                         context->groupClauses,
+                                         context->groupClauseCommonVars,
                                          &context->qry->constraintDeps))
            {
                *context->func_grouped_rels =
@@ -1084,6 +1329,396 @@ check_ungrouped_columns_walker(Node *node,
                                  (void *) context);
 }
 
+/*
+ * finalize_grouping_exprs -
+ *   Scan the given expression tree for GROUPING() and related calls,
+ *    and validate and process their arguments.
+ *
+ * This is split out from check_ungrouped_columns above because it needs
+ * to modify the nodes (which it does in-place, not via a mutator) while
+ * check_ungrouped_columns may see only a copy of the original thanks to
+ * flattening of join alias vars. So here, we flatten each individual
+ * GROUPING argument as we see it before comparing it.
+ */
+static void
+finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
+                       List *groupClauses, PlannerInfo *root,
+                       bool have_non_var_grouping)
+{
+   check_ungrouped_columns_context context;
+
+   context.pstate = pstate;
+   context.qry = qry;
+   context.root = root;
+   context.groupClauses = groupClauses;
+   context.groupClauseCommonVars = NIL;
+   context.have_non_var_grouping = have_non_var_grouping;
+   context.func_grouped_rels = NULL;
+   context.sublevels_up = 0;
+   context.in_agg_direct_args = false;
+   finalize_grouping_exprs_walker(node, &context);
+}
+
+static bool
+finalize_grouping_exprs_walker(Node *node,
+                              check_ungrouped_columns_context *context)
+{
+   ListCell   *gl;
+
+   if (node == NULL)
+       return false;
+   if (IsA(node, Const) ||
+       IsA(node, Param))
+       return false;           /* constants are always acceptable */
+
+   if (IsA(node, Aggref))
+   {
+       Aggref     *agg = (Aggref *) node;
+
+       if ((int) agg->agglevelsup == context->sublevels_up)
+       {
+           /*
+            * If we find an aggregate call of the original level, do not
+            * recurse into its normal arguments, ORDER BY arguments, or
+            * filter; GROUPING exprs of this level are not allowed there. But
+            * check direct arguments as though they weren't in an aggregate.
+            */
+           bool        result;
+
+           Assert(!context->in_agg_direct_args);
+           context->in_agg_direct_args = true;
+           result = finalize_grouping_exprs_walker((Node *) agg->aggdirectargs,
+                                                   context);
+           context->in_agg_direct_args = false;
+           return result;
+       }
+
+       /*
+        * We can skip recursing into aggregates of higher levels altogether,
+        * since they could not possibly contain exprs of concern to us (see
+        * transformAggregateCall).  We do need to look at aggregates of lower
+        * levels, however.
+        */
+       if ((int) agg->agglevelsup > context->sublevels_up)
+           return false;
+   }
+
+   if (IsA(node, GroupingFunc))
+   {
+       GroupingFunc *grp = (GroupingFunc *) node;
+
+       /*
+        * We only need to check GroupingFunc nodes at the exact level to which
+        * they belong, since they cannot mix levels in arguments.
+        */
+
+       if ((int) grp->agglevelsup == context->sublevels_up)
+       {
+           ListCell  *lc;
+           List      *ref_list = NIL;
+
+           foreach(lc, grp->args)
+           {
+               Node   *expr = lfirst(lc);
+               Index   ref = 0;
+
+               if (context->root)
+                   expr = flatten_join_alias_vars(context->root, expr);
+
+               /*
+                * Each expression must match a grouping entry at the current
+                * query level. Unlike the general expression case, we don't
+                * allow functional dependencies or outer references.
+                */
+
+               if (IsA(expr, Var))
+               {
+                   Var *var = (Var *) expr;
+
+                   if (var->varlevelsup == context->sublevels_up)
+                   {
+                       foreach(gl, context->groupClauses)
+                       {
+                           TargetEntry *tle = lfirst(gl);
+                           Var         *gvar = (Var *) tle->expr;
+
+                           if (IsA(gvar, Var) &&
+                               gvar->varno == var->varno &&
+                               gvar->varattno == var->varattno &&
+                               gvar->varlevelsup == 0)
+                           {
+                               ref = tle->ressortgroupref;
+                               break;
+                           }
+                       }
+                   }
+               }
+               else if (context->have_non_var_grouping
+                        && context->sublevels_up == 0)
+               {
+                   foreach(gl, context->groupClauses)
+                   {
+                       TargetEntry *tle = lfirst(gl);
+
+                       if (equal(expr, tle->expr))
+                       {
+                           ref = tle->ressortgroupref;
+                           break;
+                       }
+                   }
+               }
+
+               if (ref == 0)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_GROUPING_ERROR),
+                            errmsg("Arguments to GROUPING must be grouping expressions of the associated query level"),
+                            parser_errposition(context->pstate,
+                                               exprLocation(expr))));
+
+               ref_list = lappend_int(ref_list, ref);
+           }
+
+           grp->refs = ref_list;
+       }
+
+       if ((int) grp->agglevelsup > context->sublevels_up)
+           return false;
+   }
+
+   if (IsA(node, Query))
+   {
+       /* Recurse into subselects */
+       bool        result;
+
+       context->sublevels_up++;
+       result = query_tree_walker((Query *) node,
+                                  finalize_grouping_exprs_walker,
+                                  (void *) context,
+                                  0);
+       context->sublevels_up--;
+       return result;
+   }
+   return expression_tree_walker(node, finalize_grouping_exprs_walker,
+                                 (void *) context);
+}
+
+
+/*
+ * Given a GroupingSet node, expand it and return a list of lists.
+ *
+ * For EMPTY nodes, return a list of one empty list.
+ *
+ * For SIMPLE nodes, return a list of one list, which is the node content.
+ *
+ * For CUBE and ROLLUP nodes, return a list of the expansions.
+ *
+ * For SET nodes, recursively expand contained CUBE and ROLLUP.
+ */
+static List*
+expand_groupingset_node(GroupingSet *gs)
+{
+   List * result = NIL;
+
+   switch (gs->kind)
+   {
+       case GROUPING_SET_EMPTY:
+           result = list_make1(NIL);
+           break;
+
+       case GROUPING_SET_SIMPLE:
+           result = list_make1(gs->content);
+           break;
+
+       case GROUPING_SET_ROLLUP:
+           {
+               List       *rollup_val = gs->content;
+               ListCell   *lc;
+               int         curgroup_size = list_length(gs->content);
+
+               while (curgroup_size > 0)
+               {
+                   List   *current_result = NIL;
+                   int     i = curgroup_size;
+
+                   foreach(lc, rollup_val)
+                   {
+                       GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
+
+                       Assert(gs_current->kind == GROUPING_SET_SIMPLE);
+
+                       current_result
+                           = list_concat(current_result,
+                                         list_copy(gs_current->content));
+
+                       /* If we are done with making the current group, break */
+                       if (--i == 0)
+                           break;
+                   }
+
+                   result = lappend(result, current_result);
+                   --curgroup_size;
+               }
+
+               result = lappend(result, NIL);
+           }
+           break;
+
+       case GROUPING_SET_CUBE:
+           {
+               List   *cube_list = gs->content;
+               int     number_bits = list_length(cube_list);
+               uint32  num_sets;
+               uint32  i;
+
+               /* parser should cap this much lower */
+               Assert(number_bits < 31);
+
+               num_sets = (1U << number_bits);
+
+               for (i = 0; i < num_sets; i++)
+               {
+                   List *current_result = NIL;
+                   ListCell *lc;
+                   uint32 mask = 1U;
+
+                   foreach(lc, cube_list)
+                   {
+                       GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
+
+                       Assert(gs_current->kind == GROUPING_SET_SIMPLE);
+
+                       if (mask & i)
+                       {
+                           current_result
+                               = list_concat(current_result,
+                                             list_copy(gs_current->content));
+                       }
+
+                       mask <<= 1;
+                   }
+
+                   result = lappend(result, current_result);
+               }
+           }
+           break;
+
+       case GROUPING_SET_SETS:
+           {
+               ListCell   *lc;
+
+               foreach(lc, gs->content)
+               {
+                   List *current_result = expand_groupingset_node(lfirst(lc));
+
+                   result = list_concat(result, current_result);
+               }
+           }
+           break;
+   }
+
+   return result;
+}
+
+static int
+cmp_list_len_asc(const void *a, const void *b)
+{
+   int la = list_length(*(List*const*)a);
+   int lb = list_length(*(List*const*)b);
+   return (la > lb) ? 1 : (la == lb) ? 0 : -1;
+}
+
+/*
+ * Expand a groupingSets clause to a flat list of grouping sets.
+ * The returned list is sorted by length, shortest sets first.
+ *
+ * This is mainly for the planner, but we use it here too to do
+ * some consistency checks.
+ */
+
+List *
+expand_grouping_sets(List *groupingSets, int limit)
+{
+   List       *expanded_groups = NIL;
+   List       *result = NIL;
+   double      numsets = 1;
+   ListCell   *lc;
+
+   if (groupingSets == NIL)
+       return NIL;
+
+   foreach(lc, groupingSets)
+   {
+       List *current_result = NIL;
+       GroupingSet *gs = lfirst(lc);
+
+       current_result = expand_groupingset_node(gs);
+
+       Assert(current_result != NIL);
+
+       numsets *= list_length(current_result);
+
+       if (limit >= 0 && numsets > limit)
+           return NIL;
+
+       expanded_groups = lappend(expanded_groups, current_result);
+   }
+
+   /*
+    * Do cartesian product between sublists of expanded_groups.
+    * While at it, remove any duplicate elements from individual
+    * grouping sets (we must NOT change the number of sets though)
+    */
+
+   foreach(lc, (List *) linitial(expanded_groups))
+   {
+       result = lappend(result, list_union_int(NIL, (List *) lfirst(lc)));
+   }
+
+   for_each_cell(lc, lnext(list_head(expanded_groups)))
+   {
+       List       *p = lfirst(lc);
+       List       *new_result = NIL;
+       ListCell   *lc2;
+
+       foreach(lc2, result)
+       {
+           List       *q = lfirst(lc2);
+           ListCell   *lc3;
+
+           foreach(lc3, p)
+           {
+               new_result = lappend(new_result,
+                                    list_union_int(q, (List *) lfirst(lc3)));
+           }
+       }
+       result = new_result;
+   }
+
+   if (list_length(result) > 1)
+   {
+       int     result_len = list_length(result);
+       List  **buf = palloc(sizeof(List*) * result_len);
+       List  **ptr = buf;
+
+       foreach(lc, result)
+       {
+           *ptr++ = lfirst(lc);
+       }
+
+       qsort(buf, result_len, sizeof(List*), cmp_list_len_asc);
+
+       result = NIL;
+       ptr = buf;
+
+       while (result_len-- > 0)
+           result = lappend(result, *ptr++);
+
+       pfree(buf);
+   }
+
+   return result;
+}
+
 /*
  * get_aggregate_argtypes
  * Identify the specific datatypes passed to an aggregate call.
index 73c505ed85bdd8481e00fbb5b431333e9e65ee89..cfed28d423389ad45f6089691efcc85b1f08b9b5 100644 (file)
@@ -39,6 +39,7 @@
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "miscadmin.h"
 
 
 /* Convenience macro for the most common makeNamespaceItem() case */
@@ -1669,40 +1670,182 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
    return target_result;
 }
 
+
 /*
- * transformGroupClause -
- *   transform a GROUP BY clause
+ * Flatten out parenthesized sublists in grouping lists, and some cases
+ * of nested grouping sets.
  *
- * GROUP BY items will be added to the targetlist (as resjunk columns)
- * if not already present, so the targetlist must be passed by reference.
+ * Inside a grouping set (ROLLUP, CUBE, or GROUPING SETS), we expect the
+ * content to be nested no more than 2 deep: i.e. ROLLUP((a,b),(c,d)) is
+ * ok, but ROLLUP((a,(b,c)),d) is flattened to ((a,b,c),d), which we then
+ * normalize to ((a,b,c),(d)).
  *
- * This is also used for window PARTITION BY clauses (which act almost the
- * same, but are always interpreted per SQL99 rules).
+ * CUBE or ROLLUP can be nested inside GROUPING SETS (but not the reverse),
+ * and we leave that alone if we find it. But if we see GROUPING SETS inside
+ * GROUPING SETS, we can flatten and normalize as follows:
+ *   GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g))
+ * becomes
+ *   GROUPING SETS ((a), (b,c), (c,d), (e), (f,g))
+ *
+ * This is per the spec's syntax transformations, but these are the only such
+ * transformations we do in parse analysis, so that queries retain the
+ * originally specified grouping set syntax for CUBE and ROLLUP as much as
+ * possible when deparsed. (Full expansion of the result into a list of
+ * grouping sets is left to the planner.)
+ *
+ * When we're done, the resulting list should contain only these possible
+ * elements:
+ *   - an expression
+ *   - a CUBE or ROLLUP with a list of expressions nested 2 deep
+ *   - a GROUPING SET containing any of:
+ *      - expression lists
+ *      - empty grouping sets
+ *      - CUBE or ROLLUP nodes with lists nested 2 deep
+ * The return is a new list, but doesn't deep-copy the old nodes except for
+ * GroupingSet nodes.
+ *
+ * As a side effect, flag whether the list has any GroupingSet nodes.
  */
-List *
-transformGroupClause(ParseState *pstate, List *grouplist,
-                    List **targetlist, List *sortClause,
-                    ParseExprKind exprKind, bool useSQL99)
+
+static Node *
+flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets)
 {
-   List       *result = NIL;
-   ListCell   *gl;
+   /* just in case of pathological input */
+   check_stack_depth();
+
+   if (expr == (Node *) NIL)
+       return (Node *) NIL;
 
-   foreach(gl, grouplist)
+   switch (expr->type)
    {
-       Node       *gexpr = (Node *) lfirst(gl);
-       TargetEntry *tle;
-       bool        found = false;
+       case T_RowExpr:
+           {
+               RowExpr *r = (RowExpr *) expr;
+               if (r->row_format == COERCE_IMPLICIT_CAST)
+                   return flatten_grouping_sets((Node *) r->args,
+                                                false, NULL);
+           }
+           break;
+       case T_GroupingSet:
+           {
+               GroupingSet *gset = (GroupingSet *) expr;
+               ListCell   *l2;
+               List       *result_set = NIL;
 
-       if (useSQL99)
-           tle = findTargetlistEntrySQL99(pstate, gexpr,
-                                          targetlist, exprKind);
-       else
-           tle = findTargetlistEntrySQL92(pstate, gexpr,
-                                          targetlist, exprKind);
+               if (hasGroupingSets)
+                   *hasGroupingSets = true;
 
-       /* Eliminate duplicates (GROUP BY x, x) */
-       if (targetIsInSortList(tle, InvalidOid, result))
-           continue;
+               /*
+                * at the top level, we skip over all empty grouping sets; the
+                * caller can supply the canonical GROUP BY () if nothing is left.
+                */
+
+               if (toplevel && gset->kind == GROUPING_SET_EMPTY)
+                   return (Node *) NIL;
+
+               foreach(l2, gset->content)
+               {
+                   Node   *n2 = flatten_grouping_sets(lfirst(l2), false, NULL);
+
+                   result_set = lappend(result_set, n2);
+               }
+
+               /*
+                * At top level, keep the grouping set node; but if we're in a nested
+                * grouping set, then we need to concat the flattened result into the
+                * outer list if it's simply nested.
+                */
+
+               if (toplevel || (gset->kind != GROUPING_SET_SETS))
+               {
+                   return (Node *) makeGroupingSet(gset->kind, result_set, gset->location);
+               }
+               else
+                   return (Node *) result_set;
+           }
+       case T_List:
+           {
+               List       *result = NIL;
+               ListCell   *l;
+
+               foreach(l, (List *)expr)
+               {
+                   Node   *n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets);
+                   if (n != (Node *) NIL)
+                   {
+                       if (IsA(n,List))
+                           result = list_concat(result, (List *) n);
+                       else
+                           result = lappend(result, n);
+                   }
+               }
+
+               return (Node *) result;
+           }
+       default:
+           break;
+   }
+
+   return expr;
+}
+
+/*
+ * Transform a single expression within a GROUP BY clause or grouping set.
+ *
+ * The expression is added to the targetlist if not already present, and to the
+ * flatresult list (which will become the groupClause) if not already present
+ * there.  The sortClause is consulted for operator and sort order hints.
+ *
+ * Returns the ressortgroupref of the expression.
+ *
+ * flatresult  reference to flat list of SortGroupClause nodes
+ * seen_local  bitmapset of sortgrouprefs already seen at the local level
+ * pstate      ParseState
+ * gexpr       node to transform
+ * targetlist  reference to TargetEntry list
+ * sortClause  ORDER BY clause (SortGroupClause nodes)
+ * exprKind        expression kind
+ * useSQL99        SQL99 rather than SQL92 syntax
+ * toplevel        false if within any grouping set
+ */
+static Index
+transformGroupClauseExpr(List **flatresult, Bitmapset *seen_local,
+                        ParseState *pstate, Node *gexpr,
+                        List **targetlist, List *sortClause,
+                        ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+   TargetEntry *tle;
+   bool        found = false;
+
+   if (useSQL99)
+       tle = findTargetlistEntrySQL99(pstate, gexpr,
+                                      targetlist, exprKind);
+   else
+       tle = findTargetlistEntrySQL92(pstate, gexpr,
+                                      targetlist, exprKind);
+
+   if (tle->ressortgroupref > 0)
+   {
+       ListCell   *sl;
+
+       /*
+        * Eliminate duplicates (GROUP BY x, x) but only at local level.
+        * (Duplicates in grouping sets can affect the number of returned
+        * rows, so can't be dropped indiscriminately.)
+        *
+        * Since we don't care about anything except the sortgroupref,
+        * we can use a bitmapset rather than scanning lists.
+        */
+       if (bms_is_member(tle->ressortgroupref,seen_local))
+           return 0;
+
+       /*
+        * If we're already in the flat clause list, we don't need
+        * to consider adding ourselves again.
+        */
+       found = targetIsInSortList(tle, InvalidOid, *flatresult);
+       if (found)
+           return tle->ressortgroupref;
 
        /*
         * If the GROUP BY tlist entry also appears in ORDER BY, copy operator
@@ -1714,35 +1857,308 @@ transformGroupClause(ParseState *pstate, List *grouplist,
         * sort step, and it allows the user to choose the equality semantics
         * used by GROUP BY, should she be working with a datatype that has
         * more than one equality operator.
+        *
+        * If we're in a grouping set, though, we force our requested ordering
+        * to be NULLS LAST, because if we have any hope of using a sorted agg
+        * for the job, we're going to be tacking on generated NULL values
+        * after the corresponding groups. If the user demands nulls first,
+        * another sort step is going to be inevitable, but that's the
+        * planner's problem.
         */
-       if (tle->ressortgroupref > 0)
+
+       foreach(sl, sortClause)
        {
-           ListCell   *sl;
+           SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
 
-           foreach(sl, sortClause)
+           if (sc->tleSortGroupRef == tle->ressortgroupref)
            {
-               SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
+               SortGroupClause *grpc = copyObject(sc);
+               if (!toplevel)
+                   grpc->nulls_first = false;
+               *flatresult = lappend(*flatresult, grpc);
+               found = true;
+               break;
+           }
+       }
+   }
 
-               if (sc->tleSortGroupRef == tle->ressortgroupref)
-               {
-                   result = lappend(result, copyObject(sc));
-                   found = true;
+   /*
+    * If no match in ORDER BY, just add it to the result using default
+    * sort/group semantics.
+    */
+   if (!found)
+       *flatresult = addTargetToGroupList(pstate, tle,
+                                          *flatresult, *targetlist,
+                                          exprLocation(gexpr),
+                                          true);
+
+   /*
+    * _something_ must have assigned us a sortgroupref by now...
+    */
+
+   return tle->ressortgroupref;
+}
+
+/*
+ * Transform a list of expressions within a GROUP BY clause or grouping set.
+ *
+ * The list of expressions belongs to a single clause within which duplicates
+ * can be safely eliminated.
+ *
+ * Returns an integer list of ressortgroupref values.
+ *
+ * flatresult  reference to flat list of SortGroupClause nodes
+ * pstate      ParseState
+ * list            nodes to transform
+ * targetlist  reference to TargetEntry list
+ * sortClause  ORDER BY clause (SortGroupClause nodes)
+ * exprKind        expression kind
+ * useSQL99        SQL99 rather than SQL92 syntax
+ * toplevel        false if within any grouping set
+ */
+static List *
+transformGroupClauseList(List **flatresult,
+                        ParseState *pstate, List *list,
+                        List **targetlist, List *sortClause,
+                        ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+   Bitmapset  *seen_local = NULL;
+   List       *result = NIL;
+   ListCell   *gl;
+
+   foreach(gl, list)
+   {
+       Node        *gexpr = (Node *) lfirst(gl);
+
+       Index ref = transformGroupClauseExpr(flatresult,
+                                            seen_local,
+                                            pstate,
+                                            gexpr,
+                                            targetlist,
+                                            sortClause,
+                                            exprKind,
+                                            useSQL99,
+                                            toplevel);
+       if (ref > 0)
+       {
+           seen_local = bms_add_member(seen_local, ref);
+           result = lappend_int(result, ref);
+       }
+   }
+
+   return result;
+}
+
+/*
+ * Transform a grouping set and (recursively) its content.
+ *
+ * The grouping set might be a GROUPING SETS node with other grouping sets
+ * inside it, but SETS within SETS have already been flattened out before
+ * reaching here.
+ *
+ * Returns the transformed node, which now contains SIMPLE nodes with lists
+ * of ressortgrouprefs rather than expressions.
+ *
+ * flatresult  reference to flat list of SortGroupClause nodes
+ * pstate      ParseState
+ * gset            grouping set to transform
+ * targetlist  reference to TargetEntry list
+ * sortClause  ORDER BY clause (SortGroupClause nodes)
+ * exprKind        expression kind
+ * useSQL99        SQL99 rather than SQL92 syntax
+ * toplevel        false if within any grouping set
+ */
+static Node *
+transformGroupingSet(List **flatresult,
+                    ParseState *pstate, GroupingSet *gset,
+                    List **targetlist, List *sortClause,
+                    ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+   ListCell   *gl;
+   List       *content = NIL;
+
+   Assert(toplevel || gset->kind != GROUPING_SET_SETS);
+
+   foreach(gl, gset->content)
+   {
+       Node   *n = lfirst(gl);
+
+       if (IsA(n, List))
+       {
+           List *l = transformGroupClauseList(flatresult,
+                                              pstate, (List *) n,
+                                              targetlist, sortClause,
+                                              exprKind, useSQL99, false);
+
+           content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
+                                                      l,
+                                                      exprLocation(n)));
+       }
+       else if (IsA(n, GroupingSet))
+       {
+           GroupingSet *gset2 = (GroupingSet *) lfirst(gl);
+
+           content = lappend(content, transformGroupingSet(flatresult,
+                                                           pstate, gset2,
+                                                           targetlist, sortClause,
+                                                           exprKind, useSQL99, false));
+       }
+       else
+       {
+           Index ref = transformGroupClauseExpr(flatresult,
+                                                NULL,
+                                                pstate,
+                                                n,
+                                                targetlist,
+                                                sortClause,
+                                                exprKind,
+                                                useSQL99,
+                                                false);
+
+           content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
+                                                      list_make1_int(ref),
+                                                      exprLocation(n)));
+       }
+   }
+
+   /* Arbitrarily cap the size of CUBE, which has exponential growth */
+   if (gset->kind == GROUPING_SET_CUBE)
+   {
+       if (list_length(content) > 12)
+           ereport(ERROR,
+                   (errcode(ERRCODE_TOO_MANY_COLUMNS),
+                    errmsg("CUBE is limited to 12 elements"),
+                    parser_errposition(pstate, gset->location)));
+   }
+
+   return (Node *) makeGroupingSet(gset->kind, content, gset->location);
+}
+
+
+/*
+ * transformGroupClause -
+ *   transform a GROUP BY clause
+ *
+ * GROUP BY items will be added to the targetlist (as resjunk columns)
+ * if not already present, so the targetlist must be passed by reference.
+ *
+ * This is also used for window PARTITION BY clauses (which act almost the
+ * same, but are always interpreted per SQL99 rules).
+ *
+ * Grouping sets make this a lot more complex than it was. Our goal here is
+ * twofold: we make a flat list of SortGroupClause nodes referencing each
+ * distinct expression used for grouping, with those expressions added to the
+ * targetlist if needed. At the same time, we build the groupingSets tree,
+ * which stores only ressortgrouprefs as integer lists inside GroupingSet nodes
+ * (possibly nested, but limited in depth: a GROUPING_SET_SETS node can contain
+ * nested SIMPLE, CUBE or ROLLUP nodes, but not more sets - we flatten that
+ * out; while CUBE and ROLLUP can contain only SIMPLE nodes).
+ *
+ * We skip much of the hard work if there are no grouping sets.
+ *
+ * One subtlety is that the groupClause list can end up empty while the
+ * groupingSets list is not; this happens if there are only empty grouping
+ * sets, or an explicit GROUP BY (). This has the same effect as specifying
+ * aggregates or a HAVING clause with no GROUP BY; the output is one row per
+ * grouping set even if the input is empty.
+ *
+ * Returns the transformed (flat) groupClause.
+ *
+ * pstate      ParseState
+ * grouplist   clause to transform
+ * groupingSets    reference to list to contain the grouping set tree
+ * targetlist  reference to TargetEntry list
+ * sortClause  ORDER BY clause (SortGroupClause nodes)
+ * exprKind        expression kind
+ * useSQL99        SQL99 rather than SQL92 syntax
+ */
+List *
+transformGroupClause(ParseState *pstate, List *grouplist, List **groupingSets,
+                    List **targetlist, List *sortClause,
+                    ParseExprKind exprKind, bool useSQL99)
+{
+   List       *result = NIL;
+   List       *flat_grouplist;
+   List       *gsets = NIL;
+   ListCell   *gl;
+   bool        hasGroupingSets = false;
+   Bitmapset  *seen_local = NULL;
+
+   /*
+    * Recursively flatten implicit RowExprs. (Technically this is only
+    * needed for GROUP BY, per the syntax rules for grouping sets, but
+    * we do it anyway.)
+    */
+   flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist,
+                                                   true,
+                                                   &hasGroupingSets);
+
+   /*
+    * If the list is now empty, but hasGroupingSets is true, it's because
+    * we elided redundant empty grouping sets. Restore a single empty
+    * grouping set to leave a canonical form: GROUP BY ()
+    */
+
+   if (flat_grouplist == NIL && hasGroupingSets)
+   {
+       flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY,
+                                                   NIL,
+                                                   exprLocation((Node *) grouplist)));
+   }
+
+   foreach(gl, flat_grouplist)
+   {
+       Node        *gexpr = (Node *) lfirst(gl);
+
+       if (IsA(gexpr, GroupingSet))
+       {
+           GroupingSet *gset = (GroupingSet *) gexpr;
+
+           switch (gset->kind)
+           {
+               case GROUPING_SET_EMPTY:
+                   gsets = lappend(gsets, gset);
+                   break;
+               case GROUPING_SET_SIMPLE:
+                   /* can't happen */
+                   Assert(false);
+                   break;
+               case GROUPING_SET_SETS:
+               case GROUPING_SET_CUBE:
+               case GROUPING_SET_ROLLUP:
+                   gsets = lappend(gsets,
+                                   transformGroupingSet(&result,
+                                                        pstate, gset,
+                                                        targetlist, sortClause,
+                                                        exprKind, useSQL99, true));
                    break;
-               }
            }
        }
+       else
+       {
+           Index ref = transformGroupClauseExpr(&result, seen_local,
+                                                pstate, gexpr,
+                                                targetlist, sortClause,
+                                                exprKind, useSQL99, true);
 
-       /*
-        * If no match in ORDER BY, just add it to the result using default
-        * sort/group semantics.
-        */
-       if (!found)
-           result = addTargetToGroupList(pstate, tle,
-                                         result, *targetlist,
-                                         exprLocation(gexpr),
-                                         true);
+           if (ref > 0)
+           {
+               seen_local = bms_add_member(seen_local, ref);
+               if (hasGroupingSets)
+                   gsets = lappend(gsets,
+                                   makeGroupingSet(GROUPING_SET_SIMPLE,
+                                                   list_make1_int(ref),
+                                                   exprLocation(gexpr)));
+           }
+       }
    }
 
+   /* parser should prevent this */
+   Assert(gsets == NIL || groupingSets != NULL);
+
+   if (groupingSets)
+       *groupingSets = gsets;
+
    return result;
 }
 
@@ -1847,6 +2263,7 @@ transformWindowDefinitions(ParseState *pstate,
                                          true /* force SQL99 rules */ );
        partitionClause = transformGroupClause(pstate,
                                               windef->partitionClause,
+                                              NULL,
                                               targetlist,
                                               orderClause,
                                               EXPR_KIND_WINDOW_PARTITION,
index f759606f88bc9c53a5527c60cdcb3d75fbc5cc10..0ff46dd457c7938bbd7ad022ea5a5f2cf115eb62 100644 (file)
@@ -32,6 +32,7 @@
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "parser/parse_type.h"
+#include "parser/parse_agg.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/xml.h"
@@ -269,6 +270,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
            result = transformMultiAssignRef(pstate, (MultiAssignRef *) expr);
            break;
 
+       case T_GroupingFunc:
+           result = transformGroupingFunc(pstate, (GroupingFunc *) expr);
+           break;
+
        case T_NamedArgExpr:
            {
                NamedArgExpr *na = (NamedArgExpr *) expr;
index 59973ba9c3c3403612bfd33239cf57825a882477..1b3fcd629c109194bb8eb6831407192a7a7914de 100644 (file)
@@ -1681,6 +1681,10 @@ FigureColnameInternal(Node *node, char **name)
            break;
        case T_CollateClause:
            return FigureColnameInternal(((CollateClause *) node)->arg, name);
+       case T_GroupingFunc:
+           /* make GROUPING() act like a regular function */
+           *name = "grouping";
+           return 2;
        case T_SubLink:
            switch (((SubLink *) node)->subLinkType)
            {
index 39302a410b8be05b7628759d87c442093333fd5b..84fbf2e266554517c9168cac9ec42dd1cbe549d0 100644 (file)
@@ -2158,7 +2158,7 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
    if (viewquery->distinctClause != NIL)
        return gettext_noop("Views containing DISTINCT are not automatically updatable.");
 
-   if (viewquery->groupClause != NIL)
+   if (viewquery->groupClause != NIL || viewquery->groupingSets)
        return gettext_noop("Views containing GROUP BY are not automatically updatable.");
 
    if (viewquery->havingQual != NULL)
index df457080fea689621335fa4adb8a6486481bc03a..83090103c7958df30848bb5f9cceb1c8d06a88fa 100644 (file)
@@ -92,6 +92,12 @@ contain_aggs_of_level_walker(Node *node,
            return true;        /* abort the tree traversal and return true */
        /* else fall through to examine argument */
    }
+   if (IsA(node, GroupingFunc))
+   {
+       if (((GroupingFunc *) node)->agglevelsup == context->sublevels_up)
+           return true;
+       /* else fall through to examine argument */
+   }
    if (IsA(node, Query))
    {
        /* Recurse into subselects */
@@ -157,6 +163,15 @@ locate_agg_of_level_walker(Node *node,
        }
        /* else fall through to examine argument */
    }
+   if (IsA(node, GroupingFunc))
+   {
+       if (((GroupingFunc *) node)->agglevelsup == context->sublevels_up &&
+           ((GroupingFunc *) node)->location >= 0)
+       {
+           context->agg_location = ((GroupingFunc *) node)->location;
+           return true;        /* abort the tree traversal and return true */
+       }
+   }
    if (IsA(node, Query))
    {
        /* Recurse into subselects */
@@ -703,6 +718,14 @@ IncrementVarSublevelsUp_walker(Node *node,
            agg->agglevelsup += context->delta_sublevels_up;
        /* fall through to recurse into argument */
    }
+   if (IsA(node, GroupingFunc))
+   {
+       GroupingFunc   *grp = (GroupingFunc *) node;
+
+       if (grp->agglevelsup >= context->min_sublevels_up)
+           grp->agglevelsup += context->delta_sublevels_up;
+       /* fall through to recurse into argument */
+   }
    if (IsA(node, PlaceHolderVar))
    {
        PlaceHolderVar *phv = (PlaceHolderVar *) node;
index 156b5331f36fb1965c3185770b9a23ddbc0028e7..4e7b7a12c84553fa4f08f99e1814fd299c2af5d2 100644 (file)
@@ -43,6 +43,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/tlist.h"
 #include "parser/keywords.h"
+#include "parser/parse_node.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
@@ -104,6 +105,8 @@ typedef struct
    int         wrapColumn;     /* max line length, or -1 for no limit */
    int         indentLevel;    /* current indent level for prettyprint */
    bool        varprefix;      /* TRUE to print prefixes on Vars */
+   ParseExprKind special_exprkind; /* set only for exprkinds needing */
+                                   /* special handling */
 } deparse_context;
 
 /*
@@ -366,9 +369,11 @@ static void get_target_list(List *targetList, deparse_context *context,
 static void get_setop_query(Node *setOp, Query *query,
                deparse_context *context,
                TupleDesc resultDesc);
-static Node *get_rule_sortgroupclause(SortGroupClause *srt, List *tlist,
+static Node *get_rule_sortgroupclause(Index ref, List *tlist,
                         bool force_colno,
                         deparse_context *context);
+static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
+                                bool omit_parens, deparse_context *context);
 static void get_rule_orderby(List *orderList, List *targetList,
                 bool force_colno, deparse_context *context);
 static void get_rule_windowclause(Query *query, deparse_context *context);
@@ -416,8 +421,9 @@ static void printSubscripts(ArrayRef *aref, deparse_context *context);
 static char *get_relation_name(Oid relid);
 static char *generate_relation_name(Oid relid, List *namespaces);
 static char *generate_function_name(Oid funcid, int nargs,
-                      List *argnames, Oid *argtypes,
-                      bool has_variadic, bool *use_variadic_p);
+                           List *argnames, Oid *argtypes,
+                           bool has_variadic, bool *use_variadic_p,
+                           ParseExprKind special_exprkind);
 static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
 static text *string_to_text(char *str);
 static char *flatten_reloptions(Oid relid);
@@ -875,6 +881,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
        context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
        context.wrapColumn = WRAP_COLUMN_DEFAULT;
        context.indentLevel = PRETTYINDENT_STD;
+       context.special_exprkind = EXPR_KIND_NONE;
 
        get_rule_expr(qual, &context, false);
 
@@ -884,7 +891,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
    appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
                     generate_function_name(trigrec->tgfoid, 0,
                                            NIL, NULL,
-                                           false, NULL));
+                                           false, NULL, EXPR_KIND_NONE));
 
    if (trigrec->tgnargs > 0)
    {
@@ -2499,6 +2506,7 @@ deparse_expression_pretty(Node *expr, List *dpcontext,
    context.prettyFlags = prettyFlags;
    context.wrapColumn = WRAP_COLUMN_DEFAULT;
    context.indentLevel = startIndent;
+   context.special_exprkind = EXPR_KIND_NONE;
 
    get_rule_expr(expr, &context, showimplicit);
 
@@ -4109,6 +4117,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
        context.prettyFlags = prettyFlags;
        context.wrapColumn = WRAP_COLUMN_DEFAULT;
        context.indentLevel = PRETTYINDENT_STD;
+       context.special_exprkind = EXPR_KIND_NONE;
 
        set_deparse_for_query(&dpns, query, NIL);
 
@@ -4260,6 +4269,7 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
    context.prettyFlags = prettyFlags;
    context.wrapColumn = wrapColumn;
    context.indentLevel = startIndent;
+   context.special_exprkind = EXPR_KIND_NONE;
 
    set_deparse_for_query(&dpns, query, parentnamespace);
 
@@ -4630,7 +4640,7 @@ get_basic_select_query(Query *query, deparse_context *context,
                SortGroupClause *srt = (SortGroupClause *) lfirst(l);
 
                appendStringInfoString(buf, sep);
-               get_rule_sortgroupclause(srt, query->targetList,
+               get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
                                         false, context);
                sep = ", ";
            }
@@ -4655,20 +4665,43 @@ get_basic_select_query(Query *query, deparse_context *context,
    }
 
    /* Add the GROUP BY clause if given */
-   if (query->groupClause != NULL)
+   if (query->groupClause != NULL || query->groupingSets != NULL)
    {
+       ParseExprKind   save_exprkind;
+
        appendContextKeyword(context, " GROUP BY ",
                             -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
-       sep = "";
-       foreach(l, query->groupClause)
+
+       save_exprkind = context->special_exprkind;
+       context->special_exprkind = EXPR_KIND_GROUP_BY;
+
+       if (query->groupingSets == NIL)
        {
-           SortGroupClause *grp = (SortGroupClause *) lfirst(l);
+           sep = "";
+           foreach(l, query->groupClause)
+           {
+               SortGroupClause *grp = (SortGroupClause *) lfirst(l);
 
-           appendStringInfoString(buf, sep);
-           get_rule_sortgroupclause(grp, query->targetList,
-                                    false, context);
-           sep = ", ";
+               appendStringInfoString(buf, sep);
+               get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList,
+                                        false, context);
+               sep = ", ";
+           }
        }
+       else
+       {
+           sep = "";
+           foreach(l, query->groupingSets)
+           {
+               GroupingSet *grp = lfirst(l);
+
+               appendStringInfoString(buf, sep);
+               get_rule_groupingset(grp, query->targetList, true, context);
+               sep = ", ";
+           }
+       }
+
+       context->special_exprkind = save_exprkind;
    }
 
    /* Add the HAVING clause if given */
@@ -4735,7 +4768,7 @@ get_target_list(List *targetList, deparse_context *context,
         * different from a whole-row Var).  We need to call get_variable
         * directly so that we can tell it to do the right thing.
         */
-       if (tle->expr && IsA(tle->expr, Var))
+       if (tle->expr && (IsA(tle->expr, Var) || IsA(tle->expr, GroupedVar)))
        {
            attname = get_variable((Var *) tle->expr, 0, true, context);
        }
@@ -4954,23 +4987,24 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
  * Also returns the expression tree, so caller need not find it again.
  */
 static Node *
-get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
+get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
                         deparse_context *context)
 {
    StringInfo  buf = context->buf;
    TargetEntry *tle;
    Node       *expr;
 
-   tle = get_sortgroupclause_tle(srt, tlist);
+   tle = get_sortgroupref_tle(ref, tlist);
    expr = (Node *) tle->expr;
 
    /*
-    * Use column-number form if requested by caller.  Otherwise, if
-    * expression is a constant, force it to be dumped with an explicit cast
-    * as decoration --- this is because a simple integer constant is
-    * ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
-    * dump it without any decoration.  Otherwise, just dump the expression
-    * normally.
+    * Use column-number form if requested by caller.  Otherwise, if expression
+    * is a constant, force it to be dumped with an explicit cast as decoration
+    * --- this is because a simple integer constant is ambiguous (and will be
+    * misinterpreted by findTargetlistEntry()) if we dump it without any
+    * decoration.  If it's anything more complex than a simple Var, then force
+    * extra parens around it, to ensure it can't be misinterpreted as a cube()
+    * or rollup() construct.
     */
    if (force_colno)
    {
@@ -4979,12 +5013,91 @@ get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
    }
    else if (expr && IsA(expr, Const))
        get_const_expr((Const *) expr, context, 1);
+   else if (!expr || IsA(expr, Var))
+       get_rule_expr(expr, context, true);
    else
+   {
+       /*
+        * We must force parens for function-like expressions even if
+        * PRETTY_PAREN is off, since those are the ones in danger of
+        * misparsing. For other expressions we need to force them
+        * only if PRETTY_PAREN is on, since otherwise the expression
+        * will output them itself. (We can't skip the parens.)
+        */
+       bool    need_paren = (PRETTY_PAREN(context)
+                             || IsA(expr, FuncExpr)
+                             || IsA(expr, Aggref)
+                             || IsA(expr, WindowFunc));
+       if (need_paren)
+           appendStringInfoString(context->buf, "(");
        get_rule_expr(expr, context, true);
+       if (need_paren)
+           appendStringInfoString(context->buf, ")");
+   }
 
    return expr;
 }
 
+/*
+ * Display a GroupingSet
+ */
+static void
+get_rule_groupingset(GroupingSet *gset, List *targetlist,
+                    bool omit_parens, deparse_context *context)
+{
+   ListCell   *l;
+   StringInfo  buf = context->buf;
+   bool        omit_child_parens = true;
+   char       *sep = "";
+
+   switch (gset->kind)
+   {
+       case GROUPING_SET_EMPTY:
+           appendStringInfoString(buf, "()");
+           return;
+
+       case GROUPING_SET_SIMPLE:
+           {
+               if (!omit_parens || list_length(gset->content) != 1)
+                   appendStringInfoString(buf, "(");
+
+               foreach(l, gset->content)
+               {
+                   Index ref = lfirst_int(l);
+
+                   appendStringInfoString(buf, sep);
+                   get_rule_sortgroupclause(ref, targetlist,
+                                            false, context);
+                   sep = ", ";
+               }
+
+               if (!omit_parens || list_length(gset->content) != 1)
+                   appendStringInfoString(buf, ")");
+           }
+           return;
+
+       case GROUPING_SET_ROLLUP:
+           appendStringInfoString(buf, "ROLLUP(");
+           break;
+       case GROUPING_SET_CUBE:
+           appendStringInfoString(buf, "CUBE(");
+           break;
+       case GROUPING_SET_SETS:
+           appendStringInfoString(buf, "GROUPING SETS (");
+           omit_child_parens = false;
+           break;
+   }
+
+   foreach(l, gset->content)
+   {
+       appendStringInfoString(buf, sep);
+       get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context);
+       sep = ", ";
+   }
+
+   appendStringInfoString(buf, ")");
+}
+
 /*
  * Display an ORDER BY list.
  */
@@ -5005,7 +5118,7 @@ get_rule_orderby(List *orderList, List *targetList,
        TypeCacheEntry *typentry;
 
        appendStringInfoString(buf, sep);
-       sortexpr = get_rule_sortgroupclause(srt, targetList,
+       sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
                                            force_colno, context);
        sortcoltype = exprType(sortexpr);
        /* See whether operator is default < or > for datatype */
@@ -5105,7 +5218,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
            SortGroupClause *grp = (SortGroupClause *) lfirst(l);
 
            appendStringInfoString(buf, sep);
-           get_rule_sortgroupclause(grp, targetList,
+           get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
                                     false, context);
            sep = ", ";
        }
@@ -5695,10 +5808,10 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
         * Force parentheses because our caller probably assumed a Var is a
         * simple expression.
         */
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, '(');
        get_rule_expr((Node *) tle->expr, context, true);
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, ')');
 
        pop_child_plan(dpns, &save_dpns);
@@ -5720,10 +5833,10 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
         * Force parentheses because our caller probably assumed a Var is a
         * simple expression.
         */
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, '(');
        get_rule_expr((Node *) tle->expr, context, true);
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, ')');
 
        pop_child_plan(dpns, &save_dpns);
@@ -5743,10 +5856,10 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
         * Force parentheses because our caller probably assumed a Var is a
         * simple expression.
         */
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, '(');
        get_rule_expr((Node *) tle->expr, context, true);
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, ')');
 
        return NULL;
@@ -5786,10 +5899,10 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
         * Force parentheses because our caller probably assumed a Var is a
         * simple expression.
         */
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, '(');
        get_rule_expr((Node *) tle->expr, context, true);
-       if (!IsA(tle->expr, Var))
+       if (!IsA(tle->expr, Var) && !IsA(tle->expr, GroupedVar))
            appendStringInfoChar(buf, ')');
 
        pop_child_plan(dpns, &save_dpns);
@@ -6820,6 +6933,10 @@ get_rule_expr(Node *node, deparse_context *context,
            (void) get_variable((Var *) node, 0, false, context);
            break;
 
+       case T_GroupedVar:
+           (void) get_variable((Var *) node, 0, false, context);
+           break;
+
        case T_Const:
            get_const_expr((Const *) node, context, 0);
            break;
@@ -6832,6 +6949,16 @@ get_rule_expr(Node *node, deparse_context *context,
            get_agg_expr((Aggref *) node, context);
            break;
 
+       case T_GroupingFunc:
+           {
+               GroupingFunc *gexpr = (GroupingFunc *) node;
+
+               appendStringInfoString(buf, "GROUPING(");
+               get_rule_expr((Node *) gexpr->args, context, true);
+               appendStringInfoChar(buf, ')');
+           }
+           break;
+
        case T_WindowFunc:
            get_windowfunc_expr((WindowFunc *) node, context);
            break;
@@ -7870,7 +7997,8 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
                     generate_function_name(funcoid, nargs,
                                            argnames, argtypes,
                                            expr->funcvariadic,
-                                           &use_variadic));
+                                           &use_variadic,
+                                           context->special_exprkind));
    nargs = 0;
    foreach(l, expr->args)
    {
@@ -7902,7 +8030,8 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
                     generate_function_name(aggref->aggfnoid, nargs,
                                            NIL, argtypes,
                                            aggref->aggvariadic,
-                                           &use_variadic),
+                                           &use_variadic,
+                                           context->special_exprkind),
                     (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
 
    if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
@@ -7992,7 +8121,8 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
    appendStringInfo(buf, "%s(",
                     generate_function_name(wfunc->winfnoid, nargs,
                                            argnames, argtypes,
-                                           false, NULL));
+                                           false, NULL,
+                                           context->special_exprkind));
    /* winstar can be set only in zero-argument aggregates */
    if (wfunc->winstar)
        appendStringInfoChar(buf, '*');
@@ -9241,7 +9371,8 @@ generate_relation_name(Oid relid, List *namespaces)
  */
 static char *
 generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
-                      bool has_variadic, bool *use_variadic_p)
+                      bool has_variadic, bool *use_variadic_p,
+                      ParseExprKind special_exprkind)
 {
    char       *result;
    HeapTuple   proctup;
@@ -9256,6 +9387,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
    int         p_nvargs;
    Oid         p_vatype;
    Oid        *p_true_typeids;
+   bool        force_qualify = false;
 
    proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
    if (!HeapTupleIsValid(proctup))
@@ -9263,6 +9395,17 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
    procform = (Form_pg_proc) GETSTRUCT(proctup);
    proname = NameStr(procform->proname);
 
+   /*
+    * Thanks to parser hacks to avoid needing to reserve CUBE, we
+    * need to force qualification in some special cases.
+    */
+
+   if (special_exprkind == EXPR_KIND_GROUP_BY)
+   {
+       if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
+           force_qualify = true;
+   }
+
    /*
     * Determine whether VARIADIC should be printed.  We must do this first
     * since it affects the lookup rules in func_get_detail().
@@ -9294,14 +9437,23 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
    /*
     * The idea here is to schema-qualify only if the parser would fail to
     * resolve the correct function given the unqualified func name with the
-    * specified argtypes and VARIADIC flag.
+    * specified argtypes and VARIADIC flag.  But if we already decided to
+    * force qualification, then we can skip the lookup and pretend we didn't
+    * find it.
     */
-   p_result = func_get_detail(list_make1(makeString(proname)),
-                              NIL, argnames, nargs, argtypes,
-                              !use_variadic, true,
-                              &p_funcid, &p_rettype,
-                              &p_retset, &p_nvargs, &p_vatype,
-                              &p_true_typeids, NULL);
+   if (!force_qualify)
+       p_result = func_get_detail(list_make1(makeString(proname)),
+                                  NIL, argnames, nargs, argtypes,
+                                  !use_variadic, true,
+                                  &p_funcid, &p_rettype,
+                                  &p_retset, &p_nvargs, &p_vatype,
+                                  &p_true_typeids, NULL);
+   else
+   {
+       p_result = FUNCDETAIL_NOTFOUND;
+       p_funcid = InvalidOid;
+   }
+
    if ((p_result == FUNCDETAIL_NORMAL ||
         p_result == FUNCDETAIL_AGGREGATE ||
         p_result == FUNCDETAIL_WINDOWFUNC) &&
index 91399f79fc80273c4978f4520abe566fa5dd8ed3..e0f6557198e356648b7035b1e2989614aaa39faa 100644 (file)
@@ -3158,6 +3158,8 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
  * groupExprs - list of expressions being grouped by
  * input_rows - number of rows estimated to arrive at the group/unique
  *     filter step
+ *  pgset - NULL, or a List** pointing to a grouping set to filter the
+ *      groupExprs against
  *
  * Given the lack of any cross-correlation statistics in the system, it's
  * impossible to do anything really trustworthy with GROUP BY conditions
@@ -3205,11 +3207,13 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
  * but we don't have the info to do better).
  */
 double
-estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
+estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
+                   List **pgset)
 {
    List       *varinfos = NIL;
    double      numdistinct;
    ListCell   *l;
+   int         i;
 
    /*
     * We don't ever want to return an estimate of zero groups, as that tends
@@ -3224,7 +3228,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
     * for normal cases with GROUP BY or DISTINCT, but it is possible for
     * corner cases with set operations.)
     */
-   if (groupExprs == NIL)
+   if (groupExprs == NIL || (pgset && list_length(*pgset) < 1))
        return 1.0;
 
    /*
@@ -3236,6 +3240,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
     */
    numdistinct = 1.0;
 
+   i = 0;
    foreach(l, groupExprs)
    {
        Node       *groupexpr = (Node *) lfirst(l);
@@ -3243,6 +3248,10 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
        List       *varshere;
        ListCell   *l2;
 
+       /* is expression in this grouping set? */
+       if (pgset && !list_member_int(*pgset, i++))
+           continue;
+
        /* Short-circuit for expressions returning boolean */
        if (exprType(groupexpr) == BOOLOID)
        {
index c9f722361a128f819504a6f62ec4d18699828958..4df44d024208dbca53ff3d8edd85511f90b21b87 100644 (file)
@@ -83,6 +83,8 @@ extern void ExplainSeparatePlans(ExplainState *es);
 
 extern void ExplainPropertyList(const char *qlabel, List *data,
                    ExplainState *es);
+extern void ExplainPropertyListNested(const char *qlabel, List *data,
+                   ExplainState *es);
 extern void ExplainPropertyText(const char *qlabel, const char *value,
                    ExplainState *es);
 extern void ExplainPropertyInteger(const char *qlabel, int value,
index 9de6d1484eb7a22e9ea0a3f36738df8b31a20d7a..d601b62314012c6a87eb6e198bffcd932007d8a5 100644 (file)
@@ -136,6 +136,8 @@ typedef struct ExprContext
    Datum      *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */
    bool       *ecxt_aggnulls;  /* null flags for aggs/windowfuncs */
 
+   Bitmapset  *grouped_cols;   /* which columns exist in current grouping set */
+
    /* Value to substitute for CaseTestExpr nodes in expression */
    Datum       caseValue_datum;
    bool        caseValue_isNull;
@@ -417,6 +419,11 @@ typedef struct EState
    HeapTuple  *es_epqTuple;    /* array of EPQ substitute tuples */
    bool       *es_epqTupleSet; /* true if EPQ tuple is provided */
    bool       *es_epqScanDone; /* true if EPQ tuple has been fetched */
+
+   /*
+    * This is for linking chained aggregate nodes
+    */
+   struct AggState    *agg_chain_head;
 } EState;
 
 
@@ -607,6 +614,21 @@ typedef struct AggrefExprState
    int         aggno;          /* ID number for agg within its plan node */
 } AggrefExprState;
 
+/* ----------------
+ *     GroupingFuncExprState node
+ *
+ * The list of column numbers refers to the input tuples of the Agg node to
+ * which the GroupingFunc belongs, and may contain 0 for references to columns
+ * that are only present in grouping sets processed by different Agg nodes (and
+ * which are therefore always considered "grouping" here).
+ * ----------------
+ */
+typedef struct GroupingFuncExprState
+{
+   ExprState   xprstate;
+   List       *clauses;        /* integer list of column numbers */
+} GroupingFuncExprState;
+
 /* ----------------
  *     WindowFuncExprState node
  * ----------------
@@ -1761,19 +1783,27 @@ typedef struct GroupState
 /* these structs are private in nodeAgg.c: */
 typedef struct AggStatePerAggData *AggStatePerAgg;
 typedef struct AggStatePerGroupData *AggStatePerGroup;
+typedef struct AggStatePerGroupingSetData *AggStatePerGroupingSet;
 
 typedef struct AggState
 {
    ScanState   ss;             /* its first field is NodeTag */
    List       *aggs;           /* all Aggref nodes in targetlist & quals */
    int         numaggs;        /* length of list (could be zero!) */
+   int         numsets;        /* number of grouping sets (or 0) */
    FmgrInfo   *eqfunctions;    /* per-grouping-field equality fns */
    FmgrInfo   *hashfunctions;  /* per-grouping-field hash fns */
    AggStatePerAgg peragg;      /* per-Aggref information */
-   MemoryContext aggcontext;   /* memory context for long-lived data */
+   ExprContext **aggcontexts;  /* econtexts for long-lived data (per GS) */
    ExprContext *tmpcontext;    /* econtext for input expressions */
    AggStatePerAgg curperagg;   /* identifies currently active aggregate */
+   bool        input_done;     /* indicates end of input */
    bool        agg_done;       /* indicates completion of Agg scan */
+   bool        chain_done;     /* indicates completion of chained fetch */
+   int         projected_set;  /* The last projected grouping set */
+   int         current_set;    /* The current grouping set being evaluated */
+   Bitmapset **grouped_cols;   /* column groupings for rollup */
+   int        *gset_lengths;   /* lengths of grouping sets */
    /* these fields are used in AGG_PLAIN and AGG_SORTED modes: */
    AggStatePerGroup pergroup;  /* per-Aggref-per-group working state */
    HeapTuple   grp_firstTuple; /* copy of first tuple of current group */
@@ -1783,6 +1813,12 @@ typedef struct AggState
    List       *hash_needed;    /* list of columns needed in hash table */
    bool        table_filled;   /* hash table filled yet? */
    TupleHashIterator hashiter; /* for iterating through hash table */
+   int         chain_depth;    /* number of chained child nodes */
+   int         chain_rescan;   /* rescan indicator */
+   int         chain_eflags;   /* saved eflags for rewind optimization */
+   bool        chain_top;      /* true for the "top" node in a chain */
+   struct AggState *chain_head;
+   Tuplestorestate *chain_tuplestore;
 } AggState;
 
 /* ----------------
index 4dff6a09014f097f5f3a3a2186639365346560cb..01d9fedb54f78a4d029043c01d3b1dca20842741 100644 (file)
@@ -81,4 +81,6 @@ extern DefElem *makeDefElem(char *name, Node *arg);
 extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
                    DefElemAction defaction);
 
+extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+
 #endif   /* MAKEFUNC_H */
index 768f413a451db08d927635f203287d054f9e83ce..e514d0d706807a9330e3fd8cbfedf346bd5f4ddb 100644 (file)
@@ -131,9 +131,11 @@ typedef enum NodeTag
    T_RangeVar,
    T_Expr,
    T_Var,
+   T_GroupedVar,
    T_Const,
    T_Param,
    T_Aggref,
+   T_GroupingFunc,
    T_WindowFunc,
    T_ArrayRef,
    T_FuncExpr,
@@ -186,6 +188,7 @@ typedef enum NodeTag
    T_GenericExprState,
    T_WholeRowVarExprState,
    T_AggrefExprState,
+   T_GroupingFuncExprState,
    T_WindowFuncExprState,
    T_ArrayRefExprState,
    T_FuncExprState,
@@ -404,6 +407,7 @@ typedef enum NodeTag
    T_RangeTblFunction,
    T_WithCheckOption,
    T_SortGroupClause,
+   T_GroupingSet,
    T_WindowClause,
    T_PrivGrantee,
    T_FuncWithArgs,
index 91ca9c6fd0e1a465aa91f12f5636e235efd3f950..2edf07b90286d2b24e90339ca8399a2b5abee7c8 100644 (file)
@@ -138,6 +138,8 @@ typedef struct Query
 
    List       *groupClause;    /* a list of SortGroupClause's */
 
+   List       *groupingSets;   /* a list of GroupingSet's if present */
+
    Node       *havingQual;     /* qualifications applied to groups */
 
    List       *windowClause;   /* a list of WindowClause's */
@@ -964,6 +966,73 @@ typedef struct SortGroupClause
    bool        hashable;       /* can eqop be implemented by hashing? */
 } SortGroupClause;
 
+/*
+ * GroupingSet -
+ *     representation of CUBE, ROLLUP and GROUPING SETS clauses
+ *
+ * In a Query with grouping sets, the groupClause contains a flat list of
+ * SortGroupClause nodes for each distinct expression used.  The actual
+ * structure of the GROUP BY clause is given by the groupingSets tree.
+ *
+ * In the raw parser output, GroupingSet nodes (of all types except SIMPLE
+ * which is not used) are potentially mixed in with the expressions in the
+ * groupClause of the SelectStmt.  (An expression can't contain a GroupingSet,
+ * but a list may mix GroupingSet and expression nodes.)  At this stage, the
+ * content of each node is a list of expressions, some of which may be RowExprs
+ * which represent sublists rather than actual row constructors, and nested
+ * GroupingSet nodes where legal in the grammar.  The structure directly
+ * reflects the query syntax.
+ *
+ * In parse analysis, the transformed expressions are used to build the tlist
+ * and groupClause list (of SortGroupClause nodes), and the groupingSets tree
+ * is eventually reduced to a fixed format:
+ *
+ * EMPTY nodes represent (), and obviously have no content
+ *
+ * SIMPLE nodes represent a list of one or more expressions to be treated as an
+ * atom by the enclosing structure; the content is an integer list of
+ * ressortgroupref values (see SortGroupClause)
+ *
+ * CUBE and ROLLUP nodes contain a list of one or more SIMPLE nodes.
+ *
+ * SETS nodes contain a list of EMPTY, SIMPLE, CUBE or ROLLUP nodes, but after
+ * parse analysis they cannot contain more SETS nodes; enough of the syntactic
+ * transforms of the spec have been applied that we no longer have arbitrarily
+ * deep nesting (though we still preserve the use of cube/rollup).
+ *
+ * Note that if the groupingSets tree contains no SIMPLE nodes (only EMPTY
+ * nodes at the leaves), then the groupClause will be empty, but this is still
+ * an aggregation query (similar to using aggs or HAVING without GROUP BY).
+ *
+ * As an example, the following clause:
+ *
+ * GROUP BY GROUPING SETS ((a,b), CUBE(c,(d,e)))
+ *
+ * looks like this after raw parsing:
+ *
+ * SETS( RowExpr(a,b) , CUBE( c, RowExpr(d,e) ) )
+ *
+ * and parse analysis converts it to:
+ *
+ * SETS( SIMPLE(1,2), CUBE( SIMPLE(3), SIMPLE(4,5) ) )
+ */
+typedef enum
+{
+   GROUPING_SET_EMPTY,
+   GROUPING_SET_SIMPLE,
+   GROUPING_SET_ROLLUP,
+   GROUPING_SET_CUBE,
+   GROUPING_SET_SETS
+} GroupingSetKind;
+
+typedef struct GroupingSet
+{
+   NodeTag     type;
+   GroupingSetKind kind;
+   List       *content;
+   int         location;
+} GroupingSet;
+
 /*
  * WindowClause -
  *     transformed representation of WINDOW and OVER clauses
index a17500082b1ae2bd69f7661a65fed57bbf21e142..729456d6e5d12c3ebec5efe70c050408d898e96f 100644 (file)
@@ -229,8 +229,9 @@ extern List *list_union_int(const List *list1, const List *list2);
 extern List *list_union_oid(const List *list1, const List *list2);
 
 extern List *list_intersection(const List *list1, const List *list2);
+extern List *list_intersection_int(const List *list1, const List *list2);
 
-/* currently, there's no need for list_intersection_int etc */
+/* currently, there's no need for list_intersection_ptr etc */
 
 extern List *list_difference(const List *list1, const List *list2);
 extern List *list_difference_ptr(const List *list1, const List *list2);
index 9313292222afb97dd5e8b49f322073c5361d8476..97d9c31bc850ba66c926d48da5ddcb7d6f0a445d 100644 (file)
@@ -689,6 +689,7 @@ typedef enum AggStrategy
 {
    AGG_PLAIN,                  /* simple agg across all input rows */
    AGG_SORTED,                 /* grouped agg, input must be sorted */
+   AGG_CHAINED,                /* chained agg, input must be sorted */
    AGG_HASHED                  /* grouped agg, use internal hashtable */
 } AggStrategy;
 
@@ -696,10 +697,12 @@ typedef struct Agg
 {
    Plan        plan;
    AggStrategy aggstrategy;
+   int         chain_depth;    /* number of associated ChainAggs in tree */
    int         numCols;        /* number of grouping columns */
    AttrNumber *grpColIdx;      /* their indexes in the target list */
    Oid        *grpOperators;   /* equality operators to compare with */
    long        numGroups;      /* estimated number of groups in input */
+   List       *groupingSets;   /* grouping sets to use */
 } Agg;
 
 /* ----------------
index f10ae4efa881dc3a00acc659972ffa5f76d0f78f..31cf86c71a14259a624a136e3606bd42bc889b36 100644 (file)
@@ -163,6 +163,22 @@ typedef struct Var
    int         location;       /* token location, or -1 if unknown */
 } Var;
 
+/*
+ * GroupedVar - expression node representing a variable that might be
+ * involved in a grouping set.
+ *
+ * This is identical to Var node except in execution; when evaluated it
+ * is conditionally NULL depending on the active grouping set.  Vars are
+ * converted to GroupedVars (if needed) only late in planning.
+ *
+ * (Because they appear only late in planning, most code that handles Vars
+ * doesn't need to know about these, either because they don't exist yet or
+ * because optimizations specific to Vars are intentionally not applied to
+ * GroupedVars.)
+ */
+
+typedef Var GroupedVar;
+
 /*
  * Const
  */
@@ -271,6 +287,41 @@ typedef struct Aggref
    int         location;       /* token location, or -1 if unknown */
 } Aggref;
 
+/*
+ * GroupingFunc
+ *
+ * A GroupingFunc is a GROUPING(...) expression, which behaves in many ways
+ * like an aggregate function (e.g. it "belongs" to a specific query level,
+ * which might not be the one immediately containing it), but also differs in
+ * an important respect: it never evaluates its arguments, they merely
+ * designate expressions from the GROUP BY clause of the query level to which
+ * it belongs.
+ *
+ * The spec defines the evaluation of GROUPING() purely by syntactic
+ * replacement, but we make it a real expression for optimization purposes so
+ * that one Agg node can handle multiple grouping sets at once.  Evaluating the
+ * result only needs the column positions to check against the grouping set
+ * being projected.  However, for EXPLAIN to produce meaningful output, we have
+ * to keep the original expressions around, since expression deparse does not
+ * give us any feasible way to get at the GROUP BY clause.
+ *
+ * Also, we treat two GroupingFunc nodes as equal if they have equal arguments
+ * lists and agglevelsup, without comparing the refs and cols annotations.
+ *
+ * In raw parse output we have only the args list; parse analysis fills in the
+ * refs list, and the planner fills in the cols list.
+ */
+typedef struct GroupingFunc
+{
+   Expr        xpr;
+   List       *args;           /* arguments, not evaluated but kept for
+                                * benefit of EXPLAIN etc. */
+   List       *refs;           /* ressortgrouprefs of arguments */
+   List       *cols;           /* actual column positions set by planner */
+   Index       agglevelsup;    /* same as Aggref.agglevelsup */
+   int         location;       /* token location */
+} GroupingFunc;
+
 /*
  * WindowFunc
  */
index d3ee61c4d046101b1a6dcbceb6821958ee72f939..01571becc9c5700afba519eb5f9f2a5a7a7c87f3 100644 (file)
@@ -260,6 +260,11 @@ typedef struct PlannerInfo
 
    /* optional private data for join_search_hook, e.g., GEQO */
    void       *join_search_private;
+
+   /* for GroupedVar fixup in setrefs */
+   AttrNumber *groupColIdx;
+   /* for GroupingFunc fixup in setrefs */
+   AttrNumber *grouping_map;
 } PlannerInfo;
 
 
index da15fca1f6c4374062ce8ffc8aaf28c46e416915..18b14e5626c8fb886e179cf7bd516f991fd87aa5 100644 (file)
@@ -59,6 +59,8 @@ extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls,
 extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual,
         AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
         int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
+        List *groupingSets,
+        int *chain_depth_p,
         long numGroups,
         Plan *lefttree);
 extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
index 3dc8babacb58467797f3d4e2e7e85f36e59aa804..b0f0f196838768749431a9f8b4539ac61c7b5356 100644 (file)
@@ -43,6 +43,9 @@ extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
 extern List *get_sortgrouplist_exprs(List *sgClauses,
                        List *targetList);
 
+extern SortGroupClause *get_sortgroupref_clause(Index sortref,
+                    List *clauses);
+
 extern Oid *extract_grouping_ops(List *groupClause);
 extern AttrNumber *extract_grouping_cols(List *groupClause, List *tlist);
 extern bool grouping_is_sortable(List *groupClause);
index faea99108c443baf1efd1632fda67da245f6c84f..b30a1b1a5ede066ff6563a325c9e7095f685b532 100644 (file)
@@ -99,6 +99,7 @@ PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD)
+PG_KEYWORD("cube", CUBE, UNRESERVED_KEYWORD)
 PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD)
 PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD)
@@ -174,6 +175,7 @@ PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD)
 PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
 PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
 PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
+PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD)
 PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
 PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
 PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
@@ -325,6 +327,7 @@ PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD)
 PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD)
+PG_KEYWORD("rollup", ROLLUP, UNRESERVED_KEYWORD)
 PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
@@ -343,6 +346,7 @@ PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD)
 PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD)
 PG_KEYWORD("set", SET, UNRESERVED_KEYWORD)
 PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD)
+PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD)
 PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD)
 PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD)
 PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD)
index 91a0706f4593da2faba12e6f252bbfcfc8d4b68e..6a5f9bbdf1556ced6d4d2031791432711e6501c2 100644 (file)
 extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
                       List *args, List *aggorder,
                       bool agg_distinct);
+
+extern Node *transformGroupingFunc(ParseState *pstate, GroupingFunc *g);
+
 extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
                        WindowDef *windef);
 
 extern void parseCheckAggregates(ParseState *pstate, Query *qry);
 
+extern List *expand_grouping_sets(List *groupingSets, int limit);
+
 extern int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes);
 
 extern Oid resolve_aggregate_transtype(Oid aggfuncid,
index f1b7d3d8969f2e893e4dda7fc6c4aefb4d4301cc..cbe5e76bb84202641eb390ad457407b7de04f1cf 100644 (file)
@@ -27,6 +27,7 @@ extern Node *transformWhereClause(ParseState *pstate, Node *clause,
 extern Node *transformLimitClause(ParseState *pstate, Node *clause,
                     ParseExprKind exprKind, const char *constructName);
 extern List *transformGroupClause(ParseState *pstate, List *grouplist,
+                                 List **groupingSets,
                     List **targetlist, List *sortClause,
                     ParseExprKind exprKind, bool useSQL99);
 extern List *transformSortClause(ParseState *pstate, List *orderlist,
index bf69f2a62949cd87f3ecfcf9cb231e927b7fe20d..fdca7130bb07984f7176c811248d55812b645908 100644 (file)
@@ -185,7 +185,7 @@ extern void mergejoinscansel(PlannerInfo *root, Node *clause,
                 Selectivity *rightstart, Selectivity *rightend);
 
 extern double estimate_num_groups(PlannerInfo *root, List *groupExprs,
-                   double input_rows);
+                                 double input_rows, List **pgset);
 
 extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
                         double nbuckets);
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
new file mode 100644 (file)
index 0000000..fbfb424
--- /dev/null
@@ -0,0 +1,575 @@
+--
+-- grouping sets
+--
+-- test data sources
+create temp view gstest1(a,b,v)
+  as values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),
+            (2,3,15),
+            (3,3,16),(3,4,17),
+            (4,1,18),(4,1,19);
+create temp table gstest2 (a integer, b integer, c integer, d integer,
+                           e integer, f integer, g integer, h integer);
+copy gstest2 from stdin;
+create temp table gstest_empty (a integer, b integer, v integer);
+create function gstest_data(v integer, out a integer, out b integer)
+  returns setof record
+  as $f$
+    begin
+      return query select v, i from generate_series(1,3) i;
+    end;
+  $f$ language plpgsql;
+-- basic functionality
+-- simple rollup with multiple plain aggregates, with and without ordering
+-- (and with ordering differing from grouping)
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b);
+ a | b | grouping | sum | count | max 
+---+---+----------+-----+-------+-----
+ 1 | 1 |        0 |  21 |     2 |  11
+ 1 | 2 |        0 |  25 |     2 |  13
+ 1 | 3 |        0 |  14 |     1 |  14
+ 1 |   |        1 |  60 |     5 |  14
+ 2 | 3 |        0 |  15 |     1 |  15
+ 2 |   |        1 |  15 |     1 |  15
+ 3 | 3 |        0 |  16 |     1 |  16
+ 3 | 4 |        0 |  17 |     1 |  17
+ 3 |   |        1 |  33 |     2 |  17
+ 4 | 1 |        0 |  37 |     2 |  19
+ 4 |   |        1 |  37 |     2 |  19
+   |   |        3 | 145 |    10 |  19
+(12 rows)
+
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b) order by a,b;
+ a | b | grouping | sum | count | max 
+---+---+----------+-----+-------+-----
+ 1 | 1 |        0 |  21 |     2 |  11
+ 1 | 2 |        0 |  25 |     2 |  13
+ 1 | 3 |        0 |  14 |     1 |  14
+ 1 |   |        1 |  60 |     5 |  14
+ 2 | 3 |        0 |  15 |     1 |  15
+ 2 |   |        1 |  15 |     1 |  15
+ 3 | 3 |        0 |  16 |     1 |  16
+ 3 | 4 |        0 |  17 |     1 |  17
+ 3 |   |        1 |  33 |     2 |  17
+ 4 | 1 |        0 |  37 |     2 |  19
+ 4 |   |        1 |  37 |     2 |  19
+   |   |        3 | 145 |    10 |  19
+(12 rows)
+
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b) order by b desc, a;
+ a | b | grouping | sum | count | max 
+---+---+----------+-----+-------+-----
+ 1 |   |        1 |  60 |     5 |  14
+ 2 |   |        1 |  15 |     1 |  15
+ 3 |   |        1 |  33 |     2 |  17
+ 4 |   |        1 |  37 |     2 |  19
+   |   |        3 | 145 |    10 |  19
+ 3 | 4 |        0 |  17 |     1 |  17
+ 1 | 3 |        0 |  14 |     1 |  14
+ 2 | 3 |        0 |  15 |     1 |  15
+ 3 | 3 |        0 |  16 |     1 |  16
+ 1 | 2 |        0 |  25 |     2 |  13
+ 1 | 1 |        0 |  21 |     2 |  11
+ 4 | 1 |        0 |  37 |     2 |  19
+(12 rows)
+
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b) order by coalesce(a,0)+coalesce(b,0);
+ a | b | grouping | sum | count | max 
+---+---+----------+-----+-------+-----
+   |   |        3 | 145 |    10 |  19
+ 1 |   |        1 |  60 |     5 |  14
+ 1 | 1 |        0 |  21 |     2 |  11
+ 2 |   |        1 |  15 |     1 |  15
+ 3 |   |        1 |  33 |     2 |  17
+ 1 | 2 |        0 |  25 |     2 |  13
+ 1 | 3 |        0 |  14 |     1 |  14
+ 4 |   |        1 |  37 |     2 |  19
+ 4 | 1 |        0 |  37 |     2 |  19
+ 2 | 3 |        0 |  15 |     1 |  15
+ 3 | 3 |        0 |  16 |     1 |  16
+ 3 | 4 |        0 |  17 |     1 |  17
+(12 rows)
+
+-- various types of ordered aggs
+select a, b, grouping(a,b),
+       array_agg(v order by v),
+       string_agg(v::text, ':' order by v desc),
+       percentile_disc(0.5) within group (order by v),
+       rank(1,2,12) within group (order by a,b,v)
+  from gstest1 group by rollup (a,b) order by a,b;
+ a | b | grouping |            array_agg            |          string_agg           | percentile_disc | rank 
+---+---+----------+---------------------------------+-------------------------------+-----------------+------
+ 1 | 1 |        0 | {10,11}                         | 11:10                         |              10 |    3
+ 1 | 2 |        0 | {12,13}                         | 13:12                         |              12 |    1
+ 1 | 3 |        0 | {14}                            | 14                            |              14 |    1
+ 1 |   |        1 | {10,11,12,13,14}                | 14:13:12:11:10                |              12 |    3
+ 2 | 3 |        0 | {15}                            | 15                            |              15 |    1
+ 2 |   |        1 | {15}                            | 15                            |              15 |    1
+ 3 | 3 |        0 | {16}                            | 16                            |              16 |    1
+ 3 | 4 |        0 | {17}                            | 17                            |              17 |    1
+ 3 |   |        1 | {16,17}                         | 17:16                         |              16 |    1
+ 4 | 1 |        0 | {18,19}                         | 19:18                         |              18 |    1
+ 4 |   |        1 | {18,19}                         | 19:18                         |              18 |    1
+   |   |        3 | {10,11,12,13,14,15,16,17,18,19} | 19:18:17:16:15:14:13:12:11:10 |              14 |    3
+(12 rows)
+
+-- test usage of grouped columns in direct args of aggs
+select grouping(a), a, array_agg(b),
+       rank(a) within group (order by b nulls first),
+       rank(a) within group (order by b nulls last)
+  from (values (1,1),(1,4),(1,5),(3,1),(3,2)) v(a,b)
+ group by rollup (a) order by a;
+ grouping | a |  array_agg  | rank | rank 
+----------+---+-------------+------+------
+        0 | 1 | {1,4,5}     |    1 |    1
+        0 | 3 | {1,2}       |    3 |    3
+        1 |   | {1,4,5,1,2} |    1 |    6
+(3 rows)
+
+-- nesting with window functions
+select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
+  from gstest2 group by rollup (a,b) order by rsum, a, b;
+ a | b | sum | rsum 
+---+---+-----+------
+ 1 | 1 |   8 |    8
+ 1 | 2 |   2 |   10
+ 1 |   |  10 |   20
+ 2 | 2 |   2 |   22
+ 2 |   |   2 |   24
+   |   |  12 |   36
+(6 rows)
+
+-- empty input: first is 0 rows, second 1, third 3 etc.
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a);
+ a | b | sum | count 
+---+---+-----+-------
+(0 rows)
+
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),());
+ a | b | sum | count 
+---+---+-----+-------
+   |   |     |     0
+(1 row)
+
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),());
+ a | b | sum | count 
+---+---+-----+-------
+   |   |     |     0
+   |   |     |     0
+   |   |     |     0
+(3 rows)
+
+select sum(v), count(*) from gstest_empty group by grouping sets ((),(),());
+ sum | count 
+-----+-------
+     |     0
+     |     0
+     |     0
+(3 rows)
+
+-- empty input with joins tests some important code paths
+select t1.a, t2.b, sum(t1.v), count(*) from gstest_empty t1, gstest_empty t2
+ group by grouping sets ((t1.a,t2.b),());
+ a | b | sum | count 
+---+---+-----+-------
+   |   |     |     0
+(1 row)
+
+-- simple joins, var resolution, GROUPING on join vars
+select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
+  from gstest1 t1, gstest2 t2
+ group by grouping sets ((t1.a, t2.b), ());
+ a | b | grouping | sum  | max 
+---+---+----------+------+-----
+ 1 | 1 |        0 |  420 |   1
+ 1 | 2 |        0 |  120 |   2
+ 2 | 1 |        0 |  105 |   1
+ 2 | 2 |        0 |   30 |   2
+ 3 | 1 |        0 |  231 |   1
+ 3 | 2 |        0 |   66 |   2
+ 4 | 1 |        0 |  259 |   1
+ 4 | 2 |        0 |   74 |   2
+   |   |        3 | 1305 |   2
+(9 rows)
+
+select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
+  from gstest1 t1 join gstest2 t2 on (t1.a=t2.a)
+ group by grouping sets ((t1.a, t2.b), ());
+ a | b | grouping | sum | max 
+---+---+----------+-----+-----
+ 1 | 1 |        0 | 420 |   1
+ 1 | 2 |        0 |  60 |   1
+ 2 | 2 |        0 |  15 |   2
+   |   |        3 | 495 |   2
+(4 rows)
+
+select a, b, grouping(a, b), sum(t1.v), max(t2.c)
+  from gstest1 t1 join gstest2 t2 using (a,b)
+ group by grouping sets ((a, b), ());
+ a | b | grouping | sum | max 
+---+---+----------+-----+-----
+ 1 | 1 |        0 | 147 |   2
+ 1 | 2 |        0 |  25 |   2
+   |   |        3 | 172 |   2
+(3 rows)
+
+-- simple rescan tests
+select a, b, sum(v.x)
+  from (values (1),(2)) v(x), gstest_data(v.x)
+ group by rollup (a,b);
+ a | b | sum 
+---+---+-----
+ 1 | 1 |   1
+ 1 | 2 |   1
+ 1 | 3 |   1
+ 1 |   |   3
+ 2 | 1 |   2
+ 2 | 2 |   2
+ 2 | 3 |   2
+ 2 |   |   6
+   |   |   9
+(9 rows)
+
+select *
+  from (values (1),(2)) v(x),
+       lateral (select a, b, sum(v.x) from gstest_data(v.x) group by rollup (a,b)) s;
+ERROR:  aggregate functions are not allowed in FROM clause of their own query level
+LINE 3:        lateral (select a, b, sum(v.x) from gstest_data(v.x) ...
+                                     ^
+-- min max optimisation should still work with GROUP BY ()
+explain (costs off)
+  select min(unique1) from tenk1 GROUP BY ();
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Result
+   InitPlan 1 (returns $0)
+     ->  Limit
+           ->  Index Only Scan using tenk1_unique1 on tenk1
+                 Index Cond: (unique1 IS NOT NULL)
+(5 rows)
+
+-- Views with GROUPING SET queries
+CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
+  from gstest2 group by rollup ((a,b,c),(c,d));
+NOTICE:  view "gstest_view" will be a temporary view
+select pg_get_viewdef('gstest_view'::regclass, true);
+                                pg_get_viewdef                                 
+-------------------------------------------------------------------------------
+  SELECT gstest2.a,                                                           +
+     gstest2.b,                                                               +
+     GROUPING(gstest2.a, gstest2.b) AS "grouping",                            +
+     sum(gstest2.c) AS sum,                                                   +
+     count(*) AS count,                                                       +
+     max(gstest2.c) AS max                                                    +
+    FROM gstest2                                                              +
+   GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
+(1 row)
+
+-- Nested queries with 3 or more levels of nesting
+select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
+ grouping 
+----------
+        0
+        0
+        0
+(3 rows)
+
+select(select (select grouping(e,f) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
+ grouping 
+----------
+        0
+        1
+        3
+(3 rows)
+
+select(select (select grouping(c) from (values (1)) v2(c) GROUP BY c) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
+ grouping 
+----------
+        0
+        0
+        0
+(3 rows)
+
+-- Combinations of operations
+select a, b, c, d from gstest2 group by rollup(a,b),grouping sets(c,d);
+ a | b | c | d 
+---+---+---+---
+ 1 | 1 | 1 |  
+ 1 |   | 1 |  
+   |   | 1 |  
+ 1 | 1 | 2 |  
+ 1 | 2 | 2 |  
+ 1 |   | 2 |  
+ 2 | 2 | 2 |  
+ 2 |   | 2 |  
+   |   | 2 |  
+ 1 | 1 |   | 1
+ 1 |   |   | 1
+   |   |   | 1
+ 1 | 1 |   | 2
+ 1 | 2 |   | 2
+ 1 |   |   | 2
+ 2 | 2 |   | 2
+ 2 |   |   | 2
+   |   |   | 2
+(18 rows)
+
+select a, b from (values (1,2),(2,3)) v(a,b) group by a,b, grouping sets(a);
+ a | b 
+---+---
+ 1 | 2
+ 2 | 3
+(2 rows)
+
+-- Tests for chained aggregates
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2));
+ a | b | grouping | sum | count | max 
+---+---+----------+-----+-------+-----
+ 1 | 1 |        0 |  21 |     2 |  11
+ 1 | 2 |        0 |  25 |     2 |  13
+ 1 | 3 |        0 |  14 |     1 |  14
+ 2 | 3 |        0 |  15 |     1 |  15
+ 3 | 3 |        0 |  16 |     1 |  16
+ 3 | 4 |        0 |  17 |     1 |  17
+ 4 | 1 |        0 |  37 |     2 |  19
+   |   |        3 |  21 |     2 |  11
+   |   |        3 |  25 |     2 |  13
+   |   |        3 |  14 |     1 |  14
+   |   |        3 |  15 |     1 |  15
+   |   |        3 |  16 |     1 |  16
+   |   |        3 |  17 |     1 |  17
+   |   |        3 |  37 |     2 |  19
+   |   |        3 |  21 |     2 |  11
+   |   |        3 |  25 |     2 |  13
+   |   |        3 |  14 |     1 |  14
+   |   |        3 |  15 |     1 |  15
+   |   |        3 |  16 |     1 |  16
+   |   |        3 |  17 |     1 |  17
+   |   |        3 |  37 |     2 |  19
+(21 rows)
+
+select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP((e+1),(f+1));
+ grouping 
+----------
+        0
+        0
+        0
+(3 rows)
+
+select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY CUBE((e+1),(f+1)) ORDER BY (e+1),(f+1);
+ grouping 
+----------
+        0
+        0
+        0
+        0
+(4 rows)
+
+select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
+  from gstest2 group by cube (a,b) order by rsum, a, b;
+ a | b | sum | rsum 
+---+---+-----+------
+ 1 | 1 |   8 |    8
+ 1 | 2 |   2 |   10
+ 1 |   |  10 |   20
+ 2 | 2 |   2 |   22
+ 2 |   |   2 |   24
+   | 1 |   8 |   32
+   | 2 |   4 |   36
+   |   |  12 |   48
+(8 rows)
+
+select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b);
+ a | b | sum 
+---+---+-----
+ 1 | 1 |  21
+ 1 | 2 |  25
+ 1 | 3 |  14
+ 1 |   |  60
+ 2 | 3 |  15
+ 2 |   |  15
+ 3 | 3 |  16
+ 3 | 4 |  17
+ 3 |   |  33
+ 4 | 1 |  37
+ 4 |   |  37
+   |   | 145
+(12 rows)
+
+select a, b, sum(v.x)
+  from (values (1),(2)) v(x), gstest_data(v.x)
+ group by cube (a,b) order by a,b;
+ a | b | sum 
+---+---+-----
+ 1 | 1 |   1
+ 1 | 2 |   1
+ 1 | 3 |   1
+ 1 |   |   3
+ 2 | 1 |   2
+ 2 | 2 |   2
+ 2 | 3 |   2
+ 2 |   |   6
+   | 1 |   3
+   | 2 |   3
+   | 3 |   3
+   |   |   9
+(12 rows)
+
+-- Agg level check. This query should error out.
+select (select grouping(a,b) from gstest2) from gstest2 group by a,b;
+ERROR:  Arguments to GROUPING must be grouping expressions of the associated query level
+LINE 1: select (select grouping(a,b) from gstest2) from gstest2 grou...
+                                ^
+--Nested queries
+select a, b, sum(c), count(*) from gstest2 group by grouping sets (rollup(a,b),a);
+ a | b | sum | count 
+---+---+-----+-------
+ 1 | 1 |   8 |     7
+ 1 | 2 |   2 |     1
+ 1 |   |  10 |     8
+ 1 |   |  10 |     8
+ 2 | 2 |   2 |     1
+ 2 |   |   2 |     1
+ 2 |   |   2 |     1
+   |   |  12 |     9
+(8 rows)
+
+-- HAVING queries
+select ten, sum(distinct four) from onek a
+group by grouping sets((ten,four),(ten))
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+ ten | sum 
+-----+-----
+   0 |   0
+   0 |   2
+   0 |   2
+   1 |   1
+   1 |   3
+   2 |   0
+   2 |   2
+   2 |   2
+   3 |   1
+   3 |   3
+   4 |   0
+   4 |   2
+   4 |   2
+   5 |   1
+   5 |   3
+   6 |   0
+   6 |   2
+   6 |   2
+   7 |   1
+   7 |   3
+   8 |   0
+   8 |   2
+   8 |   2
+   9 |   1
+   9 |   3
+(25 rows)
+
+-- FILTER queries
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by rollup(ten);
+ ten | sum 
+-----+-----
+   0 |    
+   1 |    
+   2 |    
+   3 |    
+   4 |    
+   5 |    
+   6 |    
+   7 |    
+   8 |    
+   9 |    
+     |    
+(11 rows)
+
+-- More rescan tests
+select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten;
+ a | a | four | ten | count 
+---+---+------+-----+-------
+ 1 | 1 |    0 |   0 |    50
+ 1 | 1 |    0 |   2 |    50
+ 1 | 1 |    0 |   4 |    50
+ 1 | 1 |    0 |   6 |    50
+ 1 | 1 |    0 |   8 |    50
+ 1 | 1 |    0 |     |   250
+ 1 | 1 |    1 |   1 |    50
+ 1 | 1 |    1 |   3 |    50
+ 1 | 1 |    1 |   5 |    50
+ 1 | 1 |    1 |   7 |    50
+ 1 | 1 |    1 |   9 |    50
+ 1 | 1 |    1 |     |   250
+ 1 | 1 |    2 |   0 |    50
+ 1 | 1 |    2 |   2 |    50
+ 1 | 1 |    2 |   4 |    50
+ 1 | 1 |    2 |   6 |    50
+ 1 | 1 |    2 |   8 |    50
+ 1 | 1 |    2 |     |   250
+ 1 | 1 |    3 |   1 |    50
+ 1 | 1 |    3 |   3 |    50
+ 1 | 1 |    3 |   5 |    50
+ 1 | 1 |    3 |   7 |    50
+ 1 | 1 |    3 |   9 |    50
+ 1 | 1 |    3 |     |   250
+ 1 | 1 |      |   0 |   100
+ 1 | 1 |      |   1 |   100
+ 1 | 1 |      |   2 |   100
+ 1 | 1 |      |   3 |   100
+ 1 | 1 |      |   4 |   100
+ 1 | 1 |      |   5 |   100
+ 1 | 1 |      |   6 |   100
+ 1 | 1 |      |   7 |   100
+ 1 | 1 |      |   8 |   100
+ 1 | 1 |      |   9 |   100
+ 1 | 1 |      |     |  1000
+ 2 | 2 |    0 |   0 |    50
+ 2 | 2 |    0 |   2 |    50
+ 2 | 2 |    0 |   4 |    50
+ 2 | 2 |    0 |   6 |    50
+ 2 | 2 |    0 |   8 |    50
+ 2 | 2 |    0 |     |   250
+ 2 | 2 |    1 |   1 |    50
+ 2 | 2 |    1 |   3 |    50
+ 2 | 2 |    1 |   5 |    50
+ 2 | 2 |    1 |   7 |    50
+ 2 | 2 |    1 |   9 |    50
+ 2 | 2 |    1 |     |   250
+ 2 | 2 |    2 |   0 |    50
+ 2 | 2 |    2 |   2 |    50
+ 2 | 2 |    2 |   4 |    50
+ 2 | 2 |    2 |   6 |    50
+ 2 | 2 |    2 |   8 |    50
+ 2 | 2 |    2 |     |   250
+ 2 | 2 |    3 |   1 |    50
+ 2 | 2 |    3 |   3 |    50
+ 2 | 2 |    3 |   5 |    50
+ 2 | 2 |    3 |   7 |    50
+ 2 | 2 |    3 |   9 |    50
+ 2 | 2 |    3 |     |   250
+ 2 | 2 |      |   0 |   100
+ 2 | 2 |      |   1 |   100
+ 2 | 2 |      |   2 |   100
+ 2 | 2 |      |   3 |   100
+ 2 | 2 |      |   4 |   100
+ 2 | 2 |      |   5 |   100
+ 2 | 2 |      |   6 |   100
+ 2 | 2 |      |   7 |   100
+ 2 | 2 |      |   8 |   100
+ 2 | 2 |      |   9 |   100
+ 2 | 2 |      |     |  1000
+(70 rows)
+
+select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a);
+                                                                        array                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ {"(1,0,0,250)","(1,0,2,250)","(1,0,,500)","(1,1,1,250)","(1,1,3,250)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)","(1,,,1000)"}
+ {"(2,0,0,250)","(2,0,2,250)","(2,0,,500)","(2,1,1,250)","(2,1,3,250)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)","(2,,,1000)"}
+(2 rows)
+
+-- end
index b0ebb6b3f4c657ce8030434ea0a846948226d988..69a81301add6337b1a1c1f2869d3687854d70b64 100644 (file)
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address groupingsets
 
 # ----------
 # Another group of parallel tests
index 8409c0f3ef205b3e5e178013e62af56d02cd7c18..d1c3749c47ab23eb9eb6036941eb431f34246bb7 100644 (file)
@@ -86,6 +86,7 @@ test: union
 test: case
 test: join
 test: aggregates
+test: groupingsets
 test: transactions
 ignore: random
 test: random
diff --git a/src/test/regress/sql/groupingsets.sql b/src/test/regress/sql/groupingsets.sql
new file mode 100644 (file)
index 0000000..aebcbbb
--- /dev/null
@@ -0,0 +1,153 @@
+--
+-- grouping sets
+--
+
+-- test data sources
+
+create temp view gstest1(a,b,v)
+  as values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),
+            (2,3,15),
+            (3,3,16),(3,4,17),
+            (4,1,18),(4,1,19);
+
+create temp table gstest2 (a integer, b integer, c integer, d integer,
+                           e integer, f integer, g integer, h integer);
+copy gstest2 from stdin;
+1  1   1   1   1   1   1   1
+1  1   1   1   1   1   1   2
+1  1   1   1   1   1   2   2
+1  1   1   1   1   2   2   2
+1  1   1   1   2   2   2   2
+1  1   1   2   2   2   2   2
+1  1   2   2   2   2   2   2
+1  2   2   2   2   2   2   2
+2  2   2   2   2   2   2   2
+\.
+
+create temp table gstest_empty (a integer, b integer, v integer);
+
+create function gstest_data(v integer, out a integer, out b integer)
+  returns setof record
+  as $f$
+    begin
+      return query select v, i from generate_series(1,3) i;
+    end;
+  $f$ language plpgsql;
+
+-- basic functionality
+
+-- simple rollup with multiple plain aggregates, with and without ordering
+-- (and with ordering differing from grouping)
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b);
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b) order by a,b;
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b) order by b desc, a;
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by rollup (a,b) order by coalesce(a,0)+coalesce(b,0);
+
+-- various types of ordered aggs
+select a, b, grouping(a,b),
+       array_agg(v order by v),
+       string_agg(v::text, ':' order by v desc),
+       percentile_disc(0.5) within group (order by v),
+       rank(1,2,12) within group (order by a,b,v)
+  from gstest1 group by rollup (a,b) order by a,b;
+
+-- test usage of grouped columns in direct args of aggs
+select grouping(a), a, array_agg(b),
+       rank(a) within group (order by b nulls first),
+       rank(a) within group (order by b nulls last)
+  from (values (1,1),(1,4),(1,5),(3,1),(3,2)) v(a,b)
+ group by rollup (a) order by a;
+
+-- nesting with window functions
+select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
+  from gstest2 group by rollup (a,b) order by rsum, a, b;
+
+-- empty input: first is 0 rows, second 1, third 3 etc.
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a);
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),());
+select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),());
+select sum(v), count(*) from gstest_empty group by grouping sets ((),(),());
+
+-- empty input with joins tests some important code paths
+select t1.a, t2.b, sum(t1.v), count(*) from gstest_empty t1, gstest_empty t2
+ group by grouping sets ((t1.a,t2.b),());
+
+-- simple joins, var resolution, GROUPING on join vars
+select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
+  from gstest1 t1, gstest2 t2
+ group by grouping sets ((t1.a, t2.b), ());
+
+select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
+  from gstest1 t1 join gstest2 t2 on (t1.a=t2.a)
+ group by grouping sets ((t1.a, t2.b), ());
+
+select a, b, grouping(a, b), sum(t1.v), max(t2.c)
+  from gstest1 t1 join gstest2 t2 using (a,b)
+ group by grouping sets ((a, b), ());
+
+-- simple rescan tests
+
+select a, b, sum(v.x)
+  from (values (1),(2)) v(x), gstest_data(v.x)
+ group by rollup (a,b);
+
+select *
+  from (values (1),(2)) v(x),
+       lateral (select a, b, sum(v.x) from gstest_data(v.x) group by rollup (a,b)) s;
+
+-- min max optimisation should still work with GROUP BY ()
+explain (costs off)
+  select min(unique1) from tenk1 GROUP BY ();
+
+-- Views with GROUPING SET queries
+CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
+  from gstest2 group by rollup ((a,b,c),(c,d));
+
+select pg_get_viewdef('gstest_view'::regclass, true);
+
+-- Nested queries with 3 or more levels of nesting
+select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
+select(select (select grouping(e,f) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
+select(select (select grouping(c) from (values (1)) v2(c) GROUP BY c) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
+
+-- Combinations of operations
+select a, b, c, d from gstest2 group by rollup(a,b),grouping sets(c,d);
+select a, b from (values (1,2),(2,3)) v(a,b) group by a,b, grouping sets(a);
+
+-- Tests for chained aggregates
+select a, b, grouping(a,b), sum(v), count(*), max(v)
+  from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2));
+select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP((e+1),(f+1));
+select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY CUBE((e+1),(f+1)) ORDER BY (e+1),(f+1);
+select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
+  from gstest2 group by cube (a,b) order by rsum, a, b;
+select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b);
+select a, b, sum(v.x)
+  from (values (1),(2)) v(x), gstest_data(v.x)
+ group by cube (a,b) order by a,b;
+
+
+-- Agg level check. This query should error out.
+select (select grouping(a,b) from gstest2) from gstest2 group by a,b;
+
+--Nested queries
+select a, b, sum(c), count(*) from gstest2 group by grouping sets (rollup(a,b),a);
+
+-- HAVING queries
+select ten, sum(distinct four) from onek a
+group by grouping sets((ten,four),(ten))
+having exists (select 1 from onek b where sum(distinct a.four) = b.four);
+
+-- FILTER queries
+select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
+group by rollup(ten);
+
+-- More rescan tests
+select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten;
+select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a);
+
+-- end