Commit f3d31185 authored by Andres Freund's avatar Andres Freund

Support GROUPING SETS, CUBE and ROLLUP.

This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.

This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.

The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.

The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.

Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage.  The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting.  The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.

A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.

Needs a catversion bump because stored rules may change.

Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
    Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
parent 6e4415c6
...@@ -2267,6 +2267,7 @@ JumbleQuery(pgssJumbleState *jstate, Query *query) ...@@ -2267,6 +2267,7 @@ JumbleQuery(pgssJumbleState *jstate, Query *query)
JumbleExpr(jstate, (Node *) query->onConflict); JumbleExpr(jstate, (Node *) query->onConflict);
JumbleExpr(jstate, (Node *) query->returningList); JumbleExpr(jstate, (Node *) query->returningList);
JumbleExpr(jstate, (Node *) query->groupClause); JumbleExpr(jstate, (Node *) query->groupClause);
JumbleExpr(jstate, (Node *) query->groupingSets);
JumbleExpr(jstate, query->havingQual); JumbleExpr(jstate, query->havingQual);
JumbleExpr(jstate, (Node *) query->windowClause); JumbleExpr(jstate, (Node *) query->windowClause);
JumbleExpr(jstate, (Node *) query->distinctClause); JumbleExpr(jstate, (Node *) query->distinctClause);
...@@ -2397,6 +2398,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) ...@@ -2397,6 +2398,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) expr->aggfilter); JumbleExpr(jstate, (Node *) expr->aggfilter);
} }
break; break;
case T_GroupingFunc:
{
GroupingFunc *grpnode = (GroupingFunc *) node;
JumbleExpr(jstate, (Node *) grpnode->refs);
}
break;
case T_WindowFunc: case T_WindowFunc:
{ {
WindowFunc *expr = (WindowFunc *) node; WindowFunc *expr = (WindowFunc *) node;
...@@ -2698,6 +2706,12 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) ...@@ -2698,6 +2706,12 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) lfirst(temp)); JumbleExpr(jstate, (Node *) lfirst(temp));
} }
break; break;
case T_IntList:
foreach(temp, (List *) node)
{
APP_JUMB(lfirst_int(temp));
}
break;
case T_SortGroupClause: case T_SortGroupClause:
{ {
SortGroupClause *sgc = (SortGroupClause *) node; SortGroupClause *sgc = (SortGroupClause *) node;
...@@ -2708,6 +2722,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) ...@@ -2708,6 +2722,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
APP_JUMB(sgc->nulls_first); APP_JUMB(sgc->nulls_first);
} }
break; break;
case T_GroupingSet:
{
GroupingSet *gsnode = (GroupingSet *) node;
JumbleExpr(jstate, (Node *) gsnode->content);
}
break;
case T_WindowClause: case T_WindowClause:
{ {
WindowClause *wc = (WindowClause *) node; WindowClause *wc = (WindowClause *) node;
......
...@@ -12228,7 +12228,9 @@ NULL baz</literallayout>(3 rows)</entry> ...@@ -12228,7 +12228,9 @@ NULL baz</literallayout>(3 rows)</entry>
<xref linkend="functions-aggregate-statistics-table">. <xref linkend="functions-aggregate-statistics-table">.
The built-in ordered-set aggregate functions The built-in ordered-set aggregate functions
are listed in <xref linkend="functions-orderedset-table"> and 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 The special syntax considerations for aggregate
functions are explained in <xref linkend="syntax-aggregates">. functions are explained in <xref linkend="syntax-aggregates">.
Consult <xref linkend="tutorial-agg"> for additional introductory Consult <xref linkend="tutorial-agg"> for additional introductory
...@@ -13326,6 +13328,72 @@ SELECT xmlagg(x) FROM (SELECT x FROM test ORDER BY y DESC) AS tab; ...@@ -13326,6 +13328,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. to the rule specified in the <literal>ORDER BY</> clause.
</para> </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 associated 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>
<sect1 id="functions-window"> <sect1 id="functions-window">
......
...@@ -1183,6 +1183,181 @@ SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit ...@@ -1183,6 +1183,181 @@ SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit
</para> </para>
</sect2> </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>
<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"> <sect2 id="queries-window">
<title>Window Function Processing</title> <title>Window Function Processing</title>
......
...@@ -37,7 +37,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac ...@@ -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> ] [, ...] ] [ * | <replaceable class="parameter">expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
[ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ] [ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
[ WHERE <replaceable class="parameter">condition</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> [, ...] ] [ HAVING <replaceable class="parameter">condition</replaceable> [, ...] ]
[ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</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> ] [ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] <replaceable class="parameter">select</replaceable> ]
...@@ -60,6 +60,15 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac ...@@ -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> [, ...] ) ] ] [ 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> [, ...] ) ] <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> <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> ) <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> )
...@@ -665,22 +674,34 @@ WHERE <replaceable class="parameter">condition</replaceable> ...@@ -665,22 +674,34 @@ WHERE <replaceable class="parameter">condition</replaceable>
<para> <para>
The optional <literal>GROUP BY</literal> clause has the general form The optional <literal>GROUP BY</literal> clause has the general form
<synopsis> <synopsis>
GROUP BY <replaceable class="parameter">expression</replaceable> [, ...] GROUP BY <replaceable class="parameter">grouping_element</replaceable> [, ...]
</synopsis> </synopsis>
</para> </para>
<para> <para>
<literal>GROUP BY</literal> will condense into a single row all <literal>GROUP BY</literal> will condense into a single row all
selected rows that share the same values for the grouped selected rows that share the same values for the grouped
expressions. <replaceable expressions. An <replaceable
class="parameter">expression</replaceable> can be an input column class="parameter">expression</replaceable> used inside a
name, or the name or ordinal number of an output column <replaceable class="parameter">grouping_element</replaceable>
(<command>SELECT</command> list item), or an arbitrary 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, expression formed from input-column values. In case of ambiguity,
a <literal>GROUP BY</literal> name will be interpreted as an a <literal>GROUP BY</literal> name will be interpreted as an
input-column name rather than an output column name. input-column name rather than an output column name.
</para> </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> <para>
Aggregate functions, if any are used, are computed across all rows Aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group. making up each group, producing a separate value for each group.
......
...@@ -467,9 +467,9 @@ T331 Basic roles YES ...@@ -467,9 +467,9 @@ T331 Basic roles YES
T332 Extended roles NO mostly supported T332 Extended roles NO mostly supported
T341 Overloading of SQL-invoked functions and procedures YES T341 Overloading of SQL-invoked functions and procedures YES
T351 Bracketed SQL comments (/*...*/ comments) YES T351 Bracketed SQL comments (/*...*/ comments) YES
T431 Extended grouping capabilities NO T431 Extended grouping capabilities YES
T432 Nested and concatenated GROUPING SETS NO T432 Nested and concatenated GROUPING SETS YES
T433 Multiargument GROUPING function NO T433 Multiargument GROUPING function YES
T434 GROUP BY DISTINCT NO T434 GROUP BY DISTINCT NO
T441 ABS and MOD functions YES T441 ABS and MOD functions YES
T461 Symmetric BETWEEN predicate YES T461 Symmetric BETWEEN predicate YES
......
...@@ -82,6 +82,12 @@ static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors, ...@@ -82,6 +82,12 @@ static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
ExplainState *es); ExplainState *es);
static void show_agg_keys(AggState *astate, List *ancestors, static void show_agg_keys(AggState *astate, List *ancestors,
ExplainState *es); ExplainState *es);
static void show_grouping_sets(PlanState *planstate, Agg *agg,
List *ancestors, ExplainState *es);
static void show_grouping_set_keys(PlanState *planstate,
Agg *aggnode, Sort *sortnode,
List *context, bool useprefix,
List *ancestors, ExplainState *es);
static void show_group_keys(GroupState *gstate, List *ancestors, static void show_group_keys(GroupState *gstate, List *ancestors,
ExplainState *es); ExplainState *es);
static void show_sort_group_keys(PlanState *planstate, const char *qlabel, static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
...@@ -1851,18 +1857,116 @@ show_agg_keys(AggState *astate, List *ancestors, ...@@ -1851,18 +1857,116 @@ show_agg_keys(AggState *astate, List *ancestors,
{ {
Agg *plan = (Agg *) astate->ss.ps.plan; 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 */ /* The key columns refer to the tlist of the child plan */
ancestors = lcons(astate, ancestors); ancestors = lcons(astate, ancestors);
show_sort_group_keys(outerPlanState(astate), "Group Key",
plan->numCols, plan->grpColIdx, if (plan->groupingSets)
NULL, NULL, NULL, show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
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); ancestors = list_delete_first(ancestors);
} }
} }
static void
show_grouping_sets(PlanState *planstate, Agg *agg,
List *ancestors, ExplainState *es)
{
List *context;
bool useprefix;
ListCell *lc;
/* 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);
show_grouping_set_keys(planstate, agg, NULL,
context, useprefix, ancestors, es);
foreach(lc, agg->chain)
{
Agg *aggnode = lfirst(lc);
Sort *sortnode = (Sort *) aggnode->plan.lefttree;
show_grouping_set_keys(planstate, aggnode, sortnode,
context, useprefix, ancestors, es);
}
ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
}
static void
show_grouping_set_keys(PlanState *planstate,
Agg *aggnode, Sort *sortnode,
List *context, bool useprefix,
List *ancestors, ExplainState *es)
{
Plan *plan = planstate->plan;
char *exprstr;
ListCell *lc;
List *gsets = aggnode->groupingSets;
AttrNumber *keycols = aggnode->grpColIdx;
ExplainOpenGroup("Grouping Set", NULL, true, es);
if (sortnode)
{
show_sort_group_keys(planstate, "Sort Key",
sortnode->numCols, sortnode->sortColIdx,
sortnode->sortOperators, sortnode->collations,
sortnode->nullsFirst,
ancestors, es);
if (es->format == EXPLAIN_FORMAT_TEXT)
es->indent++;
}
ExplainOpenGroup("Group Keys", "Group Keys", 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("Group Keys", "Group Keys", false, es);
if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)
es->indent--;
ExplainCloseGroup("Grouping Set", NULL, true, es);
}
/* /*
* Show the grouping keys for a Group node. * Show the grouping keys for a Group node.
*/ */
...@@ -2612,6 +2716,52 @@ ExplainPropertyList(const char *qlabel, List *data, ExplainState *es) ...@@ -2612,6 +2716,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. * Explain a simple property.
* *
......
...@@ -181,6 +181,9 @@ static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, ...@@ -181,6 +181,9 @@ static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
bool *isNull, ExprDoneCond *isDone); bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone); bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
...@@ -3016,6 +3019,44 @@ ExecEvalCaseTestExpr(ExprState *exprstate, ...@@ -3016,6 +3019,44 @@ ExecEvalCaseTestExpr(ExprState *exprstate,
return econtext->caseValue_datum; 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;
Bitmapset *grouped_cols = gstate->aggstate->grouped_cols;
ListCell *lc;
if (isDone)
*isDone = ExprSingleResult;
*isNull = false;
foreach(lc, (gstate->clauses))
{
attnum = lfirst_int(lc);
result = result << 1;
if (!bms_is_member(attnum, grouped_cols))
result = result | 1;
}
return (Datum) result;
}
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* ExecEvalArray - ARRAY[] expressions * ExecEvalArray - ARRAY[] expressions
* ---------------------------------------------------------------- * ----------------------------------------------------------------
...@@ -4482,6 +4523,28 @@ ExecInitExpr(Expr *node, PlanState *parent) ...@@ -4482,6 +4523,28 @@ ExecInitExpr(Expr *node, PlanState *parent)
state = (ExprState *) astate; state = (ExprState *) astate;
} }
break; break;
case T_GroupingFunc:
{
GroupingFunc *grp_node = (GroupingFunc *) node;
GroupingFuncExprState *grp_state = makeNode(GroupingFuncExprState);
Agg *agg = NULL;
if (!parent || !IsA(parent, AggState) || !IsA(parent->plan, Agg))
elog(ERROR, "parent of GROUPING is not Agg node");
grp_state->aggstate = (AggState *) parent;
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: case T_WindowFunc:
{ {
WindowFunc *wfunc = (WindowFunc *) node; WindowFunc *wfunc = (WindowFunc *) node;
......
...@@ -642,9 +642,10 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo) ...@@ -642,9 +642,10 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
/* /*
* Don't examine the arguments or filters of Aggrefs or WindowFuncs, * Don't examine the arguments or filters of Aggrefs or WindowFuncs,
* because those do not represent expressions to be evaluated within the * 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; return false;
if (IsA(node, WindowFunc)) if (IsA(node, WindowFunc))
return false; return false;
......
This diff is collapsed.
...@@ -12,6 +12,7 @@ subdir = src/backend/lib ...@@ -12,6 +12,7 @@ subdir = src/backend/lib
top_builddir = ../../.. top_builddir = ../../..
include $(top_builddir)/src/Makefile.global include $(top_builddir)/src/Makefile.global
OBJS = ilist.o binaryheap.o hyperloglog.o pairingheap.o rbtree.o stringinfo.o OBJS = binaryheap.o bipartite_match.o hyperloglog.o ilist.o pairingheap.o \
rbtree.o stringinfo.o
include $(top_srcdir)/src/backend/common.mk include $(top_srcdir)/src/backend/common.mk
/*-------------------------------------------------------------------------
*
* bipartite_match.c
* Hopcroft-Karp maximum cardinality algorithm for bipartite graphs
*
* This implementation is based on pseudocode found at:
*
* http://en.wikipedia.org/w/index.php?title=Hopcroft%E2%80%93Karp_algorithm&oldid=593898016
*
* Copyright (c) 2015, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/lib/bipartite_match.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include <limits.h>
#include "lib/bipartite_match.h"
#include "miscadmin.h"
#include "utils/palloc.h"
static bool hk_breadth_search(BipartiteMatchState *state);
static bool hk_depth_search(BipartiteMatchState *state, int u, int depth);
/*
* Given the size of U and V, where each is indexed 1..size, and an adjacency
* list, perform the matching and return the resulting state.
*/
BipartiteMatchState *
BipartiteMatch(int u_size, int v_size, short **adjacency)
{
BipartiteMatchState *state = palloc(sizeof(BipartiteMatchState));
Assert(u_size < SHRT_MAX);
Assert(v_size < SHRT_MAX);
state->u_size = u_size;
state->v_size = v_size;
state->matching = 0;
state->adjacency = adjacency;
state->pair_uv = palloc0((u_size + 1) * sizeof(short));
state->pair_vu = palloc0((v_size + 1) * sizeof(short));
state->distance = palloc((u_size + 1) * sizeof(float));
state->queue = palloc((u_size + 2) * sizeof(short));
while (hk_breadth_search(state))
{
int u;
for (u = 1; u <= u_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;
}
/*
* Free a state returned by BipartiteMatch, except for the original adjacency
* list, which is owned by the caller. This only frees memory, so it's optional.
*/
void
BipartiteMatchFree(BipartiteMatchState *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);
}
static bool
hk_breadth_search(BipartiteMatchState *state)
{
int usize = state->u_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 <= usize; ++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 <= usize+2);
}
}
}
}
return !isinf(distance[0]);
}
static bool
hk_depth_search(BipartiteMatchState *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;
}
...@@ -839,6 +839,8 @@ _copyAgg(const Agg *from) ...@@ -839,6 +839,8 @@ _copyAgg(const Agg *from)
COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid)); COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
} }
COPY_SCALAR_FIELD(numGroups); COPY_SCALAR_FIELD(numGroups);
COPY_NODE_FIELD(groupingSets);
COPY_NODE_FIELD(chain);
return newnode; return newnode;
} }
...@@ -1208,6 +1210,23 @@ _copyAggref(const Aggref *from) ...@@ -1208,6 +1210,23 @@ _copyAggref(const Aggref *from)
return newnode; 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 * _copyWindowFunc
*/ */
...@@ -2152,6 +2171,18 @@ _copySortGroupClause(const SortGroupClause *from) ...@@ -2152,6 +2171,18 @@ _copySortGroupClause(const SortGroupClause *from)
return newnode; 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 * static WindowClause *
_copyWindowClause(const WindowClause *from) _copyWindowClause(const WindowClause *from)
{ {
...@@ -2676,6 +2707,7 @@ _copyQuery(const Query *from) ...@@ -2676,6 +2707,7 @@ _copyQuery(const Query *from)
COPY_NODE_FIELD(onConflict); COPY_NODE_FIELD(onConflict);
COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(groupClause); COPY_NODE_FIELD(groupClause);
COPY_NODE_FIELD(groupingSets);
COPY_NODE_FIELD(havingQual); COPY_NODE_FIELD(havingQual);
COPY_NODE_FIELD(windowClause); COPY_NODE_FIELD(windowClause);
COPY_NODE_FIELD(distinctClause); COPY_NODE_FIELD(distinctClause);
...@@ -4309,6 +4341,9 @@ copyObject(const void *from) ...@@ -4309,6 +4341,9 @@ copyObject(const void *from)
case T_Aggref: case T_Aggref:
retval = _copyAggref(from); retval = _copyAggref(from);
break; break;
case T_GroupingFunc:
retval = _copyGroupingFunc(from);
break;
case T_WindowFunc: case T_WindowFunc:
retval = _copyWindowFunc(from); retval = _copyWindowFunc(from);
break; break;
...@@ -4878,6 +4913,9 @@ copyObject(const void *from) ...@@ -4878,6 +4913,9 @@ copyObject(const void *from)
case T_SortGroupClause: case T_SortGroupClause:
retval = _copySortGroupClause(from); retval = _copySortGroupClause(from);
break; break;
case T_GroupingSet:
retval = _copyGroupingSet(from);
break;
case T_WindowClause: case T_WindowClause:
retval = _copyWindowClause(from); retval = _copyWindowClause(from);
break; break;
......
...@@ -207,6 +207,21 @@ _equalAggref(const Aggref *a, const Aggref *b) ...@@ -207,6 +207,21 @@ _equalAggref(const Aggref *a, const Aggref *b)
return true; 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 static bool
_equalWindowFunc(const WindowFunc *a, const WindowFunc *b) _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
{ {
...@@ -896,6 +911,7 @@ _equalQuery(const Query *a, const Query *b) ...@@ -896,6 +911,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_NODE_FIELD(onConflict); COMPARE_NODE_FIELD(onConflict);
COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(groupClause); COMPARE_NODE_FIELD(groupClause);
COMPARE_NODE_FIELD(groupingSets);
COMPARE_NODE_FIELD(havingQual); COMPARE_NODE_FIELD(havingQual);
COMPARE_NODE_FIELD(windowClause); COMPARE_NODE_FIELD(windowClause);
COMPARE_NODE_FIELD(distinctClause); COMPARE_NODE_FIELD(distinctClause);
...@@ -2426,6 +2442,16 @@ _equalSortGroupClause(const SortGroupClause *a, const SortGroupClause *b) ...@@ -2426,6 +2442,16 @@ _equalSortGroupClause(const SortGroupClause *a, const SortGroupClause *b)
return true; 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 static bool
_equalWindowClause(const WindowClause *a, const WindowClause *b) _equalWindowClause(const WindowClause *a, const WindowClause *b)
{ {
...@@ -2693,6 +2719,9 @@ equal(const void *a, const void *b) ...@@ -2693,6 +2719,9 @@ equal(const void *a, const void *b)
case T_Aggref: case T_Aggref:
retval = _equalAggref(a, b); retval = _equalAggref(a, b);
break; break;
case T_GroupingFunc:
retval = _equalGroupingFunc(a, b);
break;
case T_WindowFunc: case T_WindowFunc:
retval = _equalWindowFunc(a, b); retval = _equalWindowFunc(a, b);
break; break;
...@@ -3249,6 +3278,9 @@ equal(const void *a, const void *b) ...@@ -3249,6 +3278,9 @@ equal(const void *a, const void *b)
case T_SortGroupClause: case T_SortGroupClause:
retval = _equalSortGroupClause(a, b); retval = _equalSortGroupClause(a, b);
break; break;
case T_GroupingSet:
retval = _equalGroupingSet(a, b);
break;
case T_WindowClause: case T_WindowClause:
retval = _equalWindowClause(a, b); retval = _equalWindowClause(a, b);
break; break;
......
...@@ -822,6 +822,32 @@ list_intersection(const List *list1, const List *list2) ...@@ -822,6 +822,32 @@ list_intersection(const List *list1, const List *list2)
return result; 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 * 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 * list2. The returned list is freshly allocated via palloc(), but the
......
...@@ -554,3 +554,18 @@ makeFuncCall(List *name, List *args, int location) ...@@ -554,3 +554,18 @@ makeFuncCall(List *name, List *args, int location)
n->location = location; n->location = location;
return n; 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;
}
...@@ -54,6 +54,9 @@ exprType(const Node *expr) ...@@ -54,6 +54,9 @@ exprType(const Node *expr)
case T_Aggref: case T_Aggref:
type = ((const Aggref *) expr)->aggtype; type = ((const Aggref *) expr)->aggtype;
break; break;
case T_GroupingFunc:
type = INT4OID;
break;
case T_WindowFunc: case T_WindowFunc:
type = ((const WindowFunc *) expr)->wintype; type = ((const WindowFunc *) expr)->wintype;
break; break;
...@@ -750,6 +753,9 @@ exprCollation(const Node *expr) ...@@ -750,6 +753,9 @@ exprCollation(const Node *expr)
case T_Aggref: case T_Aggref:
coll = ((const Aggref *) expr)->aggcollid; coll = ((const Aggref *) expr)->aggcollid;
break; break;
case T_GroupingFunc:
coll = InvalidOid;
break;
case T_WindowFunc: case T_WindowFunc:
coll = ((const WindowFunc *) expr)->wincollid; coll = ((const WindowFunc *) expr)->wincollid;
break; break;
...@@ -986,6 +992,9 @@ exprSetCollation(Node *expr, Oid collation) ...@@ -986,6 +992,9 @@ exprSetCollation(Node *expr, Oid collation)
case T_Aggref: case T_Aggref:
((Aggref *) expr)->aggcollid = collation; ((Aggref *) expr)->aggcollid = collation;
break; break;
case T_GroupingFunc:
Assert(!OidIsValid(collation));
break;
case T_WindowFunc: case T_WindowFunc:
((WindowFunc *) expr)->wincollid = collation; ((WindowFunc *) expr)->wincollid = collation;
break; break;
...@@ -1202,6 +1211,9 @@ exprLocation(const Node *expr) ...@@ -1202,6 +1211,9 @@ exprLocation(const Node *expr)
/* function name should always be the first thing */ /* function name should always be the first thing */
loc = ((const Aggref *) expr)->location; loc = ((const Aggref *) expr)->location;
break; break;
case T_GroupingFunc:
loc = ((const GroupingFunc *) expr)->location;
break;
case T_WindowFunc: case T_WindowFunc:
/* function name should always be the first thing */ /* function name should always be the first thing */
loc = ((const WindowFunc *) expr)->location; loc = ((const WindowFunc *) expr)->location;
...@@ -1491,6 +1503,9 @@ exprLocation(const Node *expr) ...@@ -1491,6 +1503,9 @@ exprLocation(const Node *expr)
/* XMLSERIALIZE keyword should always be the first thing */ /* XMLSERIALIZE keyword should always be the first thing */
loc = ((const XmlSerialize *) expr)->location; loc = ((const XmlSerialize *) expr)->location;
break; break;
case T_GroupingSet:
loc = ((const GroupingSet *) expr)->location;
break;
case T_WithClause: case T_WithClause:
loc = ((const WithClause *) expr)->location; loc = ((const WithClause *) expr)->location;
break; break;
...@@ -1685,6 +1700,15 @@ expression_tree_walker(Node *node, ...@@ -1685,6 +1700,15 @@ expression_tree_walker(Node *node,
return true; return true;
} }
break; break;
case T_GroupingFunc:
{
GroupingFunc *grouping = (GroupingFunc *) node;
if (expression_tree_walker((Node *) grouping->args,
walker, context))
return true;
}
break;
case T_WindowFunc: case T_WindowFunc:
{ {
WindowFunc *expr = (WindowFunc *) node; WindowFunc *expr = (WindowFunc *) node;
...@@ -2243,6 +2267,29 @@ expression_tree_mutator(Node *node, ...@@ -2243,6 +2267,29 @@ expression_tree_mutator(Node *node,
return (Node *) newnode; return (Node *) newnode;
} }
break; 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: case T_WindowFunc:
{ {
WindowFunc *wfunc = (WindowFunc *) node; WindowFunc *wfunc = (WindowFunc *) node;
...@@ -2962,6 +3009,8 @@ raw_expression_tree_walker(Node *node, ...@@ -2962,6 +3009,8 @@ raw_expression_tree_walker(Node *node,
break; break;
case T_RangeVar: case T_RangeVar:
return walker(((RangeVar *) node)->alias, context); return walker(((RangeVar *) node)->alias, context);
case T_GroupingFunc:
return walker(((GroupingFunc *) node)->args, context);
case T_SubLink: case T_SubLink:
{ {
SubLink *sublink = (SubLink *) node; SubLink *sublink = (SubLink *) node;
...@@ -3287,6 +3336,8 @@ raw_expression_tree_walker(Node *node, ...@@ -3287,6 +3336,8 @@ raw_expression_tree_walker(Node *node,
/* for now, constraints are ignored */ /* for now, constraints are ignored */
} }
break; break;
case T_GroupingSet:
return walker(((GroupingSet *) node)->content, context);
case T_LockingClause: case T_LockingClause:
return walker(((LockingClause *) node)->lockedRels, context); return walker(((LockingClause *) node)->lockedRels, context);
case T_XmlSerialize: case T_XmlSerialize:
......
...@@ -679,6 +679,9 @@ _outAgg(StringInfo str, const Agg *node) ...@@ -679,6 +679,9 @@ _outAgg(StringInfo str, const Agg *node)
appendStringInfo(str, " %u", node->grpOperators[i]); appendStringInfo(str, " %u", node->grpOperators[i]);
WRITE_LONG_FIELD(numGroups); WRITE_LONG_FIELD(numGroups);
WRITE_NODE_FIELD(groupingSets);
WRITE_NODE_FIELD(chain);
} }
static void static void
...@@ -1003,6 +1006,18 @@ _outAggref(StringInfo str, const Aggref *node) ...@@ -1003,6 +1006,18 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_LOCATION_FIELD(location); 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 static void
_outWindowFunc(StringInfo str, const WindowFunc *node) _outWindowFunc(StringInfo str, const WindowFunc *node)
{ {
...@@ -2364,6 +2379,7 @@ _outQuery(StringInfo str, const Query *node) ...@@ -2364,6 +2379,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_NODE_FIELD(onConflict); WRITE_NODE_FIELD(onConflict);
WRITE_NODE_FIELD(returningList); WRITE_NODE_FIELD(returningList);
WRITE_NODE_FIELD(groupClause); WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(groupingSets);
WRITE_NODE_FIELD(havingQual); WRITE_NODE_FIELD(havingQual);
WRITE_NODE_FIELD(windowClause); WRITE_NODE_FIELD(windowClause);
WRITE_NODE_FIELD(distinctClause); WRITE_NODE_FIELD(distinctClause);
...@@ -2398,6 +2414,16 @@ _outSortGroupClause(StringInfo str, const SortGroupClause *node) ...@@ -2398,6 +2414,16 @@ _outSortGroupClause(StringInfo str, const SortGroupClause *node)
WRITE_BOOL_FIELD(hashable); 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 static void
_outWindowClause(StringInfo str, const WindowClause *node) _outWindowClause(StringInfo str, const WindowClause *node)
{ {
...@@ -3087,6 +3113,9 @@ _outNode(StringInfo str, const void *obj) ...@@ -3087,6 +3113,9 @@ _outNode(StringInfo str, const void *obj)
case T_Aggref: case T_Aggref:
_outAggref(str, obj); _outAggref(str, obj);
break; break;
case T_GroupingFunc:
_outGroupingFunc(str, obj);
break;
case T_WindowFunc: case T_WindowFunc:
_outWindowFunc(str, obj); _outWindowFunc(str, obj);
break; break;
...@@ -3349,6 +3378,9 @@ _outNode(StringInfo str, const void *obj) ...@@ -3349,6 +3378,9 @@ _outNode(StringInfo str, const void *obj)
case T_SortGroupClause: case T_SortGroupClause:
_outSortGroupClause(str, obj); _outSortGroupClause(str, obj);
break; break;
case T_GroupingSet:
_outGroupingSet(str, obj);
break;
case T_WindowClause: case T_WindowClause:
_outWindowClause(str, obj); _outWindowClause(str, obj);
break; break;
......
...@@ -217,6 +217,7 @@ _readQuery(void) ...@@ -217,6 +217,7 @@ _readQuery(void)
READ_NODE_FIELD(onConflict); READ_NODE_FIELD(onConflict);
READ_NODE_FIELD(returningList); READ_NODE_FIELD(returningList);
READ_NODE_FIELD(groupClause); READ_NODE_FIELD(groupClause);
READ_NODE_FIELD(groupingSets);
READ_NODE_FIELD(havingQual); READ_NODE_FIELD(havingQual);
READ_NODE_FIELD(windowClause); READ_NODE_FIELD(windowClause);
READ_NODE_FIELD(distinctClause); READ_NODE_FIELD(distinctClause);
...@@ -292,6 +293,21 @@ _readSortGroupClause(void) ...@@ -292,6 +293,21 @@ _readSortGroupClause(void)
READ_DONE(); 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 * _readWindowClause
*/ */
...@@ -551,6 +567,23 @@ _readAggref(void) ...@@ -551,6 +567,23 @@ _readAggref(void)
READ_DONE(); 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 * _readWindowFunc
*/ */
...@@ -1386,6 +1419,8 @@ parseNodeString(void) ...@@ -1386,6 +1419,8 @@ parseNodeString(void)
return_value = _readWithCheckOption(); return_value = _readWithCheckOption();
else if (MATCH("SORTGROUPCLAUSE", 15)) else if (MATCH("SORTGROUPCLAUSE", 15))
return_value = _readSortGroupClause(); return_value = _readSortGroupClause();
else if (MATCH("GROUPINGSET", 11))
return_value = _readGroupingSet();
else if (MATCH("WINDOWCLAUSE", 12)) else if (MATCH("WINDOWCLAUSE", 12))
return_value = _readWindowClause(); return_value = _readWindowClause();
else if (MATCH("ROWMARKCLAUSE", 13)) else if (MATCH("ROWMARKCLAUSE", 13))
...@@ -1412,6 +1447,8 @@ parseNodeString(void) ...@@ -1412,6 +1447,8 @@ parseNodeString(void)
return_value = _readParam(); return_value = _readParam();
else if (MATCH("AGGREF", 6)) else if (MATCH("AGGREF", 6))
return_value = _readAggref(); return_value = _readAggref();
else if (MATCH("GROUPINGFUNC", 12))
return_value = _readGroupingFunc();
else if (MATCH("WINDOWFUNC", 10)) else if (MATCH("WINDOWFUNC", 10))
return_value = _readWindowFunc(); return_value = _readWindowFunc();
else if (MATCH("ARRAYREF", 8)) else if (MATCH("ARRAYREF", 8))
......
...@@ -1290,6 +1290,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -1290,6 +1290,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
*/ */
if (parse->hasAggs || if (parse->hasAggs ||
parse->groupClause || parse->groupClause ||
parse->groupingSets ||
parse->havingQual || parse->havingQual ||
parse->distinctClause || parse->distinctClause ||
parse->sortClause || parse->sortClause ||
...@@ -2150,7 +2151,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual) ...@@ -2150,7 +2151,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
* subquery uses grouping or aggregation, put it in HAVING (since the * subquery uses grouping or aggregation, put it in HAVING (since the
* qual really refers to the group-result rows). * 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); subquery->havingQual = make_and_qual(subquery->havingQual, qual);
else else
subquery->jointree->quals = subquery->jointree->quals =
......
...@@ -1954,7 +1954,8 @@ adjust_rowcount_for_semijoins(PlannerInfo *root, ...@@ -1954,7 +1954,8 @@ adjust_rowcount_for_semijoins(PlannerInfo *root,
nraw = approximate_joinrel_size(root, sjinfo->syn_righthand); nraw = approximate_joinrel_size(root, sjinfo->syn_righthand);
nunique = estimate_num_groups(root, nunique = estimate_num_groups(root,
sjinfo->semi_rhs_exprs, sjinfo->semi_rhs_exprs,
nraw); nraw,
NULL);
if (rowcount > nunique) if (rowcount > nunique)
rowcount = nunique; rowcount = nunique;
} }
......
...@@ -581,6 +581,7 @@ query_supports_distinctness(Query *query) ...@@ -581,6 +581,7 @@ query_supports_distinctness(Query *query)
{ {
if (query->distinctClause != NIL || if (query->distinctClause != NIL ||
query->groupClause != NIL || query->groupClause != NIL ||
query->groupingSets != NIL ||
query->hasAggs || query->hasAggs ||
query->havingQual || query->havingQual ||
query->setOperations) query->setOperations)
...@@ -649,10 +650,10 @@ query_is_distinct_for(Query *query, List *colnos, List *opids) ...@@ -649,10 +650,10 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
} }
/* /*
* Similarly, GROUP BY guarantees uniqueness if all the grouped columns * Similarly, GROUP BY without GROUPING SETS guarantees uniqueness if all
* appear in colnos and operator semantics match. * the grouped columns appear in colnos and operator semantics match.
*/ */
if (query->groupClause) if (query->groupClause && !query->groupingSets)
{ {
foreach(l, query->groupClause) foreach(l, query->groupClause)
{ {
...@@ -668,6 +669,27 @@ query_is_distinct_for(Query *query, List *colnos, List *opids) ...@@ -668,6 +669,27 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
if (l == NULL) /* had matches for all? */ if (l == NULL) /* had matches for all? */
return true; 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 else
{ {
/* /*
......
...@@ -1042,6 +1042,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -1042,6 +1042,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
numGroupCols, numGroupCols,
groupColIdx, groupColIdx,
groupOperators, groupOperators,
NIL,
numGroups, numGroups,
subplan); subplan);
} }
...@@ -4492,6 +4493,7 @@ Agg * ...@@ -4492,6 +4493,7 @@ Agg *
make_agg(PlannerInfo *root, List *tlist, List *qual, make_agg(PlannerInfo *root, List *tlist, List *qual,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts, AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
List *groupingSets,
long numGroups, long numGroups,
Plan *lefttree) Plan *lefttree)
{ {
...@@ -4521,10 +4523,12 @@ make_agg(PlannerInfo *root, List *tlist, List *qual, ...@@ -4521,10 +4523,12 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
* group otherwise. * group otherwise.
*/ */
if (aggstrategy == AGG_PLAIN) if (aggstrategy == AGG_PLAIN)
plan->plan_rows = 1; plan->plan_rows = groupingSets ? list_length(groupingSets) : 1;
else else
plan->plan_rows = numGroups; plan->plan_rows = numGroups;
node->groupingSets = groupingSets;
/* /*
* We also need to account for the cost of evaluation of the qual (ie, the * 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 * HAVING clause) and the tlist. Note that cost_qual_eval doesn't charge
...@@ -4545,6 +4549,7 @@ make_agg(PlannerInfo *root, List *tlist, List *qual, ...@@ -4545,6 +4549,7 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
plan->qual = qual; plan->qual = qual;
plan->targetlist = tlist; plan->targetlist = tlist;
plan->lefttree = lefttree; plan->lefttree = lefttree;
plan->righttree = NULL; plan->righttree = NULL;
......
...@@ -96,7 +96,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist) ...@@ -96,7 +96,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
* performs assorted processing related to these features between calling * performs assorted processing related to these features between calling
* preprocess_minmax_aggregates and optimize_minmax_aggregates.) * preprocess_minmax_aggregates and optimize_minmax_aggregates.)
*/ */
if (parse->groupClause || parse->hasWindowFuncs) if (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs)
return; return;
/* /*
......
This diff is collapsed.
...@@ -140,7 +140,6 @@ static bool fix_opfuncids_walker(Node *node, void *context); ...@@ -140,7 +140,6 @@ static bool fix_opfuncids_walker(Node *node, void *context);
static bool extract_query_dependencies_walker(Node *node, static bool extract_query_dependencies_walker(Node *node,
PlannerInfo *context); PlannerInfo *context);
/***************************************************************************** /*****************************************************************************
* *
* SUBPLAN REFERENCES * SUBPLAN REFERENCES
...@@ -656,6 +655,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) ...@@ -656,6 +655,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
} }
break; break;
case T_Agg: case T_Agg:
set_upper_references(root, plan, rtoffset);
break;
case T_Group: case T_Group:
set_upper_references(root, plan, rtoffset); set_upper_references(root, plan, rtoffset);
break; break;
...@@ -1229,6 +1230,7 @@ copyVar(Var *var) ...@@ -1229,6 +1230,7 @@ copyVar(Var *var)
* We must look up operator opcode info for OpExpr and related nodes, * We must look up operator opcode info for OpExpr and related nodes,
* add OIDs from regclass Const nodes into root->glob->relationOids, and * add OIDs from regclass Const nodes into root->glob->relationOids, and
* add catalog TIDs for user-defined functions into root->glob->invalItems. * 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 * 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. * scribble on the planner's input data structures, but it's OK.
...@@ -1292,6 +1294,31 @@ fix_expr_common(PlannerInfo *root, Node *node) ...@@ -1292,6 +1294,31 @@ fix_expr_common(PlannerInfo *root, Node *node)
lappend_oid(root->glob->relationOids, lappend_oid(root->glob->relationOids,
DatumGetObjectId(con->constvalue)); DatumGetObjectId(con->constvalue));
} }
else if (IsA(node, GroupingFunc))
{
GroupingFunc *g = (GroupingFunc *) node;
AttrNumber *grouping_map = root->grouping_map;
/* If there are no grouping sets, we don't need this. */
Assert(grouping_map || g->cols == NIL);
if (grouping_map)
{
ListCell *lc;
List *cols = NIL;
foreach(lc, g->refs)
{
cols = lappend_int(cols, grouping_map[lfirst_int(lc)]);
}
Assert(!g->cols || equal(cols, g->cols));
if (!g->cols)
g->cols = cols;
}
}
} }
/* /*
...@@ -2186,6 +2213,7 @@ set_returning_clause_references(PlannerInfo *root, ...@@ -2186,6 +2213,7 @@ set_returning_clause_references(PlannerInfo *root,
return rlist; return rlist;
} }
/***************************************************************************** /*****************************************************************************
* OPERATOR REGPROC LOOKUP * OPERATOR REGPROC LOOKUP
*****************************************************************************/ *****************************************************************************/
......
...@@ -335,6 +335,48 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) ...@@ -335,6 +335,48 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg)
return retval; 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. * Generate a new Param node that will not conflict with any other.
* *
...@@ -1494,14 +1536,16 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query) ...@@ -1494,14 +1536,16 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query)
{ {
/* /*
* We don't try to simplify at all if the query uses set operations, * We don't try to simplify at all if the query uses set operations,
* aggregates, modifying CTEs, HAVING, OFFSET, or FOR UPDATE/SHARE; none * aggregates, grouping sets, modifying CTEs, HAVING, OFFSET, or FOR
* of these seem likely in normal usage and their possible effects are * UPDATE/SHARE; none of these seem likely in normal usage and their
* complex. (Note: we could ignore an "OFFSET 0" clause, but that * possible effects are complex. (Note: we could ignore an "OFFSET 0"
* traditionally is used as an optimization fence, so we don't.) * clause, but that traditionally is used as an optimization fence, so we
* don't.)
*/ */
if (query->commandType != CMD_SELECT || if (query->commandType != CMD_SELECT ||
query->setOperations || query->setOperations ||
query->hasAggs || query->hasAggs ||
query->groupingSets ||
query->hasWindowFuncs || query->hasWindowFuncs ||
query->hasModifyingCTE || query->hasModifyingCTE ||
query->havingQual || query->havingQual ||
...@@ -1851,6 +1895,11 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root) ...@@ -1851,6 +1895,11 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
if (((Aggref *) node)->agglevelsup > 0) if (((Aggref *) node)->agglevelsup > 0)
return (Node *) replace_outer_agg(root, (Aggref *) node); 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, return expression_tree_mutator(node,
replace_correlation_vars_mutator, replace_correlation_vars_mutator,
(void *) root); (void *) root);
......
...@@ -1412,6 +1412,7 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, ...@@ -1412,6 +1412,7 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
if (subquery->hasAggs || if (subquery->hasAggs ||
subquery->hasWindowFuncs || subquery->hasWindowFuncs ||
subquery->groupClause || subquery->groupClause ||
subquery->groupingSets ||
subquery->havingQual || subquery->havingQual ||
subquery->sortClause || subquery->sortClause ||
subquery->distinctClause || subquery->distinctClause ||
......
...@@ -268,13 +268,15 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, ...@@ -268,13 +268,15 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
*/ */
if (pNumGroups) if (pNumGroups)
{ {
if (subquery->groupClause || subquery->distinctClause || if (subquery->groupClause || subquery->groupingSets ||
subquery->distinctClause ||
subroot->hasHavingQual || subquery->hasAggs) subroot->hasHavingQual || subquery->hasAggs)
*pNumGroups = subplan->plan_rows; *pNumGroups = subplan->plan_rows;
else else
*pNumGroups = estimate_num_groups(subroot, *pNumGroups = estimate_num_groups(subroot,
get_tlist_exprs(subquery->targetList, false), get_tlist_exprs(subquery->targetList, false),
subplan->plan_rows); subplan->plan_rows,
NULL);
} }
/* /*
...@@ -771,6 +773,7 @@ make_union_unique(SetOperationStmt *op, Plan *plan, ...@@ -771,6 +773,7 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
extract_grouping_cols(groupList, extract_grouping_cols(groupList,
plan->targetlist), plan->targetlist),
extract_grouping_ops(groupList), extract_grouping_ops(groupList),
NIL,
numGroups, numGroups,
plan); plan);
/* Hashed aggregation produces randomly-ordered results */ /* Hashed aggregation produces randomly-ordered results */
......
...@@ -4353,6 +4353,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, ...@@ -4353,6 +4353,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree->jointree->fromlist || querytree->jointree->fromlist ||
querytree->jointree->quals || querytree->jointree->quals ||
querytree->groupClause || querytree->groupClause ||
querytree->groupingSets ||
querytree->havingQual || querytree->havingQual ||
querytree->windowClause || querytree->windowClause ||
querytree->distinctClause || querytree->distinctClause ||
......
...@@ -1214,7 +1214,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, ...@@ -1214,7 +1214,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
/* Estimate number of output rows */ /* Estimate number of output rows */
pathnode->path.rows = estimate_num_groups(root, pathnode->path.rows = estimate_num_groups(root,
sjinfo->semi_rhs_exprs, sjinfo->semi_rhs_exprs,
rel->rows); rel->rows,
NULL);
numCols = list_length(sjinfo->semi_rhs_exprs); numCols = list_length(sjinfo->semi_rhs_exprs);
if (sjinfo->semi_can_btree) if (sjinfo->semi_can_btree)
......
...@@ -394,6 +394,28 @@ get_sortgrouplist_exprs(List *sgClauses, List *targetList) ...@@ -394,6 +394,28 @@ get_sortgrouplist_exprs(List *sgClauses, List *targetList)
* functions just above, and they don't seem to deserve their own file. * 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 * extract_grouping_ops - make an array of the equality operator OIDs
* for a SortGroupClause list * for a SortGroupClause list
......
...@@ -564,6 +564,30 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context) ...@@ -564,6 +564,30 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
break; 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)) else if (IsA(node, PlaceHolderVar))
{ {
if (((PlaceHolderVar *) node)->phlevelsup != 0) if (((PlaceHolderVar *) node)->phlevelsup != 0)
......
...@@ -1060,6 +1060,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) ...@@ -1060,6 +1060,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->groupClause = transformGroupClause(pstate, qry->groupClause = transformGroupClause(pstate,
stmt->groupClause, stmt->groupClause,
&qry->groupingSets,
&qry->targetList, &qry->targetList,
qry->sortClause, qry->sortClause,
EXPR_KIND_GROUP_BY, EXPR_KIND_GROUP_BY,
...@@ -1106,7 +1107,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) ...@@ -1106,7 +1107,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs; qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasAggs = pstate->p_hasAggs; 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); parseCheckAggregates(pstate, qry);
foreach(l, stmt->lockingClause) foreach(l, stmt->lockingClause)
...@@ -1566,7 +1567,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) ...@@ -1566,7 +1567,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks; qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs; qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasAggs = pstate->p_hasAggs; 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); parseCheckAggregates(pstate, qry);
foreach(l, lockingClause) foreach(l, lockingClause)
......
...@@ -371,6 +371,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -371,6 +371,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list relation_expr_list dostmt_opt_list
transform_element_list transform_type_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 <list> opt_fdw_options fdw_options
%type <defelt> fdw_option %type <defelt> fdw_option
...@@ -438,7 +442,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -438,7 +442,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> ExclusionConstraintList ExclusionConstraintElem %type <list> ExclusionConstraintList ExclusionConstraintElem
%type <list> func_arg_list %type <list> func_arg_list
%type <node> func_arg_expr %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 <node> case_expr case_arg when_clause case_default
%type <list> when_clause_list %type <list> when_clause_list
%type <ival> sub_type %type <ival> sub_type
...@@ -568,7 +572,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -568,7 +572,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE 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_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
...@@ -583,7 +587,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -583,7 +587,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS 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 HANDLER HAVING HEADER_P HOLD HOUR_P
...@@ -617,12 +621,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -617,12 +621,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA 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 ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P SYMMETRIC SYSID SYSTEM_P
...@@ -682,6 +686,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -682,6 +686,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* and for NULL so that it can follow b_expr in ColQualList without creating * and for NULL so that it can follow b_expr in ColQualList without creating
* postfix-operator problems. * 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 * The frame_bound productions UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING
* are even messier: since UNBOUNDED is an unreserved keyword (per spec!), * are even messier: since UNBOUNDED is an unreserved keyword (per spec!),
* there is no principled way to distinguish these from the productions * there is no principled way to distinguish these from the productions
...@@ -692,7 +701,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ...@@ -692,7 +701,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* blame any funny behavior of UNBOUNDED on the SQL standard, though. * blame any funny behavior of UNBOUNDED on the SQL standard, though.
*/ */
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */ %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 Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-' %left '+' '-'
%left '*' '/' '%' %left '*' '/' '%'
...@@ -10296,11 +10305,78 @@ first_or_next: FIRST_P { $$ = 0; } ...@@ -10296,11 +10305,78 @@ 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_clause:
GROUP_P BY expr_list { $$ = $3; } GROUP_P BY group_by_list { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; } | /*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_clause:
HAVING a_expr { $$ = $2; } HAVING a_expr { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; } | /*EMPTY*/ { $$ = NULL; }
...@@ -11953,15 +12029,33 @@ c_expr: columnref { $$ = $1; } ...@@ -11953,15 +12029,33 @@ c_expr: columnref { $$ = $1; }
n->location = @1; n->location = @1;
$$ = (Node *)n; $$ = (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); RowExpr *r = makeNode(RowExpr);
r->args = $1; r->args = $1;
r->row_typeid = InvalidOid; /* not analyzed yet */ r->row_typeid = InvalidOid; /* not analyzed yet */
r->colnames = NIL; /* to be filled in during analysis */ r->colnames = NIL; /* to be filled in during analysis */
r->row_format = COERCE_IMPLICIT_CAST; /* abuse */
r->location = @1; r->location = @1;
$$ = (Node *)r; $$ = (Node *)r;
} }
| GROUPING '(' expr_list ')'
{
GroupingFunc *g = makeNode(GroupingFunc);
g->args = $3;
g->location = @1;
$$ = (Node *)g;
}
; ;
func_application: func_name '(' ')' func_application: func_name '(' ')'
...@@ -12711,6 +12805,13 @@ row: ROW '(' expr_list ')' { $$ = $3; } ...@@ -12711,6 +12805,13 @@ row: ROW '(' expr_list ')' { $$ = $3; }
| '(' expr_list ',' a_expr ')' { $$ = lappend($2, $4); } | '(' 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; } sub_type: ANY { $$ = ANY_SUBLINK; }
| SOME { $$ = ANY_SUBLINK; } | SOME { $$ = ANY_SUBLINK; }
| ALL { $$ = ALL_SUBLINK; } | ALL { $$ = ALL_SUBLINK; }
...@@ -13520,6 +13621,7 @@ unreserved_keyword: ...@@ -13520,6 +13621,7 @@ unreserved_keyword:
| COPY | COPY
| COST | COST
| CSV | CSV
| CUBE
| CURRENT_P | CURRENT_P
| CURSOR | CURSOR
| CYCLE | CYCLE
...@@ -13668,6 +13770,7 @@ unreserved_keyword: ...@@ -13668,6 +13770,7 @@ unreserved_keyword:
| REVOKE | REVOKE
| ROLE | ROLE
| ROLLBACK | ROLLBACK
| ROLLUP
| ROWS | ROWS
| RULE | RULE
| SAVEPOINT | SAVEPOINT
...@@ -13682,6 +13785,7 @@ unreserved_keyword: ...@@ -13682,6 +13785,7 @@ unreserved_keyword:
| SERVER | SERVER
| SESSION | SESSION
| SET | SET
| SETS
| SHARE | SHARE
| SHOW | SHOW
| SIMPLE | SIMPLE
...@@ -13767,6 +13871,7 @@ col_name_keyword: ...@@ -13767,6 +13871,7 @@ col_name_keyword:
| EXTRACT | EXTRACT
| FLOAT_P | FLOAT_P
| GREATEST | GREATEST
| GROUPING
| INOUT | INOUT
| INT_P | INT_P
| INTEGER | INTEGER
......
This diff is collapsed.
This diff is collapsed.
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "parser/parse_relation.h" #include "parser/parse_relation.h"
#include "parser/parse_target.h" #include "parser/parse_target.h"
#include "parser/parse_type.h" #include "parser/parse_type.h"
#include "parser/parse_agg.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/xml.h" #include "utils/xml.h"
...@@ -269,6 +270,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) ...@@ -269,6 +270,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformMultiAssignRef(pstate, (MultiAssignRef *) expr); result = transformMultiAssignRef(pstate, (MultiAssignRef *) expr);
break; break;
case T_GroupingFunc:
result = transformGroupingFunc(pstate, (GroupingFunc *) expr);
break;
case T_NamedArgExpr: case T_NamedArgExpr:
{ {
NamedArgExpr *na = (NamedArgExpr *) expr; NamedArgExpr *na = (NamedArgExpr *) expr;
......
...@@ -1681,6 +1681,10 @@ FigureColnameInternal(Node *node, char **name) ...@@ -1681,6 +1681,10 @@ FigureColnameInternal(Node *node, char **name)
break; break;
case T_CollateClause: case T_CollateClause:
return FigureColnameInternal(((CollateClause *) node)->arg, name); return FigureColnameInternal(((CollateClause *) node)->arg, name);
case T_GroupingFunc:
/* make GROUPING() act like a regular function */
*name = "grouping";
return 2;
case T_SubLink: case T_SubLink:
switch (((SubLink *) node)->subLinkType) switch (((SubLink *) node)->subLinkType)
{ {
......
...@@ -2158,7 +2158,7 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols) ...@@ -2158,7 +2158,7 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
if (viewquery->distinctClause != NIL) if (viewquery->distinctClause != NIL)
return gettext_noop("Views containing DISTINCT are not automatically updatable."); 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."); return gettext_noop("Views containing GROUP BY are not automatically updatable.");
if (viewquery->havingQual != NULL) if (viewquery->havingQual != NULL)
......
...@@ -92,6 +92,12 @@ contain_aggs_of_level_walker(Node *node, ...@@ -92,6 +92,12 @@ contain_aggs_of_level_walker(Node *node,
return true; /* abort the tree traversal and return true */ return true; /* abort the tree traversal and return true */
/* else fall through to examine argument */ /* 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)) if (IsA(node, Query))
{ {
/* Recurse into subselects */ /* Recurse into subselects */
...@@ -157,6 +163,15 @@ locate_agg_of_level_walker(Node *node, ...@@ -157,6 +163,15 @@ locate_agg_of_level_walker(Node *node,
} }
/* else fall through to examine argument */ /* 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)) if (IsA(node, Query))
{ {
/* Recurse into subselects */ /* Recurse into subselects */
...@@ -712,6 +727,14 @@ IncrementVarSublevelsUp_walker(Node *node, ...@@ -712,6 +727,14 @@ IncrementVarSublevelsUp_walker(Node *node,
agg->agglevelsup += context->delta_sublevels_up; agg->agglevelsup += context->delta_sublevels_up;
/* fall through to recurse into argument */ /* 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)) if (IsA(node, PlaceHolderVar))
{ {
PlaceHolderVar *phv = (PlaceHolderVar *) node; PlaceHolderVar *phv = (PlaceHolderVar *) node;
......
This diff is collapsed.
...@@ -3158,6 +3158,8 @@ add_unique_group_var(PlannerInfo *root, List *varinfos, ...@@ -3158,6 +3158,8 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
* groupExprs - list of expressions being grouped by * groupExprs - list of expressions being grouped by
* input_rows - number of rows estimated to arrive at the group/unique * input_rows - number of rows estimated to arrive at the group/unique
* filter step * 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 * Given the lack of any cross-correlation statistics in the system, it's
* impossible to do anything really trustworthy with GROUP BY conditions * impossible to do anything really trustworthy with GROUP BY conditions
...@@ -3205,11 +3207,13 @@ add_unique_group_var(PlannerInfo *root, List *varinfos, ...@@ -3205,11 +3207,13 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
* but we don't have the info to do better). * but we don't have the info to do better).
*/ */
double 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; List *varinfos = NIL;
double numdistinct; double numdistinct;
ListCell *l; ListCell *l;
int i;
/* /*
* We don't ever want to return an estimate of zero groups, as that tends * 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) ...@@ -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 * for normal cases with GROUP BY or DISTINCT, but it is possible for
* corner cases with set operations.) * corner cases with set operations.)
*/ */
if (groupExprs == NIL) if (groupExprs == NIL || (pgset && list_length(*pgset) < 1))
return 1.0; return 1.0;
/* /*
...@@ -3236,6 +3240,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows) ...@@ -3236,6 +3240,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
*/ */
numdistinct = 1.0; numdistinct = 1.0;
i = 0;
foreach(l, groupExprs) foreach(l, groupExprs)
{ {
Node *groupexpr = (Node *) lfirst(l); Node *groupexpr = (Node *) lfirst(l);
...@@ -3243,6 +3248,10 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows) ...@@ -3243,6 +3248,10 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
List *varshere; List *varshere;
ListCell *l2; ListCell *l2;
/* is expression in this grouping set? */
if (pgset && !list_member_int(*pgset, i++))
continue;
/* Short-circuit for expressions returning boolean */ /* Short-circuit for expressions returning boolean */
if (exprType(groupexpr) == BOOLOID) if (exprType(groupexpr) == BOOLOID)
{ {
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201505152 #define CATALOG_VERSION_NO 201505153
#endif #endif
...@@ -83,6 +83,8 @@ extern void ExplainSeparatePlans(ExplainState *es); ...@@ -83,6 +83,8 @@ extern void ExplainSeparatePlans(ExplainState *es);
extern void ExplainPropertyList(const char *qlabel, List *data, extern void ExplainPropertyList(const char *qlabel, List *data,
ExplainState *es); ExplainState *es);
extern void ExplainPropertyListNested(const char *qlabel, List *data,
ExplainState *es);
extern void ExplainPropertyText(const char *qlabel, const char *value, extern void ExplainPropertyText(const char *qlabel, const char *value,
ExplainState *es); ExplainState *es);
extern void ExplainPropertyInteger(const char *qlabel, int value, extern void ExplainPropertyInteger(const char *qlabel, int value,
......
/*
* bipartite_match.h
*
* Copyright (c) 2015, PostgreSQL Global Development Group
*
* src/include/lib/bipartite_match.h
*/
#ifndef BIPARTITE_MATCH_H
#define BIPARTITE_MATCH_H
/*
* Given a bipartite graph consisting of nodes U numbered 1..nU, nodes V
* numbered 1..nV, and an adjacency map of undirected edges in the form
* adjacency[u] = [n, v1, v2, v3, ... vn], we wish to find a "maximum
* cardinality matching", which is defined as follows: a matching is a subset
* of the original edges such that no node has more than one edge, and a
* matching has maximum cardinality if there exists no other matching with a
* greater number of edges.
*
* This matching has various applications in graph theory, but the motivating
* example here is Dilworth's theorem: a partially-ordered set can be divided
* into the minimum number of chains (i.e. subsets X where x1 < x2 < x3 ...) by
* a bipartite graph construction. This gives us a polynomial-time solution to
* the problem of planning a collection of grouping sets with the provably
* minimal number of sort operations.
*/
typedef struct bipartite_match_state
{
int u_size; /* size of U */
int v_size; /* size of V */
int matching; /* number of edges in 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 */
} BipartiteMatchState;
BipartiteMatchState *BipartiteMatch(int u_size, int v_size, short **adjacency);
void BipartiteMatchFree(BipartiteMatchState *state);
#endif /* BIPARTITE_MATCH_H */
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "utils/reltrigger.h" #include "utils/reltrigger.h"
#include "utils/sortsupport.h" #include "utils/sortsupport.h"
#include "utils/tuplestore.h" #include "utils/tuplestore.h"
#include "utils/tuplesort.h"
/* ---------------- /* ----------------
...@@ -614,6 +615,22 @@ typedef struct AggrefExprState ...@@ -614,6 +615,22 @@ typedef struct AggrefExprState
int aggno; /* ID number for agg within its plan node */ int aggno; /* ID number for agg within its plan node */
} AggrefExprState; } 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;
struct AggState *aggstate;
List *clauses; /* integer list of column numbers */
} GroupingFuncExprState;
/* ---------------- /* ----------------
* WindowFuncExprState node * WindowFuncExprState node
* ---------------- * ----------------
...@@ -1796,19 +1813,33 @@ typedef struct GroupState ...@@ -1796,19 +1813,33 @@ typedef struct GroupState
/* these structs are private in nodeAgg.c: */ /* these structs are private in nodeAgg.c: */
typedef struct AggStatePerAggData *AggStatePerAgg; typedef struct AggStatePerAggData *AggStatePerAgg;
typedef struct AggStatePerGroupData *AggStatePerGroup; typedef struct AggStatePerGroupData *AggStatePerGroup;
typedef struct AggStatePerPhaseData *AggStatePerPhase;
typedef struct AggState typedef struct AggState
{ {
ScanState ss; /* its first field is NodeTag */ ScanState ss; /* its first field is NodeTag */
List *aggs; /* all Aggref nodes in targetlist & quals */ List *aggs; /* all Aggref nodes in targetlist & quals */
int numaggs; /* length of list (could be zero!) */ int numaggs; /* length of list (could be zero!) */
FmgrInfo *eqfunctions; /* per-grouping-field equality fns */ AggStatePerPhase phase; /* pointer to current phase data */
int numphases; /* number of phases */
int current_phase; /* current phase number */
FmgrInfo *hashfunctions; /* per-grouping-field hash fns */ FmgrInfo *hashfunctions; /* per-grouping-field hash fns */
AggStatePerAgg peragg; /* per-Aggref information */ 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 */ ExprContext *tmpcontext; /* econtext for input expressions */
AggStatePerAgg curperagg; /* identifies currently active aggregate */ AggStatePerAgg curperagg; /* identifies currently active aggregate */
bool input_done; /* indicates end of input */
bool agg_done; /* indicates completion of Agg scan */ bool agg_done; /* indicates completion of Agg scan */
int projected_set; /* The last projected grouping set */
int current_set; /* The current grouping set being evaluated */
Bitmapset *grouped_cols; /* grouped cols in current projection */
List *all_grouped_cols; /* list of all grouped cols in DESC order */
/* These fields are for grouping set phase data */
int maxsets; /* The max number of sets in any phase */
AggStatePerPhase phases; /* array of all phases */
Tuplesortstate *sort_in; /* sorted input to phases > 0 */
Tuplesortstate *sort_out; /* input is copied here for next phase */
TupleTableSlot *sort_slot; /* slot for sort results */
/* these fields are used in AGG_PLAIN and AGG_SORTED modes: */ /* these fields are used in AGG_PLAIN and AGG_SORTED modes: */
AggStatePerGroup pergroup; /* per-Aggref-per-group working state */ AggStatePerGroup pergroup; /* per-Aggref-per-group working state */
HeapTuple grp_firstTuple; /* copy of first tuple of current group */ HeapTuple grp_firstTuple; /* copy of first tuple of current group */
......
...@@ -81,4 +81,6 @@ extern DefElem *makeDefElem(char *name, Node *arg); ...@@ -81,4 +81,6 @@ extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg, extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction); DefElemAction defaction);
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
#endif /* MAKEFUNC_H */ #endif /* MAKEFUNC_H */
...@@ -136,6 +136,7 @@ typedef enum NodeTag ...@@ -136,6 +136,7 @@ typedef enum NodeTag
T_Const, T_Const,
T_Param, T_Param,
T_Aggref, T_Aggref,
T_GroupingFunc,
T_WindowFunc, T_WindowFunc,
T_ArrayRef, T_ArrayRef,
T_FuncExpr, T_FuncExpr,
...@@ -188,6 +189,7 @@ typedef enum NodeTag ...@@ -188,6 +189,7 @@ typedef enum NodeTag
T_GenericExprState, T_GenericExprState,
T_WholeRowVarExprState, T_WholeRowVarExprState,
T_AggrefExprState, T_AggrefExprState,
T_GroupingFuncExprState,
T_WindowFuncExprState, T_WindowFuncExprState,
T_ArrayRefExprState, T_ArrayRefExprState,
T_FuncExprState, T_FuncExprState,
...@@ -406,6 +408,7 @@ typedef enum NodeTag ...@@ -406,6 +408,7 @@ typedef enum NodeTag
T_RangeTblFunction, T_RangeTblFunction,
T_WithCheckOption, T_WithCheckOption,
T_SortGroupClause, T_SortGroupClause,
T_GroupingSet,
T_WindowClause, T_WindowClause,
T_PrivGrantee, T_PrivGrantee,
T_FuncWithArgs, T_FuncWithArgs,
......
This diff is collapsed.
...@@ -229,8 +229,9 @@ extern List *list_union_int(const List *list1, const List *list2); ...@@ -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_union_oid(const List *list1, const List *list2);
extern List *list_intersection(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(const List *list1, const List *list2);
extern List *list_difference_ptr(const List *list1, const List *list2); extern List *list_difference_ptr(const List *list1, const List *list2);
......
...@@ -712,6 +712,8 @@ typedef struct Agg ...@@ -712,6 +712,8 @@ typedef struct Agg
AttrNumber *grpColIdx; /* their indexes in the target list */ AttrNumber *grpColIdx; /* their indexes in the target list */
Oid *grpOperators; /* equality operators to compare with */ Oid *grpOperators; /* equality operators to compare with */
long numGroups; /* estimated number of groups in input */ long numGroups; /* estimated number of groups in input */
List *groupingSets; /* grouping sets to use */
List *chain; /* chained Agg/Sort nodes */
} Agg; } Agg;
/* ---------------- /* ----------------
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment