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
This commit is contained in:
Andres Freund 2015-05-16 03:40:59 +02:00
parent 6e4415c6aa
commit f3d3118532
63 changed files with 5255 additions and 618 deletions

View File

@ -2267,6 +2267,7 @@ JumbleQuery(pgssJumbleState *jstate, Query *query)
JumbleExpr(jstate, (Node *) query->onConflict);
JumbleExpr(jstate, (Node *) query->returningList);
JumbleExpr(jstate, (Node *) query->groupClause);
JumbleExpr(jstate, (Node *) query->groupingSets);
JumbleExpr(jstate, query->havingQual);
JumbleExpr(jstate, (Node *) query->windowClause);
JumbleExpr(jstate, (Node *) query->distinctClause);
@ -2397,6 +2398,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) expr->aggfilter);
}
break;
case T_GroupingFunc:
{
GroupingFunc *grpnode = (GroupingFunc *) node;
JumbleExpr(jstate, (Node *) grpnode->refs);
}
break;
case T_WindowFunc:
{
WindowFunc *expr = (WindowFunc *) node;
@ -2698,6 +2706,12 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) lfirst(temp));
}
break;
case T_IntList:
foreach(temp, (List *) node)
{
APP_JUMB(lfirst_int(temp));
}
break;
case T_SortGroupClause:
{
SortGroupClause *sgc = (SortGroupClause *) node;
@ -2708,6 +2722,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
APP_JUMB(sgc->nulls_first);
}
break;
case T_GroupingSet:
{
GroupingSet *gsnode = (GroupingSet *) node;
JumbleExpr(jstate, (Node *) gsnode->content);
}
break;
case T_WindowClause:
{
WindowClause *wc = (WindowClause *) node;

View File

@ -12228,7 +12228,9 @@ NULL baz</literallayout>(3 rows)</entry>
<xref linkend="functions-aggregate-statistics-table">.
The built-in ordered-set aggregate functions
are listed in <xref linkend="functions-orderedset-table"> and
<xref linkend="functions-hypothetical-table">.
<xref linkend="functions-hypothetical-table">. Grouping operations,
which are closely related to aggregate functions, are listed in
<xref linkend="functions-grouping-table">.
The special syntax considerations for aggregate
functions are explained in <xref linkend="syntax-aggregates">.
Consult <xref linkend="tutorial-agg"> for additional introductory
@ -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.
</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 id="functions-window">

View File

@ -1183,6 +1183,181 @@ SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit
</para>
</sect2>
<sect2 id="queries-grouping-sets">
<title><literal>GROUPING SETS</>, <literal>CUBE</>, and <literal>ROLLUP</></title>
<indexterm zone="queries-grouping-sets">
<primary>GROUPING SETS</primary>
</indexterm>
<indexterm zone="queries-grouping-sets">
<primary>CUBE</primary>
</indexterm>
<indexterm zone="queries-grouping-sets">
<primary>ROLLUP</primary>
</indexterm>
<para>
More complex grouping operations than those described above are possible
using the concept of <firstterm>grouping sets</>. The data selected by
the <literal>FROM</> and <literal>WHERE</> clauses is grouped separately
by each specified grouping set, aggregates computed for each group just as
for simple <literal>GROUP BY</> clauses, and then the results returned.
For example:
<screen>
<prompt>=&gt;</> <userinput>SELECT * FROM items_sold;</>
brand | size | sales
-------+------+-------
Foo | L | 10
Foo | M | 20
Bar | M | 15
Bar | L | 5
(4 rows)
<prompt>=&gt;</> <userinput>SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());</>
brand | size | sum
-------+------+-----
Foo | | 30
Bar | | 20
| L | 15
| M | 35
| | 50
(5 rows)
</screen>
</para>
<para>
Each sublist of <literal>GROUPING SETS</> may specify zero or more columns
or expressions and is interpreted the same way as though it were directly
in the <literal>GROUP BY</> clause. An empty grouping set means that all
rows are aggregated down to a single group (which is output even if no
input rows were present), as described above for the case of aggregate
functions with no <literal>GROUP BY</> clause.
</para>
<para>
References to the grouping columns or expressions are replaced
by <literal>NULL</> values in result rows for grouping sets in which those
columns do not appear. To distinguish which grouping a particular output
row resulted from, see <xref linkend="functions-grouping-table">.
</para>
<para>
A shorthand notation is provided for specifying two common types of grouping set.
A clause of the form
<programlisting>
ROLLUP ( <replaceable>e1</>, <replaceable>e2</>, <replaceable>e3</>, ... )
</programlisting>
represents the given list of expressions and all prefixes of the list including
the empty list; thus it is equivalent to
<programlisting>
GROUPING SETS (
( <replaceable>e1</>, <replaceable>e2</>, <replaceable>e3</>, ... ),
...
( <replaceable>e1</>, <replaceable>e2</> )
( <replaceable>e1</> )
( )
)
</programlisting>
This is commonly used for analysis over hierarchical data; e.g. total
salary by department, division, and company-wide total.
</para>
<para>
A clause of the form
<programlisting>
CUBE ( <replaceable>e1</>, <replaceable>e2</>, ... )
</programlisting>
represents the given list and all of its possible subsets (i.e. the power
set). Thus
<programlisting>
CUBE ( a, b, c )
</programlisting>
is equivalent to
<programlisting>
GROUPING SETS (
( a, b, c ),
( a, b ),
( a, c ),
( a ),
( b, c ),
( b ),
( c ),
( ),
)
</programlisting>
</para>
<para>
The individual elements of a <literal>CUBE</> or <literal>ROLLUP</>
clause may be either individual expressions, or sub-lists of elements in
parentheses. In the latter case, the sub-lists are treated as single
units for the purposes of generating the individual grouping sets.
For example:
<programlisting>
CUBE ( (a,b), (c,d) )
</programlisting>
is equivalent to
<programlisting>
GROUPING SETS (
( a, b, c, d )
( a, b )
( c, d )
( )
)
</programlisting>
and
<programlisting>
ROLLUP ( a, (b,c), d )
</programlisting>
is equivalent to
<programlisting>
GROUPING SETS (
( a, b, c, d )
( a, b, c )
( a )
( )
)
</programlisting>
</para>
<para>
The <literal>CUBE</> and <literal>ROLLUP</> constructs can be used either
directly in the <literal>GROUP BY</> clause, or nested inside a
<literal>GROUPING SETS</> clause. If one <literal>GROUPING SETS</> clause
is nested inside another, the effect is the same as if all the elements of
the inner clause had been written directly in the outer clause.
</para>
<para>
If multiple grouping items are specified in a single <literal>GROUP BY</>
clause, then the final list of grouping sets is the cross product of the
individual items. For example:
<programlisting>
GROUP BY a, CUBE(b,c), GROUPING SETS ((d), (e))
</programlisting>
is equivalent to
<programlisting>
GROUP BY GROUPING SETS (
(a,b,c,d), (a,b,c,e),
(a,b,d), (a,b,e),
(a,c,d), (a,c,e),
(a,d), (a,e)
)
</programlisting>
</para>
<note>
<para>
The construct <literal>(a,b)</> is normally recognized in expressions as
a <link linkend="sql-syntax-row-constructors">row constructor</link>.
Within the <literal>GROUP BY</> clause, this does not apply at the top
levels of expressions, and <literal>(a,b)</> is parsed as a list of
expressions as described above. If for some reason you <emphasis>need</>
a row constructor in a grouping expression, use <literal>ROW(a,b)</>.
</para>
</note>
</sect2>
<sect2 id="queries-window">
<title>Window Function Processing</title>

View File

@ -37,7 +37,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
[ * | <replaceable class="parameter">expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ]
[ FROM <replaceable class="parameter">from_item</replaceable> [, ...] ]
[ WHERE <replaceable class="parameter">condition</replaceable> ]
[ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...] ]
[ GROUP BY <replaceable class="parameter">grouping_element</replaceable> [, ...] ]
[ HAVING <replaceable class="parameter">condition</replaceable> [, ...] ]
[ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceable class="parameter">window_definition</replaceable> ) [, ...] ]
[ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] <replaceable class="parameter">select</replaceable> ]
@ -60,6 +60,15 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
[ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
<replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
<phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
( )
<replaceable class="parameter">expression</replaceable>
( <replaceable class="parameter">expression</replaceable> [, ...] )
ROLLUP ( { <replaceable class="parameter">expression</replaceable> | ( <replaceable class="parameter">expression</replaceable> [, ...] ) } [, ...] )
CUBE ( { <replaceable class="parameter">expression</replaceable> | ( <replaceable class="parameter">expression</replaceable> [, ...] ) } [, ...] )
GROUPING SETS ( <replaceable class="parameter">grouping_element</replaceable> [, ...] )
<phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase>
<replaceable class="parameter">with_query_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] AS ( <replaceable class="parameter">select</replaceable> | <replaceable class="parameter">values</replaceable> | <replaceable class="parameter">insert</replaceable> | <replaceable class="parameter">update</replaceable> | <replaceable class="parameter">delete</replaceable> )
@ -665,22 +674,34 @@ WHERE <replaceable class="parameter">condition</replaceable>
<para>
The optional <literal>GROUP BY</literal> clause has the general form
<synopsis>
GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
GROUP BY <replaceable class="parameter">grouping_element</replaceable> [, ...]
</synopsis>
</para>
<para>
<literal>GROUP BY</literal> will condense into a single row all
selected rows that share the same values for the grouped
expressions. <replaceable
class="parameter">expression</replaceable> can be an input column
name, or the name or ordinal number of an output column
(<command>SELECT</command> list item), or an arbitrary
expressions. An <replaceable
class="parameter">expression</replaceable> used inside a
<replaceable class="parameter">grouping_element</replaceable>
can be an input column name, or the name or ordinal number of an
output column (<command>SELECT</command> list item), or an arbitrary
expression formed from input-column values. In case of ambiguity,
a <literal>GROUP BY</literal> name will be interpreted as an
input-column name rather than an output column name.
</para>
<para>
If any of <literal>GROUPING SETS</>, <literal>ROLLUP</> or
<literal>CUBE</> are present as grouping elements, then the
<literal>GROUP BY</> clause as a whole defines some number of
independent <replaceable>grouping sets</>. The effect of this is
equivalent to constructing a <literal>UNION ALL</> between
subqueries with the individual grouping sets as their
<literal>GROUP BY</> clauses. For further details on the handling
of grouping sets see <xref linkend="queries-grouping-sets">.
</para>
<para>
Aggregate functions, if any are used, are computed across all rows
making up each group, producing a separate value for each group.

View File

@ -467,9 +467,9 @@ T331 Basic roles YES
T332 Extended roles NO mostly supported
T341 Overloading of SQL-invoked functions and procedures YES
T351 Bracketed SQL comments (/*...*/ comments) YES
T431 Extended grouping capabilities NO
T432 Nested and concatenated GROUPING SETS NO
T433 Multiargument GROUPING function NO
T431 Extended grouping capabilities YES
T432 Nested and concatenated GROUPING SETS YES
T433 Multiargument GROUPING function YES
T434 GROUP BY DISTINCT NO
T441 ABS and MOD functions YES
T461 Symmetric BETWEEN predicate YES

View File

@ -82,6 +82,12 @@ static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
ExplainState *es);
static void show_agg_keys(AggState *astate, List *ancestors,
ExplainState *es);
static void show_grouping_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,
ExplainState *es);
static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
@ -1851,18 +1857,116 @@ show_agg_keys(AggState *astate, List *ancestors,
{
Agg *plan = (Agg *) astate->ss.ps.plan;
if (plan->numCols > 0)
if (plan->numCols > 0 || plan->groupingSets)
{
/* The key columns refer to the tlist of the child plan */
ancestors = lcons(astate, ancestors);
show_sort_group_keys(outerPlanState(astate), "Group Key",
plan->numCols, plan->grpColIdx,
NULL, NULL, NULL,
ancestors, es);
if (plan->groupingSets)
show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
else
show_sort_group_keys(outerPlanState(astate), "Group Key",
plan->numCols, plan->grpColIdx,
NULL, NULL, NULL,
ancestors, es);
ancestors = list_delete_first(ancestors);
}
}
static void
show_grouping_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.
*/
@ -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.
*

View File

@ -181,6 +181,9 @@ static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
ExprContext *econtext,
bool *isNull, ExprDoneCond *isDone);
/* ----------------------------------------------------------------
@ -3016,6 +3019,44 @@ ExecEvalCaseTestExpr(ExprState *exprstate,
return econtext->caseValue_datum;
}
/*
* ExecEvalGroupingFuncExpr
*
* Return a bitmask with a bit for each (unevaluated) argument expression
* (rightmost arg is least significant bit).
*
* A bit is set if the corresponding expression is NOT part of the set of
* grouping expressions in the current grouping set.
*/
static Datum
ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
{
int result = 0;
int attnum = 0;
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
* ----------------------------------------------------------------
@ -4482,6 +4523,28 @@ ExecInitExpr(Expr *node, PlanState *parent)
state = (ExprState *) astate;
}
break;
case T_GroupingFunc:
{
GroupingFunc *grp_node = (GroupingFunc *) node;
GroupingFuncExprState *grp_state = makeNode(GroupingFuncExprState);
Agg *agg = NULL;
if (!parent || !IsA(parent, 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:
{
WindowFunc *wfunc = (WindowFunc *) node;

View File

@ -642,9 +642,10 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
/*
* Don't examine the arguments or filters of Aggrefs or WindowFuncs,
* because those do not represent expressions to be evaluated within the
* overall targetlist's econtext.
* overall targetlist's econtext. GroupingFunc arguments are never
* evaluated at all.
*/
if (IsA(node, Aggref))
if (IsA(node, Aggref) || IsA(node, GroupingFunc))
return false;
if (IsA(node, WindowFunc))
return false;

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ subdir = src/backend/lib
top_builddir = ../../..
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

View File

@ -0,0 +1,161 @@
/*-------------------------------------------------------------------------
*
* 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;
}

View File

@ -839,6 +839,8 @@ _copyAgg(const Agg *from)
COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
}
COPY_SCALAR_FIELD(numGroups);
COPY_NODE_FIELD(groupingSets);
COPY_NODE_FIELD(chain);
return newnode;
}
@ -1208,6 +1210,23 @@ _copyAggref(const Aggref *from)
return newnode;
}
/*
* _copyGroupingFunc
*/
static GroupingFunc *
_copyGroupingFunc(const GroupingFunc *from)
{
GroupingFunc *newnode = makeNode(GroupingFunc);
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(refs);
COPY_NODE_FIELD(cols);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
return newnode;
}
/*
* _copyWindowFunc
*/
@ -2152,6 +2171,18 @@ _copySortGroupClause(const SortGroupClause *from)
return newnode;
}
static GroupingSet *
_copyGroupingSet(const GroupingSet *from)
{
GroupingSet *newnode = makeNode(GroupingSet);
COPY_SCALAR_FIELD(kind);
COPY_NODE_FIELD(content);
COPY_LOCATION_FIELD(location);
return newnode;
}
static WindowClause *
_copyWindowClause(const WindowClause *from)
{
@ -2676,6 +2707,7 @@ _copyQuery(const Query *from)
COPY_NODE_FIELD(onConflict);
COPY_NODE_FIELD(returningList);
COPY_NODE_FIELD(groupClause);
COPY_NODE_FIELD(groupingSets);
COPY_NODE_FIELD(havingQual);
COPY_NODE_FIELD(windowClause);
COPY_NODE_FIELD(distinctClause);
@ -4309,6 +4341,9 @@ copyObject(const void *from)
case T_Aggref:
retval = _copyAggref(from);
break;
case T_GroupingFunc:
retval = _copyGroupingFunc(from);
break;
case T_WindowFunc:
retval = _copyWindowFunc(from);
break;
@ -4878,6 +4913,9 @@ copyObject(const void *from)
case T_SortGroupClause:
retval = _copySortGroupClause(from);
break;
case T_GroupingSet:
retval = _copyGroupingSet(from);
break;
case T_WindowClause:
retval = _copyWindowClause(from);
break;

View File

@ -207,6 +207,21 @@ _equalAggref(const Aggref *a, const Aggref *b)
return true;
}
static bool
_equalGroupingFunc(const GroupingFunc *a, const GroupingFunc *b)
{
COMPARE_NODE_FIELD(args);
/*
* We must not compare the refs or cols field
*/
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
{
@ -896,6 +911,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_NODE_FIELD(onConflict);
COMPARE_NODE_FIELD(returningList);
COMPARE_NODE_FIELD(groupClause);
COMPARE_NODE_FIELD(groupingSets);
COMPARE_NODE_FIELD(havingQual);
COMPARE_NODE_FIELD(windowClause);
COMPARE_NODE_FIELD(distinctClause);
@ -2426,6 +2442,16 @@ _equalSortGroupClause(const SortGroupClause *a, const SortGroupClause *b)
return true;
}
static bool
_equalGroupingSet(const GroupingSet *a, const GroupingSet *b)
{
COMPARE_SCALAR_FIELD(kind);
COMPARE_NODE_FIELD(content);
COMPARE_LOCATION_FIELD(location);
return true;
}
static bool
_equalWindowClause(const WindowClause *a, const WindowClause *b)
{
@ -2693,6 +2719,9 @@ equal(const void *a, const void *b)
case T_Aggref:
retval = _equalAggref(a, b);
break;
case T_GroupingFunc:
retval = _equalGroupingFunc(a, b);
break;
case T_WindowFunc:
retval = _equalWindowFunc(a, b);
break;
@ -3249,6 +3278,9 @@ equal(const void *a, const void *b)
case T_SortGroupClause:
retval = _equalSortGroupClause(a, b);
break;
case T_GroupingSet:
retval = _equalGroupingSet(a, b);
break;
case T_WindowClause:
retval = _equalWindowClause(a, b);
break;

View File

@ -822,6 +822,32 @@ list_intersection(const List *list1, const List *list2)
return result;
}
/*
* As list_intersection but operates on lists of integers.
*/
List *
list_intersection_int(const List *list1, const List *list2)
{
List *result;
const ListCell *cell;
if (list1 == NIL || list2 == NIL)
return NIL;
Assert(IsIntegerList(list1));
Assert(IsIntegerList(list2));
result = NIL;
foreach(cell, list1)
{
if (list_member_int(list2, lfirst_int(cell)))
result = lappend_int(result, lfirst_int(cell));
}
check_list_invariants(result);
return result;
}
/*
* Return a list that contains all the cells in list1 that are not in
* list2. The returned list is freshly allocated via palloc(), but the

View File

@ -554,3 +554,18 @@ makeFuncCall(List *name, List *args, int location)
n->location = location;
return n;
}
/*
* makeGroupingSet
*
*/
GroupingSet *
makeGroupingSet(GroupingSetKind kind, List *content, int location)
{
GroupingSet *n = makeNode(GroupingSet);
n->kind = kind;
n->content = content;
n->location = location;
return n;
}

View File

@ -54,6 +54,9 @@ exprType(const Node *expr)
case T_Aggref:
type = ((const Aggref *) expr)->aggtype;
break;
case T_GroupingFunc:
type = INT4OID;
break;
case T_WindowFunc:
type = ((const WindowFunc *) expr)->wintype;
break;
@ -750,6 +753,9 @@ exprCollation(const Node *expr)
case T_Aggref:
coll = ((const Aggref *) expr)->aggcollid;
break;
case T_GroupingFunc:
coll = InvalidOid;
break;
case T_WindowFunc:
coll = ((const WindowFunc *) expr)->wincollid;
break;
@ -986,6 +992,9 @@ exprSetCollation(Node *expr, Oid collation)
case T_Aggref:
((Aggref *) expr)->aggcollid = collation;
break;
case T_GroupingFunc:
Assert(!OidIsValid(collation));
break;
case T_WindowFunc:
((WindowFunc *) expr)->wincollid = collation;
break;
@ -1202,6 +1211,9 @@ exprLocation(const Node *expr)
/* function name should always be the first thing */
loc = ((const Aggref *) expr)->location;
break;
case T_GroupingFunc:
loc = ((const GroupingFunc *) expr)->location;
break;
case T_WindowFunc:
/* function name should always be the first thing */
loc = ((const WindowFunc *) expr)->location;
@ -1491,6 +1503,9 @@ exprLocation(const Node *expr)
/* XMLSERIALIZE keyword should always be the first thing */
loc = ((const XmlSerialize *) expr)->location;
break;
case T_GroupingSet:
loc = ((const GroupingSet *) expr)->location;
break;
case T_WithClause:
loc = ((const WithClause *) expr)->location;
break;
@ -1685,6 +1700,15 @@ expression_tree_walker(Node *node,
return true;
}
break;
case T_GroupingFunc:
{
GroupingFunc *grouping = (GroupingFunc *) node;
if (expression_tree_walker((Node *) grouping->args,
walker, context))
return true;
}
break;
case T_WindowFunc:
{
WindowFunc *expr = (WindowFunc *) node;
@ -2243,6 +2267,29 @@ expression_tree_mutator(Node *node,
return (Node *) newnode;
}
break;
case T_GroupingFunc:
{
GroupingFunc *grouping = (GroupingFunc *) node;
GroupingFunc *newnode;
FLATCOPY(newnode, grouping, GroupingFunc);
MUTATE(newnode->args, grouping->args, List *);
/*
* We assume here that mutating the arguments does not change
* the semantics, i.e. that the arguments are not mutated in a
* way that makes them semantically different from their
* previously matching expressions in the GROUP BY clause.
*
* If a mutator somehow wanted to do this, it would have to
* handle the refs and cols lists itself as appropriate.
*/
newnode->refs = list_copy(grouping->refs);
newnode->cols = list_copy(grouping->cols);
return (Node *) newnode;
}
break;
case T_WindowFunc:
{
WindowFunc *wfunc = (WindowFunc *) node;
@ -2962,6 +3009,8 @@ raw_expression_tree_walker(Node *node,
break;
case T_RangeVar:
return walker(((RangeVar *) node)->alias, context);
case T_GroupingFunc:
return walker(((GroupingFunc *) node)->args, context);
case T_SubLink:
{
SubLink *sublink = (SubLink *) node;
@ -3287,6 +3336,8 @@ raw_expression_tree_walker(Node *node,
/* for now, constraints are ignored */
}
break;
case T_GroupingSet:
return walker(((GroupingSet *) node)->content, context);
case T_LockingClause:
return walker(((LockingClause *) node)->lockedRels, context);
case T_XmlSerialize:

View File

@ -679,6 +679,9 @@ _outAgg(StringInfo str, const Agg *node)
appendStringInfo(str, " %u", node->grpOperators[i]);
WRITE_LONG_FIELD(numGroups);
WRITE_NODE_FIELD(groupingSets);
WRITE_NODE_FIELD(chain);
}
static void
@ -1003,6 +1006,18 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_LOCATION_FIELD(location);
}
static void
_outGroupingFunc(StringInfo str, const GroupingFunc *node)
{
WRITE_NODE_TYPE("GROUPINGFUNC");
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(refs);
WRITE_NODE_FIELD(cols);
WRITE_INT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
}
static void
_outWindowFunc(StringInfo str, const WindowFunc *node)
{
@ -2364,6 +2379,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_NODE_FIELD(onConflict);
WRITE_NODE_FIELD(returningList);
WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(groupingSets);
WRITE_NODE_FIELD(havingQual);
WRITE_NODE_FIELD(windowClause);
WRITE_NODE_FIELD(distinctClause);
@ -2398,6 +2414,16 @@ _outSortGroupClause(StringInfo str, const SortGroupClause *node)
WRITE_BOOL_FIELD(hashable);
}
static void
_outGroupingSet(StringInfo str, const GroupingSet *node)
{
WRITE_NODE_TYPE("GROUPINGSET");
WRITE_ENUM_FIELD(kind, GroupingSetKind);
WRITE_NODE_FIELD(content);
WRITE_LOCATION_FIELD(location);
}
static void
_outWindowClause(StringInfo str, const WindowClause *node)
{
@ -3087,6 +3113,9 @@ _outNode(StringInfo str, const void *obj)
case T_Aggref:
_outAggref(str, obj);
break;
case T_GroupingFunc:
_outGroupingFunc(str, obj);
break;
case T_WindowFunc:
_outWindowFunc(str, obj);
break;
@ -3349,6 +3378,9 @@ _outNode(StringInfo str, const void *obj)
case T_SortGroupClause:
_outSortGroupClause(str, obj);
break;
case T_GroupingSet:
_outGroupingSet(str, obj);
break;
case T_WindowClause:
_outWindowClause(str, obj);
break;

View File

@ -217,6 +217,7 @@ _readQuery(void)
READ_NODE_FIELD(onConflict);
READ_NODE_FIELD(returningList);
READ_NODE_FIELD(groupClause);
READ_NODE_FIELD(groupingSets);
READ_NODE_FIELD(havingQual);
READ_NODE_FIELD(windowClause);
READ_NODE_FIELD(distinctClause);
@ -292,6 +293,21 @@ _readSortGroupClause(void)
READ_DONE();
}
/*
* _readGroupingSet
*/
static GroupingSet *
_readGroupingSet(void)
{
READ_LOCALS(GroupingSet);
READ_ENUM_FIELD(kind, GroupingSetKind);
READ_NODE_FIELD(content);
READ_LOCATION_FIELD(location);
READ_DONE();
}
/*
* _readWindowClause
*/
@ -551,6 +567,23 @@ _readAggref(void)
READ_DONE();
}
/*
* _readGroupingFunc
*/
static GroupingFunc *
_readGroupingFunc(void)
{
READ_LOCALS(GroupingFunc);
READ_NODE_FIELD(args);
READ_NODE_FIELD(refs);
READ_NODE_FIELD(cols);
READ_INT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
READ_DONE();
}
/*
* _readWindowFunc
*/
@ -1386,6 +1419,8 @@ parseNodeString(void)
return_value = _readWithCheckOption();
else if (MATCH("SORTGROUPCLAUSE", 15))
return_value = _readSortGroupClause();
else if (MATCH("GROUPINGSET", 11))
return_value = _readGroupingSet();
else if (MATCH("WINDOWCLAUSE", 12))
return_value = _readWindowClause();
else if (MATCH("ROWMARKCLAUSE", 13))
@ -1412,6 +1447,8 @@ parseNodeString(void)
return_value = _readParam();
else if (MATCH("AGGREF", 6))
return_value = _readAggref();
else if (MATCH("GROUPINGFUNC", 12))
return_value = _readGroupingFunc();
else if (MATCH("WINDOWFUNC", 10))
return_value = _readWindowFunc();
else if (MATCH("ARRAYREF", 8))

View File

@ -1290,6 +1290,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
*/
if (parse->hasAggs ||
parse->groupClause ||
parse->groupingSets ||
parse->havingQual ||
parse->distinctClause ||
parse->sortClause ||
@ -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
* qual really refers to the group-result rows).
*/
if (subquery->hasAggs || subquery->groupClause || subquery->havingQual)
if (subquery->hasAggs || subquery->groupClause || subquery->groupingSets || subquery->havingQual)
subquery->havingQual = make_and_qual(subquery->havingQual, qual);
else
subquery->jointree->quals =

View File

@ -1954,7 +1954,8 @@ adjust_rowcount_for_semijoins(PlannerInfo *root,
nraw = approximate_joinrel_size(root, sjinfo->syn_righthand);
nunique = estimate_num_groups(root,
sjinfo->semi_rhs_exprs,
nraw);
nraw,
NULL);
if (rowcount > nunique)
rowcount = nunique;
}

View File

@ -581,6 +581,7 @@ query_supports_distinctness(Query *query)
{
if (query->distinctClause != NIL ||
query->groupClause != NIL ||
query->groupingSets != NIL ||
query->hasAggs ||
query->havingQual ||
query->setOperations)
@ -649,10 +650,10 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
}
/*
* Similarly, GROUP BY guarantees uniqueness if all the grouped columns
* appear in colnos and operator semantics match.
* Similarly, GROUP BY without GROUPING SETS guarantees uniqueness if all
* the grouped columns appear in colnos and operator semantics match.
*/
if (query->groupClause)
if (query->groupClause && !query->groupingSets)
{
foreach(l, query->groupClause)
{
@ -668,6 +669,27 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
if (l == NULL) /* had matches for all? */
return true;
}
else if (query->groupingSets)
{
/*
* If we have grouping sets with expressions, we probably
* don't have uniqueness and analysis would be hard. Punt.
*/
if (query->groupClause)
return false;
/*
* If we have no groupClause (therefore no grouping expressions),
* we might have one or many empty grouping sets. If there's just
* one, then we're returning only one row and are certainly unique.
* But otherwise, we know we're certainly not unique.
*/
if (list_length(query->groupingSets) == 1 &&
((GroupingSet *)linitial(query->groupingSets))->kind == GROUPING_SET_EMPTY)
return true;
else
return false;
}
else
{
/*

View File

@ -1042,6 +1042,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
numGroupCols,
groupColIdx,
groupOperators,
NIL,
numGroups,
subplan);
}
@ -4492,6 +4493,7 @@ Agg *
make_agg(PlannerInfo *root, List *tlist, List *qual,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
List *groupingSets,
long numGroups,
Plan *lefttree)
{
@ -4521,10 +4523,12 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
* group otherwise.
*/
if (aggstrategy == AGG_PLAIN)
plan->plan_rows = 1;
plan->plan_rows = groupingSets ? list_length(groupingSets) : 1;
else
plan->plan_rows = numGroups;
node->groupingSets = groupingSets;
/*
* We also need to account for the cost of evaluation of the qual (ie, the
* HAVING clause) and the tlist. Note that cost_qual_eval doesn't charge
@ -4545,6 +4549,7 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
plan->qual = qual;
plan->targetlist = tlist;
plan->lefttree = lefttree;
plan->righttree = NULL;

View File

@ -96,7 +96,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
* performs assorted processing related to these features between calling
* preprocess_minmax_aggregates and optimize_minmax_aggregates.)
*/
if (parse->groupClause || parse->hasWindowFuncs)
if (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs)
return;
/*

File diff suppressed because it is too large Load Diff

View File

@ -140,7 +140,6 @@ static bool fix_opfuncids_walker(Node *node, void *context);
static bool extract_query_dependencies_walker(Node *node,
PlannerInfo *context);
/*****************************************************************************
*
* SUBPLAN REFERENCES
@ -656,6 +655,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
}
break;
case T_Agg:
set_upper_references(root, plan, rtoffset);
break;
case T_Group:
set_upper_references(root, plan, rtoffset);
break;
@ -1229,6 +1230,7 @@ copyVar(Var *var)
* We must look up operator opcode info for OpExpr and related nodes,
* add OIDs from regclass Const nodes into root->glob->relationOids, and
* add catalog TIDs for user-defined functions into root->glob->invalItems.
* We also fill in column index lists for GROUPING() expressions.
*
* We assume it's okay to update opcode info in-place. So this could possibly
* scribble on the planner's input data structures, but it's OK.
@ -1292,6 +1294,31 @@ fix_expr_common(PlannerInfo *root, Node *node)
lappend_oid(root->glob->relationOids,
DatumGetObjectId(con->constvalue));
}
else if (IsA(node, GroupingFunc))
{
GroupingFunc *g = (GroupingFunc *) node;
AttrNumber *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,
return rlist;
}
/*****************************************************************************
* OPERATOR REGPROC LOOKUP
*****************************************************************************/

View File

@ -335,6 +335,48 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg)
return retval;
}
/*
* Generate a Param node to replace the given GroupingFunc expression which is
* expected to have agglevelsup > 0 (ie, it is not local).
*/
static Param *
replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
{
Param *retval;
PlannerParamItem *pitem;
Index levelsup;
Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level);
/* Find the query level the GroupingFunc belongs to */
for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--)
root = root->parent_root;
/*
* It does not seem worthwhile to try to match duplicate outer aggs. Just
* make a new slot every time.
*/
grp = (GroupingFunc *) copyObject(grp);
IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0);
Assert(grp->agglevelsup == 0);
pitem = makeNode(PlannerParamItem);
pitem->item = (Node *) grp;
pitem->paramId = root->glob->nParamExec++;
root->plan_params = lappend(root->plan_params, pitem);
retval = makeNode(Param);
retval->paramkind = PARAM_EXEC;
retval->paramid = pitem->paramId;
retval->paramtype = exprType((Node *) grp);
retval->paramtypmod = -1;
retval->paramcollid = InvalidOid;
retval->location = grp->location;
return retval;
}
/*
* Generate a new Param node that will not conflict with any other.
*
@ -1494,14 +1536,16 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query)
{
/*
* We don't try to simplify at all if the query uses set operations,
* aggregates, modifying CTEs, HAVING, OFFSET, or FOR UPDATE/SHARE; none
* of these seem likely in normal usage and their possible effects are
* complex. (Note: we could ignore an "OFFSET 0" clause, but that
* traditionally is used as an optimization fence, so we don't.)
* aggregates, grouping sets, modifying CTEs, HAVING, OFFSET, or FOR
* UPDATE/SHARE; none of these seem likely in normal usage and their
* possible effects are complex. (Note: we could ignore an "OFFSET 0"
* clause, but that traditionally is used as an optimization fence, so we
* don't.)
*/
if (query->commandType != CMD_SELECT ||
query->setOperations ||
query->hasAggs ||
query->groupingSets ||
query->hasWindowFuncs ||
query->hasModifyingCTE ||
query->havingQual ||
@ -1851,6 +1895,11 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
if (((Aggref *) node)->agglevelsup > 0)
return (Node *) replace_outer_agg(root, (Aggref *) node);
}
if (IsA(node, GroupingFunc))
{
if (((GroupingFunc *) node)->agglevelsup > 0)
return (Node *) replace_outer_grouping(root, (GroupingFunc *) node);
}
return expression_tree_mutator(node,
replace_correlation_vars_mutator,
(void *) root);

View File

@ -1412,6 +1412,7 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
if (subquery->hasAggs ||
subquery->hasWindowFuncs ||
subquery->groupClause ||
subquery->groupingSets ||
subquery->havingQual ||
subquery->sortClause ||
subquery->distinctClause ||

View File

@ -268,13 +268,15 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
*/
if (pNumGroups)
{
if (subquery->groupClause || subquery->distinctClause ||
if (subquery->groupClause || subquery->groupingSets ||
subquery->distinctClause ||
subroot->hasHavingQual || subquery->hasAggs)
*pNumGroups = subplan->plan_rows;
else
*pNumGroups = estimate_num_groups(subroot,
get_tlist_exprs(subquery->targetList, false),
subplan->plan_rows);
subplan->plan_rows,
NULL);
}
/*
@ -771,6 +773,7 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
extract_grouping_cols(groupList,
plan->targetlist),
extract_grouping_ops(groupList),
NIL,
numGroups,
plan);
/* Hashed aggregation produces randomly-ordered results */

View File

@ -4353,6 +4353,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree->jointree->fromlist ||
querytree->jointree->quals ||
querytree->groupClause ||
querytree->groupingSets ||
querytree->havingQual ||
querytree->windowClause ||
querytree->distinctClause ||

View File

@ -1214,7 +1214,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
/* Estimate number of output rows */
pathnode->path.rows = estimate_num_groups(root,
sjinfo->semi_rhs_exprs,
rel->rows);
rel->rows,
NULL);
numCols = list_length(sjinfo->semi_rhs_exprs);
if (sjinfo->semi_can_btree)

View File

@ -394,6 +394,28 @@ get_sortgrouplist_exprs(List *sgClauses, List *targetList)
* functions just above, and they don't seem to deserve their own file.
*****************************************************************************/
/*
* get_sortgroupref_clause
* Find the SortGroupClause matching the given SortGroupRef index,
* and return it.
*/
SortGroupClause *
get_sortgroupref_clause(Index sortref, List *clauses)
{
ListCell *l;
foreach(l, clauses)
{
SortGroupClause *cl = (SortGroupClause *) lfirst(l);
if (cl->tleSortGroupRef == sortref)
return cl;
}
elog(ERROR, "ORDER/GROUP BY expression not found in list");
return NULL; /* keep compiler quiet */
}
/*
* extract_grouping_ops - make an array of the equality operator OIDs
* for a SortGroupClause list

View File

@ -564,6 +564,30 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
break;
}
}
else if (IsA(node, GroupingFunc))
{
if (((GroupingFunc *) node)->agglevelsup != 0)
elog(ERROR, "Upper-level GROUPING found where not expected");
switch (context->aggbehavior)
{
case PVC_REJECT_AGGREGATES:
elog(ERROR, "GROUPING found where not expected");
break;
case PVC_INCLUDE_AGGREGATES:
context->varlist = lappend(context->varlist, node);
/* we do NOT descend into the contained expression */
return false;
case PVC_RECURSE_AGGREGATES:
/*
* we do NOT descend into the contained expression,
* even if the caller asked for it, because we never
* actually evaluate it - the result is driven entirely
* off the associated GROUP BY clause, so we never need
* to extract the actual Vars here.
*/
return false;
}
}
else if (IsA(node, PlaceHolderVar))
{
if (((PlaceHolderVar *) node)->phlevelsup != 0)

View File

@ -1060,6 +1060,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->groupClause = transformGroupClause(pstate,
stmt->groupClause,
&qry->groupingSets,
&qry->targetList,
qry->sortClause,
EXPR_KIND_GROUP_BY,
@ -1106,7 +1107,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
parseCheckAggregates(pstate, qry);
foreach(l, stmt->lockingClause)
@ -1566,7 +1567,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasSubLinks = pstate->p_hasSubLinks;
qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
qry->hasAggs = pstate->p_hasAggs;
if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
parseCheckAggregates(pstate, qry);
foreach(l, lockingClause)

View File

@ -371,6 +371,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
relation_expr_list dostmt_opt_list
transform_element_list transform_type_list
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@ -438,7 +442,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> ExclusionConstraintList ExclusionConstraintElem
%type <list> func_arg_list
%type <node> func_arg_expr
%type <list> row type_list array_expr_list
%type <list> row explicit_row implicit_row type_list array_expr_list
%type <node> case_expr case_arg when_clause case_default
%type <list> when_clause_list
%type <ival> sub_type
@ -568,7 +572,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
COMMITTED CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT
CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
CROSS CSV CURRENT_P
CROSS CSV CUBE CURRENT_P
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
@ -583,7 +587,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P
GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING
HANDLER HAVING HEADER_P HOLD HOUR_P
@ -617,12 +621,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROW ROWS RULE
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P
@ -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
* postfix-operator problems.
*
* To support CUBE and ROLLUP in GROUP BY without reserving them, we give them
* an explicit priority lower than '(', so that a rule with CUBE '(' will shift
* rather than reducing a conflicting rule that takes CUBE as a function name.
* Using the same precedence as IDENT seems right for the reasons given above.
*
* The frame_bound productions UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING
* are even messier: since UNBOUNDED is an unreserved keyword (per spec!),
* there is no principled way to distinguish these from the productions
@ -692,7 +701,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* blame any funny behavior of UNBOUNDED on the SQL standard, though.
*/
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
%nonassoc IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING
%nonassoc IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING CUBE ROLLUP
%left Op OPERATOR /* multi-character ops and user-defined operators */
%left '+' '-'
%left '*' '/' '%'
@ -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_P BY expr_list { $$ = $3; }
GROUP_P BY group_by_list { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
group_by_list:
group_by_item { $$ = list_make1($1); }
| group_by_list ',' group_by_item { $$ = lappend($1,$3); }
;
group_by_item:
a_expr { $$ = $1; }
| empty_grouping_set { $$ = $1; }
| cube_clause { $$ = $1; }
| rollup_clause { $$ = $1; }
| grouping_sets_clause { $$ = $1; }
;
empty_grouping_set:
'(' ')'
{
$$ = (Node *) makeGroupingSet(GROUPING_SET_EMPTY, NIL, @1);
}
;
/*
* These hacks rely on setting precedence of CUBE and ROLLUP below that of '(',
* so that they shift in these rules rather than reducing the conflicting
* unreserved_keyword rule.
*/
rollup_clause:
ROLLUP '(' expr_list ')'
{
$$ = (Node *) makeGroupingSet(GROUPING_SET_ROLLUP, $3, @1);
}
;
cube_clause:
CUBE '(' expr_list ')'
{
$$ = (Node *) makeGroupingSet(GROUPING_SET_CUBE, $3, @1);
}
;
grouping_sets_clause:
GROUPING SETS '(' group_by_list ')'
{
$$ = (Node *) makeGroupingSet(GROUPING_SET_SETS, $4, @1);
}
;
having_clause:
HAVING a_expr { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
@ -11953,15 +12029,33 @@ c_expr: columnref { $$ = $1; }
n->location = @1;
$$ = (Node *)n;
}
| row
| explicit_row
{
RowExpr *r = makeNode(RowExpr);
r->args = $1;
r->row_typeid = InvalidOid; /* not analyzed yet */
r->colnames = NIL; /* to be filled in during analysis */
r->row_format = COERCE_EXPLICIT_CALL; /* abuse */
r->location = @1;
$$ = (Node *)r;
}
| implicit_row
{
RowExpr *r = makeNode(RowExpr);
r->args = $1;
r->row_typeid = InvalidOid; /* not analyzed yet */
r->colnames = NIL; /* to be filled in during analysis */
r->row_format = COERCE_IMPLICIT_CAST; /* abuse */
r->location = @1;
$$ = (Node *)r;
}
| GROUPING '(' expr_list ')'
{
GroupingFunc *g = makeNode(GroupingFunc);
g->args = $3;
g->location = @1;
$$ = (Node *)g;
}
;
func_application: func_name '(' ')'
@ -12711,6 +12805,13 @@ row: ROW '(' expr_list ')' { $$ = $3; }
| '(' expr_list ',' a_expr ')' { $$ = lappend($2, $4); }
;
explicit_row: ROW '(' expr_list ')' { $$ = $3; }
| ROW '(' ')' { $$ = NIL; }
;
implicit_row: '(' expr_list ',' a_expr ')' { $$ = lappend($2, $4); }
;
sub_type: ANY { $$ = ANY_SUBLINK; }
| SOME { $$ = ANY_SUBLINK; }
| ALL { $$ = ALL_SUBLINK; }
@ -13520,6 +13621,7 @@ unreserved_keyword:
| COPY
| COST
| CSV
| CUBE
| CURRENT_P
| CURSOR
| CYCLE
@ -13668,6 +13770,7 @@ unreserved_keyword:
| REVOKE
| ROLE
| ROLLBACK
| ROLLUP
| ROWS
| RULE
| SAVEPOINT
@ -13682,6 +13785,7 @@ unreserved_keyword:
| SERVER
| SESSION
| SET
| SETS
| SHARE
| SHOW
| SIMPLE
@ -13767,6 +13871,7 @@ col_name_keyword:
| EXTRACT
| FLOAT_P
| GREATEST
| GROUPING
| INOUT
| INT_P
| INTEGER

View File

@ -42,7 +42,9 @@ typedef struct
{
ParseState *pstate;
Query *qry;
PlannerInfo *root;
List *groupClauses;
List *groupClauseCommonVars;
bool have_non_var_grouping;
List **func_grouped_rels;
int sublevels_up;
@ -56,11 +58,18 @@ static int check_agg_arguments(ParseState *pstate,
static bool check_agg_arguments_walker(Node *node,
check_agg_arguments_context *context);
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, bool have_non_var_grouping,
List *groupClauses, List *groupClauseVars,
bool have_non_var_grouping,
List **func_grouped_rels);
static bool check_ungrouped_columns_walker(Node *node,
check_ungrouped_columns_context *context);
static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, PlannerInfo *root,
bool have_non_var_grouping);
static bool finalize_grouping_exprs_walker(Node *node,
check_ungrouped_columns_context *context);
static void check_agglevels_and_constraints(ParseState *pstate,Node *expr);
static List *expand_groupingset_node(GroupingSet *gs);
/*
* transformAggregateCall -
@ -96,10 +105,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
List *tdistinct = NIL;
AttrNumber attno = 1;
int save_next_resno;
int min_varlevel;
ListCell *lc;
const char *err;
bool errkind;
if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
{
@ -214,15 +220,97 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
agg->aggorder = torder;
agg->aggdistinct = tdistinct;
check_agglevels_and_constraints(pstate, (Node *) agg);
}
/*
* transformGroupingFunc
* Transform a GROUPING expression
*
* GROUPING() behaves very like an aggregate. Processing of levels and nesting
* is done as for aggregates. We set p_hasAggs for these expressions too.
*/
Node *
transformGroupingFunc(ParseState *pstate, GroupingFunc *p)
{
ListCell *lc;
List *args = p->args;
List *result_list = NIL;
GroupingFunc *result = makeNode(GroupingFunc);
if (list_length(args) > 31)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("GROUPING must have fewer than 32 arguments"),
parser_errposition(pstate, p->location)));
foreach(lc, args)
{
Node *current_result;
current_result = transformExpr(pstate, (Node*) lfirst(lc), pstate->p_expr_kind);
/* acceptability of expressions is checked later */
result_list = lappend(result_list, current_result);
}
result->args = result_list;
result->location = p->location;
check_agglevels_and_constraints(pstate, (Node *) result);
return (Node *) result;
}
/*
* Aggregate functions and grouping operations (which are combined in the spec
* as <set function specification>) are very similar with regard to level and
* nesting restrictions (though we allow a lot more things than the spec does).
* Centralise those restrictions here.
*/
static void
check_agglevels_and_constraints(ParseState *pstate, Node *expr)
{
List *directargs = NIL;
List *args = NIL;
Expr *filter = NULL;
int min_varlevel;
int location = -1;
Index *p_levelsup;
const char *err;
bool errkind;
bool isAgg = IsA(expr, Aggref);
if (isAgg)
{
Aggref *agg = (Aggref *) expr;
directargs = agg->aggdirectargs;
args = agg->args;
filter = agg->aggfilter;
location = agg->location;
p_levelsup = &agg->agglevelsup;
}
else
{
GroupingFunc *grp = (GroupingFunc *) expr;
args = grp->args;
location = grp->location;
p_levelsup = &grp->agglevelsup;
}
/*
* Check the arguments to compute the aggregate's level and detect
* improper nesting.
*/
min_varlevel = check_agg_arguments(pstate,
agg->aggdirectargs,
agg->args,
agg->aggfilter);
agg->agglevelsup = min_varlevel;
directargs,
args,
filter);
*p_levelsup = min_varlevel;
/* Mark the correct pstate level as having aggregates */
while (min_varlevel-- > 0)
@ -247,20 +335,32 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
Assert(false); /* can't happen */
break;
case EXPR_KIND_OTHER:
/* Accept aggregate here; caller must throw error if wanted */
/* Accept aggregate/grouping here; caller must throw error if wanted */
break;
case EXPR_KIND_JOIN_ON:
case EXPR_KIND_JOIN_USING:
err = _("aggregate functions are not allowed in JOIN conditions");
if (isAgg)
err = _("aggregate functions are not allowed in JOIN conditions");
else
err = _("grouping operations are not allowed in JOIN conditions");
break;
case EXPR_KIND_FROM_SUBSELECT:
/* Should only be possible in a LATERAL subquery */
Assert(pstate->p_lateral_active);
/* Aggregate scope rules make it worth being explicit here */
err = _("aggregate functions are not allowed in FROM clause of their own query level");
/* Aggregate/grouping scope rules make it worth being explicit here */
if (isAgg)
err = _("aggregate functions are not allowed in FROM clause of their own query level");
else
err = _("grouping operations are not allowed in FROM clause of their own query level");
break;
case EXPR_KIND_FROM_FUNCTION:
err = _("aggregate functions are not allowed in functions in FROM");
if (isAgg)
err = _("aggregate functions are not allowed in functions in FROM");
else
err = _("grouping operations are not allowed in functions in FROM");
break;
case EXPR_KIND_WHERE:
errkind = true;
@ -278,10 +378,18 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
/* okay */
break;
case EXPR_KIND_WINDOW_FRAME_RANGE:
err = _("aggregate functions are not allowed in window RANGE");
if (isAgg)
err = _("aggregate functions are not allowed in window RANGE");
else
err = _("grouping operations are not allowed in window RANGE");
break;
case EXPR_KIND_WINDOW_FRAME_ROWS:
err = _("aggregate functions are not allowed in window ROWS");
if (isAgg)
err = _("aggregate functions are not allowed in window ROWS");
else
err = _("grouping operations are not allowed in window ROWS");
break;
case EXPR_KIND_SELECT_TARGET:
/* okay */
@ -312,26 +420,55 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
break;
case EXPR_KIND_CHECK_CONSTRAINT:
case EXPR_KIND_DOMAIN_CHECK:
err = _("aggregate functions are not allowed in check constraints");
if (isAgg)
err = _("aggregate functions are not allowed in check constraints");
else
err = _("grouping operations are not allowed in check constraints");
break;
case EXPR_KIND_COLUMN_DEFAULT:
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("aggregate functions are not allowed in DEFAULT expressions");
if (isAgg)
err = _("aggregate functions are not allowed in DEFAULT expressions");
else
err = _("grouping operations are not allowed in DEFAULT expressions");
break;
case EXPR_KIND_INDEX_EXPRESSION:
err = _("aggregate functions are not allowed in index expressions");
if (isAgg)
err = _("aggregate functions are not allowed in index expressions");
else
err = _("grouping operations are not allowed in index expressions");
break;
case EXPR_KIND_INDEX_PREDICATE:
err = _("aggregate functions are not allowed in index predicates");
if (isAgg)
err = _("aggregate functions are not allowed in index predicates");
else
err = _("grouping operations are not allowed in index predicates");
break;
case EXPR_KIND_ALTER_COL_TRANSFORM:
err = _("aggregate functions are not allowed in transform expressions");
if (isAgg)
err = _("aggregate functions are not allowed in transform expressions");
else
err = _("grouping operations are not allowed in transform expressions");
break;
case EXPR_KIND_EXECUTE_PARAMETER:
err = _("aggregate functions are not allowed in EXECUTE parameters");
if (isAgg)
err = _("aggregate functions are not allowed in EXECUTE parameters");
else
err = _("grouping operations are not allowed in EXECUTE parameters");
break;
case EXPR_KIND_TRIGGER_WHEN:
err = _("aggregate functions are not allowed in trigger WHEN conditions");
if (isAgg)
err = _("aggregate functions are not allowed in trigger WHEN conditions");
else
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
/*
@ -342,18 +479,28 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
* which is sane anyway.
*/
}
if (err)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg_internal("%s", err),
parser_errposition(pstate, agg->location)));
parser_errposition(pstate, location)));
if (errkind)
{
if (isAgg)
/* translator: %s is name of a SQL construct, eg GROUP BY */
err = _("aggregate functions are not allowed in %s");
else
/* translator: %s is name of a SQL construct, eg GROUP BY */
err = _("grouping operations are not allowed in %s");
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
/* translator: %s is name of a SQL construct, eg GROUP BY */
errmsg("aggregate functions are not allowed in %s",
ParseExprKindName(pstate->p_expr_kind)),
parser_errposition(pstate, agg->location)));
errmsg_internal(err,
ParseExprKindName(pstate->p_expr_kind)),
parser_errposition(pstate, location)));
}
}
/*
@ -466,7 +613,6 @@ check_agg_arguments(ParseState *pstate,
locate_agg_of_level((Node *) directargs,
context.min_agglevel))));
}
return agglevel;
}
@ -507,6 +653,21 @@ check_agg_arguments_walker(Node *node,
/* no need to examine args of the inner aggregate */
return false;
}
if (IsA(node, GroupingFunc))
{
int agglevelsup = ((GroupingFunc *) node)->agglevelsup;
/* convert levelsup to frame of reference of original query */
agglevelsup -= context->sublevels_up;
/* ignore local aggs of subqueries */
if (agglevelsup >= 0)
{
if (context->min_agglevel < 0 ||
context->min_agglevel > agglevelsup)
context->min_agglevel = agglevelsup;
}
/* Continue and descend into subtree */
}
/* We can throw error on sight for a window function */
if (IsA(node, WindowFunc))
ereport(ERROR,
@ -527,6 +688,7 @@ check_agg_arguments_walker(Node *node,
context->sublevels_up--;
return result;
}
return expression_tree_walker(node,
check_agg_arguments_walker,
(void *) context);
@ -770,17 +932,66 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
void
parseCheckAggregates(ParseState *pstate, Query *qry)
{
List *gset_common = NIL;
List *groupClauses = NIL;
List *groupClauseCommonVars = NIL;
bool have_non_var_grouping;
List *func_grouped_rels = NIL;
ListCell *l;
bool hasJoinRTEs;
bool hasSelfRefRTEs;
PlannerInfo *root;
PlannerInfo *root = NULL;
Node *clause;
/* This should only be called if we found aggregates or grouping */
Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual);
Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual || qry->groupingSets);
/*
* If we have grouping sets, expand them and find the intersection of all
* sets.
*/
if (qry->groupingSets)
{
/*
* The limit of 4096 is arbitrary and exists simply to avoid resource
* issues from pathological constructs.
*/
List *gsets = expand_grouping_sets(qry->groupingSets, 4096);
if (!gsets)
ereport(ERROR,
(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
errmsg("too many grouping sets present (max 4096)"),
parser_errposition(pstate,
qry->groupClause
? exprLocation((Node *) qry->groupClause)
: exprLocation((Node *) qry->groupingSets))));
/*
* The intersection will often be empty, so help things along by
* seeding the intersect with the smallest set.
*/
gset_common = linitial(gsets);
if (gset_common)
{
for_each_cell(l, lnext(list_head(gsets)))
{
gset_common = list_intersection_int(gset_common, lfirst(l));
if (!gset_common)
break;
}
}
/*
* If there was only one grouping set in the expansion, AND if the
* groupClause is non-empty (meaning that the grouping set is not empty
* either), then we can ditch the grouping set and pretend we just had
* a normal GROUP BY.
*/
if (list_length(gsets) == 1 && qry->groupClause)
qry->groupingSets = NIL;
}
/*
* Scan the range table to see if there are JOIN or self-reference CTE
@ -800,15 +1011,19 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
/*
* Build a list of the acceptable GROUP BY expressions for use by
* check_ungrouped_columns().
*
* We get the TLE, not just the expr, because GROUPING wants to know
* the sortgroupref.
*/
foreach(l, qry->groupClause)
{
SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
Node *expr;
TargetEntry *expr;
expr = get_sortgroupclause_expr(grpcl, qry->targetList);
expr = get_sortgroupclause_tle(grpcl, qry->targetList);
if (expr == NULL)
continue; /* probably cannot happen */
groupClauses = lcons(expr, groupClauses);
}
@ -830,21 +1045,28 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
groupClauses = (List *) flatten_join_alias_vars(root,
(Node *) groupClauses);
}
else
root = NULL; /* keep compiler quiet */
/*
* Detect whether any of the grouping expressions aren't simple Vars; if
* they're all Vars then we don't have to work so hard in the recursive
* scans. (Note we have to flatten aliases before this.)
*
* Track Vars that are included in all grouping sets separately in
* groupClauseCommonVars, since these are the only ones we can use to check
* for functional dependencies.
*/
have_non_var_grouping = false;
foreach(l, groupClauses)
{
if (!IsA((Node *) lfirst(l), Var))
TargetEntry *tle = lfirst(l);
if (!IsA(tle->expr, Var))
{
have_non_var_grouping = true;
break;
}
else if (!qry->groupingSets ||
list_member_int(gset_common, tle->ressortgroupref))
{
groupClauseCommonVars = lappend(groupClauseCommonVars, tle->expr);
}
}
@ -855,19 +1077,30 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
* this will also find ungrouped variables that came from ORDER BY and
* WINDOW clauses. For that matter, it's also going to examine the
* grouping expressions themselves --- but they'll all pass the test ...
*
* We also finalize GROUPING expressions, but for that we need to traverse
* the original (unflattened) clause in order to modify nodes.
*/
clause = (Node *) qry->targetList;
finalize_grouping_exprs(clause, pstate, qry,
groupClauses, root,
have_non_var_grouping);
if (hasJoinRTEs)
clause = flatten_join_alias_vars(root, clause);
check_ungrouped_columns(clause, pstate, qry,
groupClauses, have_non_var_grouping,
groupClauses, groupClauseCommonVars,
have_non_var_grouping,
&func_grouped_rels);
clause = (Node *) qry->havingQual;
finalize_grouping_exprs(clause, pstate, qry,
groupClauses, root,
have_non_var_grouping);
if (hasJoinRTEs)
clause = flatten_join_alias_vars(root, clause);
check_ungrouped_columns(clause, pstate, qry,
groupClauses, have_non_var_grouping,
groupClauses, groupClauseCommonVars,
have_non_var_grouping,
&func_grouped_rels);
/*
@ -904,14 +1137,17 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
*/
static void
check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, bool have_non_var_grouping,
List *groupClauses, List *groupClauseCommonVars,
bool have_non_var_grouping,
List **func_grouped_rels)
{
check_ungrouped_columns_context context;
context.pstate = pstate;
context.qry = qry;
context.root = NULL;
context.groupClauses = groupClauses;
context.groupClauseCommonVars = groupClauseCommonVars;
context.have_non_var_grouping = have_non_var_grouping;
context.func_grouped_rels = func_grouped_rels;
context.sublevels_up = 0;
@ -965,6 +1201,16 @@ check_ungrouped_columns_walker(Node *node,
return false;
}
if (IsA(node, GroupingFunc))
{
GroupingFunc *grp = (GroupingFunc *) node;
/* handled GroupingFunc separately, no need to recheck at this level */
if ((int) grp->agglevelsup >= context->sublevels_up)
return false;
}
/*
* If we have any GROUP BY items that are not simple Vars, check to see if
* subexpression as a whole matches any GROUP BY item. We need to do this
@ -976,7 +1222,9 @@ check_ungrouped_columns_walker(Node *node,
{
foreach(gl, context->groupClauses)
{
if (equal(node, lfirst(gl)))
TargetEntry *tle = lfirst(gl);
if (equal(node, tle->expr))
return false; /* acceptable, do not descend more */
}
}
@ -1003,7 +1251,7 @@ check_ungrouped_columns_walker(Node *node,
{
foreach(gl, context->groupClauses)
{
Var *gvar = (Var *) lfirst(gl);
Var *gvar = (Var *) ((TargetEntry *) lfirst(gl))->expr;
if (IsA(gvar, Var) &&
gvar->varno == var->varno &&
@ -1040,7 +1288,7 @@ check_ungrouped_columns_walker(Node *node,
if (check_functional_grouping(rte->relid,
var->varno,
0,
context->groupClauses,
context->groupClauseCommonVars,
&context->qry->constraintDeps))
{
*context->func_grouped_rels =
@ -1084,6 +1332,395 @@ check_ungrouped_columns_walker(Node *node,
(void *) context);
}
/*
* finalize_grouping_exprs -
* Scan the given expression tree for GROUPING() and related calls,
* and validate and process their arguments.
*
* This is split out from check_ungrouped_columns above because it needs
* to modify the nodes (which it does in-place, not via a mutator) while
* check_ungrouped_columns may see only a copy of the original thanks to
* flattening of join alias vars. So here, we flatten each individual
* GROUPING argument as we see it before comparing it.
*/
static void
finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, PlannerInfo *root,
bool have_non_var_grouping)
{
check_ungrouped_columns_context context;
context.pstate = pstate;
context.qry = qry;
context.root = root;
context.groupClauses = groupClauses;
context.groupClauseCommonVars = NIL;
context.have_non_var_grouping = have_non_var_grouping;
context.func_grouped_rels = NULL;
context.sublevels_up = 0;
context.in_agg_direct_args = false;
finalize_grouping_exprs_walker(node, &context);
}
static bool
finalize_grouping_exprs_walker(Node *node,
check_ungrouped_columns_context *context)
{
ListCell *gl;
if (node == NULL)
return false;
if (IsA(node, Const) ||
IsA(node, Param))
return false; /* constants are always acceptable */
if (IsA(node, Aggref))
{
Aggref *agg = (Aggref *) node;
if ((int) agg->agglevelsup == context->sublevels_up)
{
/*
* If we find an aggregate call of the original level, do not
* recurse into its normal arguments, ORDER BY arguments, or
* filter; GROUPING exprs of this level are not allowed there. But
* check direct arguments as though they weren't in an aggregate.
*/
bool result;
Assert(!context->in_agg_direct_args);
context->in_agg_direct_args = true;
result = finalize_grouping_exprs_walker((Node *) agg->aggdirectargs,
context);
context->in_agg_direct_args = false;
return result;
}
/*
* We can skip recursing into aggregates of higher levels altogether,
* since they could not possibly contain exprs of concern to us (see
* transformAggregateCall). We do need to look at aggregates of lower
* levels, however.
*/
if ((int) agg->agglevelsup > context->sublevels_up)
return false;
}
if (IsA(node, GroupingFunc))
{
GroupingFunc *grp = (GroupingFunc *) node;
/*
* We only need to check GroupingFunc nodes at the exact level to which
* they belong, since they cannot mix levels in arguments.
*/
if ((int) grp->agglevelsup == context->sublevels_up)
{
ListCell *lc;
List *ref_list = NIL;
foreach(lc, grp->args)
{
Node *expr = lfirst(lc);
Index ref = 0;
if (context->root)
expr = flatten_join_alias_vars(context->root, expr);
/*
* Each expression must match a grouping entry at the current
* query level. Unlike the general expression case, we don't
* allow functional dependencies or outer references.
*/
if (IsA(expr, Var))
{
Var *var = (Var *) expr;
if (var->varlevelsup == context->sublevels_up)
{
foreach(gl, context->groupClauses)
{
TargetEntry *tle = lfirst(gl);
Var *gvar = (Var *) tle->expr;
if (IsA(gvar, Var) &&
gvar->varno == var->varno &&
gvar->varattno == var->varattno &&
gvar->varlevelsup == 0)
{
ref = tle->ressortgroupref;
break;
}
}
}
}
else if (context->have_non_var_grouping &&
context->sublevels_up == 0)
{
foreach(gl, context->groupClauses)
{
TargetEntry *tle = lfirst(gl);
if (equal(expr, tle->expr))
{
ref = tle->ressortgroupref;
break;
}
}
}
if (ref == 0)
ereport(ERROR,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("arguments to GROUPING must be grouping expressions of the associated query level"),
parser_errposition(context->pstate,
exprLocation(expr))));
ref_list = lappend_int(ref_list, ref);
}
grp->refs = ref_list;
}
if ((int) grp->agglevelsup > context->sublevels_up)
return false;
}
if (IsA(node, Query))
{
/* Recurse into subselects */
bool result;
context->sublevels_up++;
result = query_tree_walker((Query *) node,
finalize_grouping_exprs_walker,
(void *) context,
0);
context->sublevels_up--;
return result;
}
return expression_tree_walker(node, finalize_grouping_exprs_walker,
(void *) context);
}
/*
* Given a GroupingSet node, expand it and return a list of lists.
*
* For EMPTY nodes, return a list of one empty list.
*
* For SIMPLE nodes, return a list of one list, which is the node content.
*
* For CUBE and ROLLUP nodes, return a list of the expansions.
*
* For SET nodes, recursively expand contained CUBE and ROLLUP.
*/
static List*
expand_groupingset_node(GroupingSet *gs)
{
List * result = NIL;
switch (gs->kind)
{
case GROUPING_SET_EMPTY:
result = list_make1(NIL);
break;
case GROUPING_SET_SIMPLE:
result = list_make1(gs->content);
break;
case GROUPING_SET_ROLLUP:
{
List *rollup_val = gs->content;
ListCell *lc;
int curgroup_size = list_length(gs->content);
while (curgroup_size > 0)
{
List *current_result = NIL;
int i = curgroup_size;
foreach(lc, rollup_val)
{
GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
Assert(gs_current->kind == GROUPING_SET_SIMPLE);
current_result
= list_concat(current_result,
list_copy(gs_current->content));
/* If we are done with making the current group, break */
if (--i == 0)
break;
}
result = lappend(result, current_result);
--curgroup_size;
}
result = lappend(result, NIL);
}
break;
case GROUPING_SET_CUBE:
{
List *cube_list = gs->content;
int number_bits = list_length(cube_list);
uint32 num_sets;
uint32 i;
/* parser should cap this much lower */
Assert(number_bits < 31);
num_sets = (1U << number_bits);
for (i = 0; i < num_sets; i++)
{
List *current_result = NIL;
ListCell *lc;
uint32 mask = 1U;
foreach(lc, cube_list)
{
GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
Assert(gs_current->kind == GROUPING_SET_SIMPLE);
if (mask & i)
{
current_result
= list_concat(current_result,
list_copy(gs_current->content));
}
mask <<= 1;
}
result = lappend(result, current_result);
}
}
break;
case GROUPING_SET_SETS:
{
ListCell *lc;
foreach(lc, gs->content)
{
List *current_result = expand_groupingset_node(lfirst(lc));
result = list_concat(result, current_result);
}
}
break;
}
return result;
}
static int
cmp_list_len_asc(const void *a, const void *b)
{
int la = list_length(*(List*const*)a);
int lb = list_length(*(List*const*)b);
return (la > lb) ? 1 : (la == lb) ? 0 : -1;
}
/*
* Expand a groupingSets clause to a flat list of grouping sets.
* The returned list is sorted by length, shortest sets first.
*
* This is mainly for the planner, but we use it here too to do
* some consistency checks.
*/
List *
expand_grouping_sets(List *groupingSets, int limit)
{
List *expanded_groups = NIL;
List *result = NIL;
double numsets = 1;
ListCell *lc;
if (groupingSets == NIL)
return NIL;
foreach(lc, groupingSets)
{
List *current_result = NIL;
GroupingSet *gs = lfirst(lc);
current_result = expand_groupingset_node(gs);
Assert(current_result != NIL);
numsets *= list_length(current_result);
if (limit >= 0 && numsets > limit)
return NIL;
expanded_groups = lappend(expanded_groups, current_result);
}
/*
* Do cartesian product between sublists of expanded_groups.
* While at it, remove any duplicate elements from individual
* grouping sets (we must NOT change the number of sets though)
*/
foreach(lc, (List *) linitial(expanded_groups))
{
result = lappend(result, list_union_int(NIL, (List *) lfirst(lc)));
}
for_each_cell(lc, lnext(list_head(expanded_groups)))
{
List *p = lfirst(lc);
List *new_result = NIL;
ListCell *lc2;
foreach(lc2, result)
{
List *q = lfirst(lc2);
ListCell *lc3;
foreach(lc3, p)
{
new_result = lappend(new_result,
list_union_int(q, (List *) lfirst(lc3)));
}
}
result = new_result;
}
if (list_length(result) > 1)
{
int result_len = list_length(result);
List **buf = palloc(sizeof(List*) * result_len);
List **ptr = buf;
foreach(lc, result)
{
*ptr++ = lfirst(lc);
}
qsort(buf, result_len, sizeof(List*), cmp_list_len_asc);
result = NIL;
ptr = buf;
while (result_len-- > 0)
result = lappend(result, *ptr++);
pfree(buf);
}
return result;
}
/*
* get_aggregate_argtypes
* Identify the specific datatypes passed to an aggregate call.

View File

@ -15,6 +15,8 @@
#include "postgres.h"
#include "miscadmin.h"
#include "access/heapam.h"
#include "catalog/catalog.h"
#include "access/htup_details.h"
@ -43,7 +45,6 @@
#include "utils/rel.h"
#include "utils/syscache.h"
/* Convenience macro for the most common makeNamespaceItem() case */
#define makeDefaultNSItem(rte) makeNamespaceItem(rte, true, true, false, true)
@ -1725,40 +1726,181 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
return target_result;
}
/*
* transformGroupClause -
* transform a GROUP BY clause
/*-------------------------------------------------------------------------
* Flatten out parenthesized sublists in grouping lists, and some cases
* of nested grouping sets.
*
* GROUP BY items will be added to the targetlist (as resjunk columns)
* if not already present, so the targetlist must be passed by reference.
* Inside a grouping set (ROLLUP, CUBE, or GROUPING SETS), we expect the
* content to be nested no more than 2 deep: i.e. ROLLUP((a,b),(c,d)) is
* ok, but ROLLUP((a,(b,c)),d) is flattened to ((a,b,c),d), which we then
* normalize to ((a,b,c),(d)).
*
* This is also used for window PARTITION BY clauses (which act almost the
* same, but are always interpreted per SQL99 rules).
* CUBE or ROLLUP can be nested inside GROUPING SETS (but not the reverse),
* and we leave that alone if we find it. But if we see GROUPING SETS inside
* GROUPING SETS, we can flatten and normalize as follows:
* GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g))
* becomes
* GROUPING SETS ((a), (b,c), (c,d), (e), (f,g))
*
* This is per the spec's syntax transformations, but these are the only such
* transformations we do in parse analysis, so that queries retain the
* originally specified grouping set syntax for CUBE and ROLLUP as much as
* possible when deparsed. (Full expansion of the result into a list of
* grouping sets is left to the planner.)
*
* When we're done, the resulting list should contain only these possible
* elements:
* - an expression
* - a CUBE or ROLLUP with a list of expressions nested 2 deep
* - a GROUPING SET containing any of:
* - expression lists
* - empty grouping sets
* - CUBE or ROLLUP nodes with lists nested 2 deep
* The return is a new list, but doesn't deep-copy the old nodes except for
* GroupingSet nodes.
*
* As a side effect, flag whether the list has any GroupingSet nodes.
*-------------------------------------------------------------------------
*/
List *
transformGroupClause(ParseState *pstate, List *grouplist,
List **targetlist, List *sortClause,
ParseExprKind exprKind, bool useSQL99)
static Node *
flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets)
{
List *result = NIL;
ListCell *gl;
/* just in case of pathological input */
check_stack_depth();
foreach(gl, grouplist)
if (expr == (Node *) NIL)
return (Node *) NIL;
switch (expr->type)
{
Node *gexpr = (Node *) lfirst(gl);
TargetEntry *tle;
bool found = false;
case T_RowExpr:
{
RowExpr *r = (RowExpr *) expr;
if (r->row_format == COERCE_IMPLICIT_CAST)
return flatten_grouping_sets((Node *) r->args,
false, NULL);
}
break;
case T_GroupingSet:
{
GroupingSet *gset = (GroupingSet *) expr;
ListCell *l2;
List *result_set = NIL;
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, gexpr,
targetlist, exprKind);
else
tle = findTargetlistEntrySQL92(pstate, gexpr,
targetlist, exprKind);
if (hasGroupingSets)
*hasGroupingSets = true;
/* Eliminate duplicates (GROUP BY x, x) */
if (targetIsInSortList(tle, InvalidOid, result))
continue;
/*
* at the top level, we skip over all empty grouping sets; the
* caller can supply the canonical GROUP BY () if nothing is left.
*/
if (toplevel && gset->kind == GROUPING_SET_EMPTY)
return (Node *) NIL;
foreach(l2, gset->content)
{
Node *n2 = flatten_grouping_sets(lfirst(l2), false, NULL);
result_set = lappend(result_set, n2);
}
/*
* At top level, keep the grouping set node; but if we're in a nested
* grouping set, then we need to concat the flattened result into the
* outer list if it's simply nested.
*/
if (toplevel || (gset->kind != GROUPING_SET_SETS))
{
return (Node *) makeGroupingSet(gset->kind, result_set, gset->location);
}
else
return (Node *) result_set;
}
case T_List:
{
List *result = NIL;
ListCell *l;
foreach(l, (List *)expr)
{
Node *n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets);
if (n != (Node *) NIL)
{
if (IsA(n,List))
result = list_concat(result, (List *) n);
else
result = lappend(result, n);
}
}
return (Node *) result;
}
default:
break;
}
return expr;
}
/*
* Transform a single expression within a GROUP BY clause or grouping set.
*
* The expression is added to the targetlist if not already present, and to the
* flatresult list (which will become the groupClause) if not already present
* there. The sortClause is consulted for operator and sort order hints.
*
* Returns the ressortgroupref of the expression.
*
* flatresult reference to flat list of SortGroupClause nodes
* seen_local bitmapset of sortgrouprefs already seen at the local level
* pstate ParseState
* gexpr node to transform
* targetlist reference to TargetEntry list
* sortClause ORDER BY clause (SortGroupClause nodes)
* exprKind expression kind
* useSQL99 SQL99 rather than SQL92 syntax
* toplevel false if within any grouping set
*/
static Index
transformGroupClauseExpr(List **flatresult, Bitmapset *seen_local,
ParseState *pstate, Node *gexpr,
List **targetlist, List *sortClause,
ParseExprKind exprKind, bool useSQL99, bool toplevel)
{
TargetEntry *tle;
bool found = false;
if (useSQL99)
tle = findTargetlistEntrySQL99(pstate, gexpr,
targetlist, exprKind);
else
tle = findTargetlistEntrySQL92(pstate, gexpr,
targetlist, exprKind);
if (tle->ressortgroupref > 0)
{
ListCell *sl;
/*
* Eliminate duplicates (GROUP BY x, x) but only at local level.
* (Duplicates in grouping sets can affect the number of returned
* rows, so can't be dropped indiscriminately.)
*
* Since we don't care about anything except the sortgroupref,
* we can use a bitmapset rather than scanning lists.
*/
if (bms_is_member(tle->ressortgroupref,seen_local))
return 0;
/*
* If we're already in the flat clause list, we don't need
* to consider adding ourselves again.
*/
found = targetIsInSortList(tle, InvalidOid, *flatresult);
if (found)
return tle->ressortgroupref;
/*
* If the GROUP BY tlist entry also appears in ORDER BY, copy operator
@ -1770,35 +1912,308 @@ transformGroupClause(ParseState *pstate, List *grouplist,
* sort step, and it allows the user to choose the equality semantics
* used by GROUP BY, should she be working with a datatype that has
* more than one equality operator.
*
* If we're in a grouping set, though, we force our requested ordering
* to be NULLS LAST, because if we have any hope of using a sorted agg
* for the job, we're going to be tacking on generated NULL values
* after the corresponding groups. If the user demands nulls first,
* another sort step is going to be inevitable, but that's the
* planner's problem.
*/
if (tle->ressortgroupref > 0)
foreach(sl, sortClause)
{
ListCell *sl;
SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
foreach(sl, sortClause)
if (sc->tleSortGroupRef == tle->ressortgroupref)
{
SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
if (sc->tleSortGroupRef == tle->ressortgroupref)
{
result = lappend(result, copyObject(sc));
found = true;
break;
}
SortGroupClause *grpc = copyObject(sc);
if (!toplevel)
grpc->nulls_first = false;
*flatresult = lappend(*flatresult, grpc);
found = true;
break;
}
}
/*
* If no match in ORDER BY, just add it to the result using default
* sort/group semantics.
*/
if (!found)
result = addTargetToGroupList(pstate, tle,
result, *targetlist,
exprLocation(gexpr),
true);
}
/*
* If no match in ORDER BY, just add it to the result using default
* sort/group semantics.
*/
if (!found)
*flatresult = addTargetToGroupList(pstate, tle,
*flatresult, *targetlist,
exprLocation(gexpr),
true);
/*
* _something_ must have assigned us a sortgroupref by now...
*/
return tle->ressortgroupref;
}
/*
* Transform a list of expressions within a GROUP BY clause or grouping set.
*
* The list of expressions belongs to a single clause within which duplicates
* can be safely eliminated.
*
* Returns an integer list of ressortgroupref values.
*
* flatresult reference to flat list of SortGroupClause nodes
* pstate ParseState
* list nodes to transform
* targetlist reference to TargetEntry list
* sortClause ORDER BY clause (SortGroupClause nodes)
* exprKind expression kind
* useSQL99 SQL99 rather than SQL92 syntax
* toplevel false if within any grouping set
*/
static List *
transformGroupClauseList(List **flatresult,
ParseState *pstate, List *list,
List **targetlist, List *sortClause,
ParseExprKind exprKind, bool useSQL99, bool toplevel)
{
Bitmapset *seen_local = NULL;
List *result = NIL;
ListCell *gl;
foreach(gl, list)
{
Node *gexpr = (Node *) lfirst(gl);
Index ref = transformGroupClauseExpr(flatresult,
seen_local,
pstate,
gexpr,
targetlist,
sortClause,
exprKind,
useSQL99,
toplevel);
if (ref > 0)
{
seen_local = bms_add_member(seen_local, ref);
result = lappend_int(result, ref);
}
}
return result;
}
/*
* Transform a grouping set and (recursively) its content.
*
* The grouping set might be a GROUPING SETS node with other grouping sets
* inside it, but SETS within SETS have already been flattened out before
* reaching here.
*
* Returns the transformed node, which now contains SIMPLE nodes with lists
* of ressortgrouprefs rather than expressions.
*
* flatresult reference to flat list of SortGroupClause nodes
* pstate ParseState
* gset grouping set to transform
* targetlist reference to TargetEntry list
* sortClause ORDER BY clause (SortGroupClause nodes)
* exprKind expression kind
* useSQL99 SQL99 rather than SQL92 syntax
* toplevel false if within any grouping set
*/
static Node *
transformGroupingSet(List **flatresult,
ParseState *pstate, GroupingSet *gset,
List **targetlist, List *sortClause,
ParseExprKind exprKind, bool useSQL99, bool toplevel)
{
ListCell *gl;
List *content = NIL;
Assert(toplevel || gset->kind != GROUPING_SET_SETS);
foreach(gl, gset->content)
{
Node *n = lfirst(gl);
if (IsA(n, List))
{
List *l = transformGroupClauseList(flatresult,
pstate, (List *) n,
targetlist, sortClause,
exprKind, useSQL99, false);
content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
l,
exprLocation(n)));
}
else if (IsA(n, GroupingSet))
{
GroupingSet *gset2 = (GroupingSet *) lfirst(gl);
content = lappend(content, transformGroupingSet(flatresult,
pstate, gset2,
targetlist, sortClause,
exprKind, useSQL99, false));
}
else
{
Index ref = transformGroupClauseExpr(flatresult,
NULL,
pstate,
n,
targetlist,
sortClause,
exprKind,
useSQL99,
false);
content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
list_make1_int(ref),
exprLocation(n)));
}
}
/* Arbitrarily cap the size of CUBE, which has exponential growth */
if (gset->kind == GROUPING_SET_CUBE)
{
if (list_length(content) > 12)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("CUBE is limited to 12 elements"),
parser_errposition(pstate, gset->location)));
}
return (Node *) makeGroupingSet(gset->kind, content, gset->location);
}
/*
* transformGroupClause -
* transform a GROUP BY clause
*
* GROUP BY items will be added to the targetlist (as resjunk columns)
* if not already present, so the targetlist must be passed by reference.
*
* This is also used for window PARTITION BY clauses (which act almost the
* same, but are always interpreted per SQL99 rules).
*
* Grouping sets make this a lot more complex than it was. Our goal here is
* twofold: we make a flat list of SortGroupClause nodes referencing each
* distinct expression used for grouping, with those expressions added to the
* targetlist if needed. At the same time, we build the groupingSets tree,
* which stores only ressortgrouprefs as integer lists inside GroupingSet nodes
* (possibly nested, but limited in depth: a GROUPING_SET_SETS node can contain
* nested SIMPLE, CUBE or ROLLUP nodes, but not more sets - we flatten that
* out; while CUBE and ROLLUP can contain only SIMPLE nodes).
*
* We skip much of the hard work if there are no grouping sets.
*
* One subtlety is that the groupClause list can end up empty while the
* groupingSets list is not; this happens if there are only empty grouping
* sets, or an explicit GROUP BY (). This has the same effect as specifying
* aggregates or a HAVING clause with no GROUP BY; the output is one row per
* grouping set even if the input is empty.
*
* Returns the transformed (flat) groupClause.
*
* pstate ParseState
* grouplist clause to transform
* groupingSets reference to list to contain the grouping set tree
* targetlist reference to TargetEntry list
* sortClause ORDER BY clause (SortGroupClause nodes)
* exprKind expression kind
* useSQL99 SQL99 rather than SQL92 syntax
*/
List *
transformGroupClause(ParseState *pstate, List *grouplist, List **groupingSets,
List **targetlist, List *sortClause,
ParseExprKind exprKind, bool useSQL99)
{
List *result = NIL;
List *flat_grouplist;
List *gsets = NIL;
ListCell *gl;
bool hasGroupingSets = false;
Bitmapset *seen_local = NULL;
/*
* Recursively flatten implicit RowExprs. (Technically this is only
* needed for GROUP BY, per the syntax rules for grouping sets, but
* we do it anyway.)
*/
flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist,
true,
&hasGroupingSets);
/*
* If the list is now empty, but hasGroupingSets is true, it's because
* we elided redundant empty grouping sets. Restore a single empty
* grouping set to leave a canonical form: GROUP BY ()
*/
if (flat_grouplist == NIL && hasGroupingSets)
{
flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY,
NIL,
exprLocation((Node *) grouplist)));
}
foreach(gl, flat_grouplist)
{
Node *gexpr = (Node *) lfirst(gl);
if (IsA(gexpr, GroupingSet))
{
GroupingSet *gset = (GroupingSet *) gexpr;
switch (gset->kind)
{
case GROUPING_SET_EMPTY:
gsets = lappend(gsets, gset);
break;
case GROUPING_SET_SIMPLE:
/* can't happen */
Assert(false);
break;
case GROUPING_SET_SETS:
case GROUPING_SET_CUBE:
case GROUPING_SET_ROLLUP:
gsets = lappend(gsets,
transformGroupingSet(&result,
pstate, gset,
targetlist, sortClause,
exprKind, useSQL99, true));
break;
}
}
else
{
Index ref = transformGroupClauseExpr(&result, seen_local,
pstate, gexpr,
targetlist, sortClause,
exprKind, useSQL99, true);
if (ref > 0)
{
seen_local = bms_add_member(seen_local, ref);
if (hasGroupingSets)
gsets = lappend(gsets,
makeGroupingSet(GROUPING_SET_SIMPLE,
list_make1_int(ref),
exprLocation(gexpr)));
}
}
}
/* parser should prevent this */
Assert(gsets == NIL || groupingSets != NULL);
if (groupingSets)
*groupingSets = gsets;
return result;
}
@ -1903,6 +2318,7 @@ transformWindowDefinitions(ParseState *pstate,
true /* force SQL99 rules */ );
partitionClause = transformGroupClause(pstate,
windef->partitionClause,
NULL,
targetlist,
orderClause,
EXPR_KIND_WINDOW_PARTITION,

View File

@ -32,6 +32,7 @@
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parse_type.h"
#include "parser/parse_agg.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/xml.h"
@ -269,6 +270,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformMultiAssignRef(pstate, (MultiAssignRef *) expr);
break;
case T_GroupingFunc:
result = transformGroupingFunc(pstate, (GroupingFunc *) expr);
break;
case T_NamedArgExpr:
{
NamedArgExpr *na = (NamedArgExpr *) expr;

View File

@ -1681,6 +1681,10 @@ FigureColnameInternal(Node *node, char **name)
break;
case T_CollateClause:
return FigureColnameInternal(((CollateClause *) node)->arg, name);
case T_GroupingFunc:
/* make GROUPING() act like a regular function */
*name = "grouping";
return 2;
case T_SubLink:
switch (((SubLink *) node)->subLinkType)
{

View File

@ -2158,7 +2158,7 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
if (viewquery->distinctClause != NIL)
return gettext_noop("Views containing DISTINCT are not automatically updatable.");
if (viewquery->groupClause != NIL)
if (viewquery->groupClause != NIL || viewquery->groupingSets)
return gettext_noop("Views containing GROUP BY are not automatically updatable.");
if (viewquery->havingQual != NULL)

View File

@ -92,6 +92,12 @@ contain_aggs_of_level_walker(Node *node,
return true; /* abort the tree traversal and return true */
/* else fall through to examine argument */
}
if (IsA(node, GroupingFunc))
{
if (((GroupingFunc *) node)->agglevelsup == context->sublevels_up)
return true;
/* else fall through to examine argument */
}
if (IsA(node, Query))
{
/* Recurse into subselects */
@ -157,6 +163,15 @@ locate_agg_of_level_walker(Node *node,
}
/* else fall through to examine argument */
}
if (IsA(node, GroupingFunc))
{
if (((GroupingFunc *) node)->agglevelsup == context->sublevels_up &&
((GroupingFunc *) node)->location >= 0)
{
context->agg_location = ((GroupingFunc *) node)->location;
return true; /* abort the tree traversal and return true */
}
}
if (IsA(node, Query))
{
/* Recurse into subselects */
@ -712,6 +727,14 @@ IncrementVarSublevelsUp_walker(Node *node,
agg->agglevelsup += context->delta_sublevels_up;
/* fall through to recurse into argument */
}
if (IsA(node, GroupingFunc))
{
GroupingFunc *grp = (GroupingFunc *) node;
if (grp->agglevelsup >= context->min_sublevels_up)
grp->agglevelsup += context->delta_sublevels_up;
/* fall through to recurse into argument */
}
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;

View File

@ -44,6 +44,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/tlist.h"
#include "parser/keywords.h"
#include "parser/parse_node.h"
#include "parser/parse_agg.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
@ -105,6 +106,8 @@ typedef struct
int wrapColumn; /* max line length, or -1 for no limit */
int indentLevel; /* current indent level for prettyprint */
bool varprefix; /* TRUE to print prefixes on Vars */
ParseExprKind special_exprkind; /* set only for exprkinds needing */
/* special handling */
} deparse_context;
/*
@ -369,9 +372,11 @@ static void get_target_list(List *targetList, deparse_context *context,
static void get_setop_query(Node *setOp, Query *query,
deparse_context *context,
TupleDesc resultDesc);
static Node *get_rule_sortgroupclause(SortGroupClause *srt, List *tlist,
static Node *get_rule_sortgroupclause(Index ref, List *tlist,
bool force_colno,
deparse_context *context);
static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
bool omit_parens, deparse_context *context);
static void get_rule_orderby(List *orderList, List *targetList,
bool force_colno, deparse_context *context);
static void get_rule_windowclause(Query *query, deparse_context *context);
@ -419,8 +424,9 @@ static void printSubscripts(ArrayRef *aref, deparse_context *context);
static char *get_relation_name(Oid relid);
static char *generate_relation_name(Oid relid, List *namespaces);
static char *generate_function_name(Oid funcid, int nargs,
List *argnames, Oid *argtypes,
bool has_variadic, bool *use_variadic_p);
List *argnames, Oid *argtypes,
bool has_variadic, bool *use_variadic_p,
ParseExprKind special_exprkind);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
@ -878,6 +884,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
context.special_exprkind = EXPR_KIND_NONE;
get_rule_expr(qual, &context, false);
@ -887,7 +894,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
generate_function_name(trigrec->tgfoid, 0,
NIL, NULL,
false, NULL));
false, NULL, EXPR_KIND_NONE));
if (trigrec->tgnargs > 0)
{
@ -2502,6 +2509,7 @@ deparse_expression_pretty(Node *expr, List *dpcontext,
context.prettyFlags = prettyFlags;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = startIndent;
context.special_exprkind = EXPR_KIND_NONE;
get_rule_expr(expr, &context, showimplicit);
@ -4112,6 +4120,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
context.prettyFlags = prettyFlags;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
context.special_exprkind = EXPR_KIND_NONE;
set_deparse_for_query(&dpns, query, NIL);
@ -4307,6 +4316,7 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
context.prettyFlags = prettyFlags;
context.wrapColumn = wrapColumn;
context.indentLevel = startIndent;
context.special_exprkind = EXPR_KIND_NONE;
set_deparse_for_query(&dpns, query, parentnamespace);
@ -4677,7 +4687,7 @@ get_basic_select_query(Query *query, deparse_context *context,
SortGroupClause *srt = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(srt, query->targetList,
get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
false, context);
sep = ", ";
}
@ -4702,20 +4712,43 @@ get_basic_select_query(Query *query, deparse_context *context,
}
/* Add the GROUP BY clause if given */
if (query->groupClause != NULL)
if (query->groupClause != NULL || query->groupingSets != NULL)
{
ParseExprKind save_exprkind;
appendContextKeyword(context, " GROUP BY ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
sep = "";
foreach(l, query->groupClause)
{
SortGroupClause *grp = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(grp, query->targetList,
false, context);
sep = ", ";
save_exprkind = context->special_exprkind;
context->special_exprkind = EXPR_KIND_GROUP_BY;
if (query->groupingSets == NIL)
{
sep = "";
foreach(l, query->groupClause)
{
SortGroupClause *grp = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList,
false, context);
sep = ", ";
}
}
else
{
sep = "";
foreach(l, query->groupingSets)
{
GroupingSet *grp = lfirst(l);
appendStringInfoString(buf, sep);
get_rule_groupingset(grp, query->targetList, true, context);
sep = ", ";
}
}
context->special_exprkind = save_exprkind;
}
/* Add the HAVING clause if given */
@ -4782,7 +4815,7 @@ get_target_list(List *targetList, deparse_context *context,
* different from a whole-row Var). We need to call get_variable
* directly so that we can tell it to do the right thing.
*/
if (tle->expr && IsA(tle->expr, Var))
if (tle->expr && (IsA(tle->expr, Var)))
{
attname = get_variable((Var *) tle->expr, 0, true, context);
}
@ -5001,23 +5034,24 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
* Also returns the expression tree, so caller need not find it again.
*/
static Node *
get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
deparse_context *context)
{
StringInfo buf = context->buf;
TargetEntry *tle;
Node *expr;
tle = get_sortgroupclause_tle(srt, tlist);
tle = get_sortgroupref_tle(ref, tlist);
expr = (Node *) tle->expr;
/*
* Use column-number form if requested by caller. Otherwise, if
* expression is a constant, force it to be dumped with an explicit cast
* as decoration --- this is because a simple integer constant is
* ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
* dump it without any decoration. Otherwise, just dump the expression
* normally.
* Use column-number form if requested by caller. Otherwise, if expression
* is a constant, force it to be dumped with an explicit cast as decoration
* --- this is because a simple integer constant is ambiguous (and will be
* misinterpreted by findTargetlistEntry()) if we dump it without any
* decoration. If it's anything more complex than a simple Var, then force
* extra parens around it, to ensure it can't be misinterpreted as a cube()
* or rollup() construct.
*/
if (force_colno)
{
@ -5026,12 +5060,91 @@ get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
}
else if (expr && IsA(expr, Const))
get_const_expr((Const *) expr, context, 1);
else
else if (!expr || IsA(expr, Var))
get_rule_expr(expr, context, true);
else
{
/*
* We must force parens for function-like expressions even if
* PRETTY_PAREN is off, since those are the ones in danger of
* misparsing. For other expressions we need to force them
* only if PRETTY_PAREN is on, since otherwise the expression
* will output them itself. (We can't skip the parens.)
*/
bool need_paren = (PRETTY_PAREN(context)
|| IsA(expr, FuncExpr)
|| IsA(expr, Aggref)
|| IsA(expr, WindowFunc));
if (need_paren)
appendStringInfoString(context->buf, "(");
get_rule_expr(expr, context, true);
if (need_paren)
appendStringInfoString(context->buf, ")");
}
return expr;
}
/*
* Display a GroupingSet
*/
static void
get_rule_groupingset(GroupingSet *gset, List *targetlist,
bool omit_parens, deparse_context *context)
{
ListCell *l;
StringInfo buf = context->buf;
bool omit_child_parens = true;
char *sep = "";
switch (gset->kind)
{
case GROUPING_SET_EMPTY:
appendStringInfoString(buf, "()");
return;
case GROUPING_SET_SIMPLE:
{
if (!omit_parens || list_length(gset->content) != 1)
appendStringInfoString(buf, "(");
foreach(l, gset->content)
{
Index ref = lfirst_int(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(ref, targetlist,
false, context);
sep = ", ";
}
if (!omit_parens || list_length(gset->content) != 1)
appendStringInfoString(buf, ")");
}
return;
case GROUPING_SET_ROLLUP:
appendStringInfoString(buf, "ROLLUP(");
break;
case GROUPING_SET_CUBE:
appendStringInfoString(buf, "CUBE(");
break;
case GROUPING_SET_SETS:
appendStringInfoString(buf, "GROUPING SETS (");
omit_child_parens = false;
break;
}
foreach(l, gset->content)
{
appendStringInfoString(buf, sep);
get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context);
sep = ", ";
}
appendStringInfoString(buf, ")");
}
/*
* Display an ORDER BY list.
*/
@ -5052,7 +5165,7 @@ get_rule_orderby(List *orderList, List *targetList,
TypeCacheEntry *typentry;
appendStringInfoString(buf, sep);
sortexpr = get_rule_sortgroupclause(srt, targetList,
sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
force_colno, context);
sortcoltype = exprType(sortexpr);
/* See whether operator is default < or > for datatype */
@ -5152,7 +5265,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
SortGroupClause *grp = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
get_rule_sortgroupclause(grp, targetList,
get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
false, context);
sep = ", ";
}
@ -6879,6 +6992,16 @@ get_rule_expr(Node *node, deparse_context *context,
get_agg_expr((Aggref *) node, context);
break;
case T_GroupingFunc:
{
GroupingFunc *gexpr = (GroupingFunc *) node;
appendStringInfoString(buf, "GROUPING(");
get_rule_expr((Node *) gexpr->args, context, true);
appendStringInfoChar(buf, ')');
}
break;
case T_WindowFunc:
get_windowfunc_expr((WindowFunc *) node, context);
break;
@ -7917,7 +8040,8 @@ get_func_expr(FuncExpr *expr, deparse_context *context,
generate_function_name(funcoid, nargs,
argnames, argtypes,
expr->funcvariadic,
&use_variadic));
&use_variadic,
context->special_exprkind));
nargs = 0;
foreach(l, expr->args)
{
@ -7949,7 +8073,8 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
generate_function_name(aggref->aggfnoid, nargs,
NIL, argtypes,
aggref->aggvariadic,
&use_variadic),
&use_variadic,
context->special_exprkind),
(aggref->aggdistinct != NIL) ? "DISTINCT " : "");
if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
@ -8039,7 +8164,8 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context)
appendStringInfo(buf, "%s(",
generate_function_name(wfunc->winfnoid, nargs,
argnames, argtypes,
false, NULL));
false, NULL,
context->special_exprkind));
/* winstar can be set only in zero-argument aggregates */
if (wfunc->winstar)
appendStringInfoChar(buf, '*');
@ -9291,7 +9417,8 @@ generate_relation_name(Oid relid, List *namespaces)
*/
static char *
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
bool has_variadic, bool *use_variadic_p)
bool has_variadic, bool *use_variadic_p,
ParseExprKind special_exprkind)
{
char *result;
HeapTuple proctup;
@ -9306,6 +9433,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
int p_nvargs;
Oid p_vatype;
Oid *p_true_typeids;
bool force_qualify = false;
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
@ -9313,6 +9441,16 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
procform = (Form_pg_proc) GETSTRUCT(proctup);
proname = NameStr(procform->proname);
/*
* Due to parser hacks to avoid needing to reserve CUBE, we need to force
* qualification in some special cases.
*/
if (special_exprkind == EXPR_KIND_GROUP_BY)
{
if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
force_qualify = true;
}
/*
* Determine whether VARIADIC should be printed. We must do this first
* since it affects the lookup rules in func_get_detail().
@ -9344,14 +9482,23 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
/*
* The idea here is to schema-qualify only if the parser would fail to
* resolve the correct function given the unqualified func name with the
* specified argtypes and VARIADIC flag.
* specified argtypes and VARIADIC flag. But if we already decided to
* force qualification, then we can skip the lookup and pretend we didn't
* find it.
*/
p_result = func_get_detail(list_make1(makeString(proname)),
NIL, argnames, nargs, argtypes,
!use_variadic, true,
&p_funcid, &p_rettype,
&p_retset, &p_nvargs, &p_vatype,
&p_true_typeids, NULL);
if (!force_qualify)
p_result = func_get_detail(list_make1(makeString(proname)),
NIL, argnames, nargs, argtypes,
!use_variadic, true,
&p_funcid, &p_rettype,
&p_retset, &p_nvargs, &p_vatype,
&p_true_typeids, NULL);
else
{
p_result = FUNCDETAIL_NOTFOUND;
p_funcid = InvalidOid;
}
if ((p_result == FUNCDETAIL_NORMAL ||
p_result == FUNCDETAIL_AGGREGATE ||
p_result == FUNCDETAIL_WINDOWFUNC) &&

View File

@ -3158,6 +3158,8 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
* groupExprs - list of expressions being grouped by
* input_rows - number of rows estimated to arrive at the group/unique
* filter step
* pgset - NULL, or a List** pointing to a grouping set to filter the
* groupExprs against
*
* Given the lack of any cross-correlation statistics in the system, it's
* impossible to do anything really trustworthy with GROUP BY conditions
@ -3205,11 +3207,13 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
* but we don't have the info to do better).
*/
double
estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
List **pgset)
{
List *varinfos = NIL;
double numdistinct;
ListCell *l;
int i;
/*
* We don't ever want to return an estimate of zero groups, as that tends
@ -3224,7 +3228,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
* for normal cases with GROUP BY or DISTINCT, but it is possible for
* corner cases with set operations.)
*/
if (groupExprs == NIL)
if (groupExprs == NIL || (pgset && list_length(*pgset) < 1))
return 1.0;
/*
@ -3236,6 +3240,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
*/
numdistinct = 1.0;
i = 0;
foreach(l, groupExprs)
{
Node *groupexpr = (Node *) lfirst(l);
@ -3243,6 +3248,10 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
List *varshere;
ListCell *l2;
/* is expression in this grouping set? */
if (pgset && !list_member_int(*pgset, i++))
continue;
/* Short-circuit for expressions returning boolean */
if (exprType(groupexpr) == BOOLOID)
{

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201505152
#define CATALOG_VERSION_NO 201505153
#endif

View File

@ -83,6 +83,8 @@ extern void ExplainSeparatePlans(ExplainState *es);
extern void ExplainPropertyList(const char *qlabel, List *data,
ExplainState *es);
extern void ExplainPropertyListNested(const char *qlabel, List *data,
ExplainState *es);
extern void ExplainPropertyText(const char *qlabel, const char *value,
ExplainState *es);
extern void ExplainPropertyInteger(const char *qlabel, int value,

View File

@ -0,0 +1,44 @@
/*
* 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 */

View File

@ -23,6 +23,7 @@
#include "utils/reltrigger.h"
#include "utils/sortsupport.h"
#include "utils/tuplestore.h"
#include "utils/tuplesort.h"
/* ----------------
@ -614,6 +615,22 @@ typedef struct AggrefExprState
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
/* ----------------
* GroupingFuncExprState node
*
* The list of column numbers refers to the input tuples of the Agg node to
* which the GroupingFunc belongs, and may contain 0 for references to columns
* that are only present in grouping sets processed by different Agg nodes (and
* which are therefore always considered "grouping" here).
* ----------------
*/
typedef struct GroupingFuncExprState
{
ExprState xprstate;
struct AggState *aggstate;
List *clauses; /* integer list of column numbers */
} GroupingFuncExprState;
/* ----------------
* WindowFuncExprState node
* ----------------
@ -1796,19 +1813,33 @@ typedef struct GroupState
/* these structs are private in nodeAgg.c: */
typedef struct AggStatePerAggData *AggStatePerAgg;
typedef struct AggStatePerGroupData *AggStatePerGroup;
typedef struct AggStatePerPhaseData *AggStatePerPhase;
typedef struct AggState
{
ScanState ss; /* its first field is NodeTag */
List *aggs; /* all Aggref nodes in targetlist & quals */
int numaggs; /* length of list (could be zero!) */
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 */
AggStatePerAgg peragg; /* per-Aggref information */
MemoryContext aggcontext; /* memory context for long-lived data */
ExprContext **aggcontexts; /* econtexts for long-lived data (per GS) */
ExprContext *tmpcontext; /* econtext for input expressions */
AggStatePerAgg curperagg; /* identifies currently active aggregate */
bool input_done; /* indicates end of input */
bool agg_done; /* indicates completion of Agg scan */
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: */
AggStatePerGroup pergroup; /* per-Aggref-per-group working state */
HeapTuple grp_firstTuple; /* copy of first tuple of current group */

View File

@ -81,4 +81,6 @@ extern DefElem *makeDefElem(char *name, Node *arg);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction);
extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
#endif /* MAKEFUNC_H */

View File

@ -136,6 +136,7 @@ typedef enum NodeTag
T_Const,
T_Param,
T_Aggref,
T_GroupingFunc,
T_WindowFunc,
T_ArrayRef,
T_FuncExpr,
@ -188,6 +189,7 @@ typedef enum NodeTag
T_GenericExprState,
T_WholeRowVarExprState,
T_AggrefExprState,
T_GroupingFuncExprState,
T_WindowFuncExprState,
T_ArrayRefExprState,
T_FuncExprState,
@ -406,6 +408,7 @@ typedef enum NodeTag
T_RangeTblFunction,
T_WithCheckOption,
T_SortGroupClause,
T_GroupingSet,
T_WindowClause,
T_PrivGrantee,
T_FuncWithArgs,

View File

@ -138,6 +138,8 @@ typedef struct Query
List *groupClause; /* a list of SortGroupClause's */
List *groupingSets; /* a list of GroupingSet's if present */
Node *havingQual; /* qualifications applied to groups */
List *windowClause; /* a list of WindowClause's */
@ -1001,6 +1003,73 @@ typedef struct SortGroupClause
bool hashable; /* can eqop be implemented by hashing? */
} SortGroupClause;
/*
* GroupingSet -
* representation of CUBE, ROLLUP and GROUPING SETS clauses
*
* In a Query with grouping sets, the groupClause contains a flat list of
* SortGroupClause nodes for each distinct expression used. The actual
* structure of the GROUP BY clause is given by the groupingSets tree.
*
* In the raw parser output, GroupingSet nodes (of all types except SIMPLE
* which is not used) are potentially mixed in with the expressions in the
* groupClause of the SelectStmt. (An expression can't contain a GroupingSet,
* but a list may mix GroupingSet and expression nodes.) At this stage, the
* content of each node is a list of expressions, some of which may be RowExprs
* which represent sublists rather than actual row constructors, and nested
* GroupingSet nodes where legal in the grammar. The structure directly
* reflects the query syntax.
*
* In parse analysis, the transformed expressions are used to build the tlist
* and groupClause list (of SortGroupClause nodes), and the groupingSets tree
* is eventually reduced to a fixed format:
*
* EMPTY nodes represent (), and obviously have no content
*
* SIMPLE nodes represent a list of one or more expressions to be treated as an
* atom by the enclosing structure; the content is an integer list of
* ressortgroupref values (see SortGroupClause)
*
* CUBE and ROLLUP nodes contain a list of one or more SIMPLE nodes.
*
* SETS nodes contain a list of EMPTY, SIMPLE, CUBE or ROLLUP nodes, but after
* parse analysis they cannot contain more SETS nodes; enough of the syntactic
* transforms of the spec have been applied that we no longer have arbitrarily
* deep nesting (though we still preserve the use of cube/rollup).
*
* Note that if the groupingSets tree contains no SIMPLE nodes (only EMPTY
* nodes at the leaves), then the groupClause will be empty, but this is still
* an aggregation query (similar to using aggs or HAVING without GROUP BY).
*
* As an example, the following clause:
*
* GROUP BY GROUPING SETS ((a,b), CUBE(c,(d,e)))
*
* looks like this after raw parsing:
*
* SETS( RowExpr(a,b) , CUBE( c, RowExpr(d,e) ) )
*
* and parse analysis converts it to:
*
* SETS( SIMPLE(1,2), CUBE( SIMPLE(3), SIMPLE(4,5) ) )
*/
typedef enum
{
GROUPING_SET_EMPTY,
GROUPING_SET_SIMPLE,
GROUPING_SET_ROLLUP,
GROUPING_SET_CUBE,
GROUPING_SET_SETS
} GroupingSetKind;
typedef struct GroupingSet
{
NodeTag type;
GroupingSetKind kind;
List *content;
int location;
} GroupingSet;
/*
* WindowClause -
* transformed representation of WINDOW and OVER clauses

View File

@ -229,8 +229,9 @@ extern List *list_union_int(const List *list1, const List *list2);
extern List *list_union_oid(const List *list1, const List *list2);
extern List *list_intersection(const List *list1, const List *list2);
extern List *list_intersection_int(const List *list1, const List *list2);
/* currently, there's no need for list_intersection_int etc */
/* currently, there's no need for list_intersection_ptr etc */
extern List *list_difference(const List *list1, const List *list2);
extern List *list_difference_ptr(const List *list1, const List *list2);

View File

@ -712,6 +712,8 @@ typedef struct Agg
AttrNumber *grpColIdx; /* their indexes in the target list */
Oid *grpOperators; /* equality operators to compare with */
long numGroups; /* estimated number of groups in input */
List *groupingSets; /* grouping sets to use */
List *chain; /* chained Agg/Sort nodes */
} Agg;
/* ----------------

View File

@ -271,6 +271,41 @@ typedef struct Aggref
int location; /* token location, or -1 if unknown */
} Aggref;
/*
* GroupingFunc
*
* A GroupingFunc is a GROUPING(...) expression, which behaves in many ways
* like an aggregate function (e.g. it "belongs" to a specific query level,
* which might not be the one immediately containing it), but also differs in
* an important respect: it never evaluates its arguments, they merely
* designate expressions from the GROUP BY clause of the query level to which
* it belongs.
*
* The spec defines the evaluation of GROUPING() purely by syntactic
* replacement, but we make it a real expression for optimization purposes so
* that one Agg node can handle multiple grouping sets at once. Evaluating the
* result only needs the column positions to check against the grouping set
* being projected. However, for EXPLAIN to produce meaningful output, we have
* to keep the original expressions around, since expression deparse does not
* give us any feasible way to get at the GROUP BY clause.
*
* Also, we treat two GroupingFunc nodes as equal if they have equal arguments
* lists and agglevelsup, without comparing the refs and cols annotations.
*
* In raw parse output we have only the args list; parse analysis fills in the
* refs list, and the planner fills in the cols list.
*/
typedef struct GroupingFunc
{
Expr xpr;
List *args; /* arguments, not evaluated but kept for
* benefit of EXPLAIN etc. */
List *refs; /* ressortgrouprefs of arguments */
List *cols; /* actual column positions set by planner */
Index agglevelsup; /* same as Aggref.agglevelsup */
int location; /* token location */
} GroupingFunc;
/*
* WindowFunc
*/

View File

@ -260,6 +260,9 @@ typedef struct PlannerInfo
/* optional private data for join_search_hook, e.g., GEQO */
void *join_search_private;
/* for GroupingFunc fixup in setrefs */
AttrNumber *grouping_map;
} PlannerInfo;

View File

@ -59,6 +59,7 @@ extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls,
extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
List *groupingSets,
long numGroups,
Plan *lefttree);
extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,

View File

@ -43,6 +43,9 @@ extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
extern List *get_sortgrouplist_exprs(List *sgClauses,
List *targetList);
extern SortGroupClause *get_sortgroupref_clause(Index sortref,
List *clauses);
extern Oid *extract_grouping_ops(List *groupClause);
extern AttrNumber *extract_grouping_cols(List *groupClause, List *tlist);
extern bool grouping_is_sortable(List *groupClause);

View File

@ -99,6 +99,7 @@ PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD)
PG_KEYWORD("cube", CUBE, UNRESERVED_KEYWORD)
PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD)
PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD)
@ -174,6 +175,7 @@ PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD)
PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD)
PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
@ -325,6 +327,7 @@ PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD)
PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD)
PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD)
PG_KEYWORD("rollup", ROLLUP, UNRESERVED_KEYWORD)
PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
@ -343,6 +346,7 @@ PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD)
PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD)
PG_KEYWORD("set", SET, UNRESERVED_KEYWORD)
PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD)
PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD)
PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD)
PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD)
PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD)

View File

@ -18,11 +18,16 @@
extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
List *args, List *aggorder,
bool agg_distinct);
extern Node *transformGroupingFunc(ParseState *pstate, GroupingFunc *g);
extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
WindowDef *windef);
extern void parseCheckAggregates(ParseState *pstate, Query *qry);
extern List *expand_grouping_sets(List *groupingSets, int limit);
extern int get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes);
extern Oid resolve_aggregate_transtype(Oid aggfuncid,

View File

@ -27,6 +27,7 @@ extern Node *transformWhereClause(ParseState *pstate, Node *clause,
extern Node *transformLimitClause(ParseState *pstate, Node *clause,
ParseExprKind exprKind, const char *constructName);
extern List *transformGroupClause(ParseState *pstate, List *grouplist,
List **groupingSets,
List **targetlist, List *sortClause,
ParseExprKind exprKind, bool useSQL99);
extern List *transformSortClause(ParseState *pstate, List *orderlist,

View File

@ -185,7 +185,7 @@ extern void mergejoinscansel(PlannerInfo *root, Node *clause,
Selectivity *rightstart, Selectivity *rightend);
extern double estimate_num_groups(PlannerInfo *root, List *groupExprs,
double input_rows);
double input_rows, List **pgset);
extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
double nbuckets);

View File

@ -0,0 +1,590 @@
--
-- grouping sets
--
-- test data sources
create temp view gstest1(a,b,v)
as values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),
(2,3,15),
(3,3,16),(3,4,17),
(4,1,18),(4,1,19);
create temp table gstest2 (a integer, b integer, c integer, d integer,
e integer, f integer, g integer, h integer);
copy gstest2 from stdin;
create temp table gstest3 (a integer, b integer, c integer, d integer);
copy gstest3 from stdin;
alter table gstest3 add primary key (a);
create temp table gstest_empty (a integer, b integer, v integer);
create function gstest_data(v integer, out a integer, out b integer)
returns setof record
as $f$
begin
return query select v, i from generate_series(1,3) i;
end;
$f$ language plpgsql;
-- basic functionality
-- simple rollup with multiple plain aggregates, with and without ordering
-- (and with ordering differing from grouping)
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b);
a | b | grouping | sum | count | max
---+---+----------+-----+-------+-----
1 | 1 | 0 | 21 | 2 | 11
1 | 2 | 0 | 25 | 2 | 13
1 | 3 | 0 | 14 | 1 | 14
1 | | 1 | 60 | 5 | 14
2 | 3 | 0 | 15 | 1 | 15
2 | | 1 | 15 | 1 | 15
3 | 3 | 0 | 16 | 1 | 16
3 | 4 | 0 | 17 | 1 | 17
3 | | 1 | 33 | 2 | 17
4 | 1 | 0 | 37 | 2 | 19
4 | | 1 | 37 | 2 | 19
| | 3 | 145 | 10 | 19
(12 rows)
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b) order by a,b;
a | b | grouping | sum | count | max
---+---+----------+-----+-------+-----
1 | 1 | 0 | 21 | 2 | 11
1 | 2 | 0 | 25 | 2 | 13
1 | 3 | 0 | 14 | 1 | 14
1 | | 1 | 60 | 5 | 14
2 | 3 | 0 | 15 | 1 | 15
2 | | 1 | 15 | 1 | 15
3 | 3 | 0 | 16 | 1 | 16
3 | 4 | 0 | 17 | 1 | 17
3 | | 1 | 33 | 2 | 17
4 | 1 | 0 | 37 | 2 | 19
4 | | 1 | 37 | 2 | 19
| | 3 | 145 | 10 | 19
(12 rows)
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b) order by b desc, a;
a | b | grouping | sum | count | max
---+---+----------+-----+-------+-----
1 | | 1 | 60 | 5 | 14
2 | | 1 | 15 | 1 | 15
3 | | 1 | 33 | 2 | 17
4 | | 1 | 37 | 2 | 19
| | 3 | 145 | 10 | 19
3 | 4 | 0 | 17 | 1 | 17
1 | 3 | 0 | 14 | 1 | 14
2 | 3 | 0 | 15 | 1 | 15
3 | 3 | 0 | 16 | 1 | 16
1 | 2 | 0 | 25 | 2 | 13
1 | 1 | 0 | 21 | 2 | 11
4 | 1 | 0 | 37 | 2 | 19
(12 rows)
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b) order by coalesce(a,0)+coalesce(b,0);
a | b | grouping | sum | count | max
---+---+----------+-----+-------+-----
| | 3 | 145 | 10 | 19
1 | | 1 | 60 | 5 | 14
1 | 1 | 0 | 21 | 2 | 11
2 | | 1 | 15 | 1 | 15
3 | | 1 | 33 | 2 | 17
1 | 2 | 0 | 25 | 2 | 13
1 | 3 | 0 | 14 | 1 | 14
4 | | 1 | 37 | 2 | 19
4 | 1 | 0 | 37 | 2 | 19
2 | 3 | 0 | 15 | 1 | 15
3 | 3 | 0 | 16 | 1 | 16
3 | 4 | 0 | 17 | 1 | 17
(12 rows)
-- various types of ordered aggs
select a, b, grouping(a,b),
array_agg(v order by v),
string_agg(v::text, ':' order by v desc),
percentile_disc(0.5) within group (order by v),
rank(1,2,12) within group (order by a,b,v)
from gstest1 group by rollup (a,b) order by a,b;
a | b | grouping | array_agg | string_agg | percentile_disc | rank
---+---+----------+---------------------------------+-------------------------------+-----------------+------
1 | 1 | 0 | {10,11} | 11:10 | 10 | 3
1 | 2 | 0 | {12,13} | 13:12 | 12 | 1
1 | 3 | 0 | {14} | 14 | 14 | 1
1 | | 1 | {10,11,12,13,14} | 14:13:12:11:10 | 12 | 3
2 | 3 | 0 | {15} | 15 | 15 | 1
2 | | 1 | {15} | 15 | 15 | 1
3 | 3 | 0 | {16} | 16 | 16 | 1
3 | 4 | 0 | {17} | 17 | 17 | 1
3 | | 1 | {16,17} | 17:16 | 16 | 1
4 | 1 | 0 | {18,19} | 19:18 | 18 | 1
4 | | 1 | {18,19} | 19:18 | 18 | 1
| | 3 | {10,11,12,13,14,15,16,17,18,19} | 19:18:17:16:15:14:13:12:11:10 | 14 | 3
(12 rows)
-- test usage of grouped columns in direct args of aggs
select grouping(a), a, array_agg(b),
rank(a) within group (order by b nulls first),
rank(a) within group (order by b nulls last)
from (values (1,1),(1,4),(1,5),(3,1),(3,2)) v(a,b)
group by rollup (a) order by a;
grouping | a | array_agg | rank | rank
----------+---+-------------+------+------
0 | 1 | {1,4,5} | 1 | 1
0 | 3 | {1,2} | 3 | 3
1 | | {1,4,5,1,2} | 1 | 6
(3 rows)
-- nesting with window functions
select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
from gstest2 group by rollup (a,b) order by rsum, a, b;
a | b | sum | rsum
---+---+-----+------
1 | 1 | 8 | 8
1 | 2 | 2 | 10
1 | | 10 | 20
2 | 2 | 2 | 22
2 | | 2 | 24
| | 12 | 36
(6 rows)
-- empty input: first is 0 rows, second 1, third 3 etc.
select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a);
a | b | sum | count
---+---+-----+-------
(0 rows)
select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),());
a | b | sum | count
---+---+-----+-------
| | | 0
(1 row)
select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),());
a | b | sum | count
---+---+-----+-------
| | | 0
| | | 0
| | | 0
(3 rows)
select sum(v), count(*) from gstest_empty group by grouping sets ((),(),());
sum | count
-----+-------
| 0
| 0
| 0
(3 rows)
-- empty input with joins tests some important code paths
select t1.a, t2.b, sum(t1.v), count(*) from gstest_empty t1, gstest_empty t2
group by grouping sets ((t1.a,t2.b),());
a | b | sum | count
---+---+-----+-------
| | | 0
(1 row)
-- simple joins, var resolution, GROUPING on join vars
select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
from gstest1 t1, gstest2 t2
group by grouping sets ((t1.a, t2.b), ());
a | b | grouping | sum | max
---+---+----------+------+-----
1 | 1 | 0 | 420 | 1
1 | 2 | 0 | 120 | 2
2 | 1 | 0 | 105 | 1
2 | 2 | 0 | 30 | 2
3 | 1 | 0 | 231 | 1
3 | 2 | 0 | 66 | 2
4 | 1 | 0 | 259 | 1
4 | 2 | 0 | 74 | 2
| | 3 | 1305 | 2
(9 rows)
select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
from gstest1 t1 join gstest2 t2 on (t1.a=t2.a)
group by grouping sets ((t1.a, t2.b), ());
a | b | grouping | sum | max
---+---+----------+-----+-----
1 | 1 | 0 | 420 | 1
1 | 2 | 0 | 60 | 1
2 | 2 | 0 | 15 | 2
| | 3 | 495 | 2
(4 rows)
select a, b, grouping(a, b), sum(t1.v), max(t2.c)
from gstest1 t1 join gstest2 t2 using (a,b)
group by grouping sets ((a, b), ());
a | b | grouping | sum | max
---+---+----------+-----+-----
1 | 1 | 0 | 147 | 2
1 | 2 | 0 | 25 | 2
| | 3 | 172 | 2
(3 rows)
-- check that functionally dependent cols are not nulled
select a, d, grouping(a,b,c)
from gstest3
group by grouping sets ((a,b), (a,c));
a | d | grouping
---+---+----------
1 | 1 | 1
2 | 2 | 1
1 | 1 | 2
2 | 2 | 2
(4 rows)
-- simple rescan tests
select a, b, sum(v.x)
from (values (1),(2)) v(x), gstest_data(v.x)
group by rollup (a,b);
a | b | sum
---+---+-----
1 | 1 | 1
1 | 2 | 1
1 | 3 | 1
1 | | 3
2 | 1 | 2
2 | 2 | 2
2 | 3 | 2
2 | | 6
| | 9
(9 rows)
select *
from (values (1),(2)) v(x),
lateral (select a, b, sum(v.x) from gstest_data(v.x) group by rollup (a,b)) s;
ERROR: aggregate functions are not allowed in FROM clause of their own query level
LINE 3: lateral (select a, b, sum(v.x) from gstest_data(v.x) ...
^
-- min max optimisation should still work with GROUP BY ()
explain (costs off)
select min(unique1) from tenk1 GROUP BY ();
QUERY PLAN
------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Only Scan using tenk1_unique1 on tenk1
Index Cond: (unique1 IS NOT NULL)
(5 rows)
-- Views with GROUPING SET queries
CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
from gstest2 group by rollup ((a,b,c),(c,d));
NOTICE: view "gstest_view" will be a temporary view
select pg_get_viewdef('gstest_view'::regclass, true);
pg_get_viewdef
-------------------------------------------------------------------------------
SELECT gstest2.a, +
gstest2.b, +
GROUPING(gstest2.a, gstest2.b) AS "grouping", +
sum(gstest2.c) AS sum, +
count(*) AS count, +
max(gstest2.c) AS max +
FROM gstest2 +
GROUP BY ROLLUP((gstest2.a, gstest2.b, gstest2.c), (gstest2.c, gstest2.d));
(1 row)
-- Nested queries with 3 or more levels of nesting
select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
grouping
----------
0
0
0
(3 rows)
select(select (select grouping(e,f) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
grouping
----------
0
1
3
(3 rows)
select(select (select grouping(c) from (values (1)) v2(c) GROUP BY c) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
grouping
----------
0
0
0
(3 rows)
-- Combinations of operations
select a, b, c, d from gstest2 group by rollup(a,b),grouping sets(c,d);
a | b | c | d
---+---+---+---
1 | 1 | 1 |
1 | | 1 |
| | 1 |
1 | 1 | 2 |
1 | 2 | 2 |
1 | | 2 |
2 | 2 | 2 |
2 | | 2 |
| | 2 |
1 | 1 | | 1
1 | | | 1
| | | 1
1 | 1 | | 2
1 | 2 | | 2
1 | | | 2
2 | 2 | | 2
2 | | | 2
| | | 2
(18 rows)
select a, b from (values (1,2),(2,3)) v(a,b) group by a,b, grouping sets(a);
a | b
---+---
1 | 2
2 | 3
(2 rows)
-- Tests for chained aggregates
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2));
a | b | grouping | sum | count | max
---+---+----------+-----+-------+-----
1 | 1 | 0 | 21 | 2 | 11
1 | 2 | 0 | 25 | 2 | 13
1 | 3 | 0 | 14 | 1 | 14
2 | 3 | 0 | 15 | 1 | 15
3 | 3 | 0 | 16 | 1 | 16
3 | 4 | 0 | 17 | 1 | 17
4 | 1 | 0 | 37 | 2 | 19
| | 3 | 21 | 2 | 11
| | 3 | 25 | 2 | 13
| | 3 | 14 | 1 | 14
| | 3 | 15 | 1 | 15
| | 3 | 16 | 1 | 16
| | 3 | 17 | 1 | 17
| | 3 | 37 | 2 | 19
| | 3 | 21 | 2 | 11
| | 3 | 25 | 2 | 13
| | 3 | 14 | 1 | 14
| | 3 | 15 | 1 | 15
| | 3 | 16 | 1 | 16
| | 3 | 17 | 1 | 17
| | 3 | 37 | 2 | 19
(21 rows)
select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP((e+1),(f+1));
grouping
----------
0
0
0
(3 rows)
select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY CUBE((e+1),(f+1)) ORDER BY (e+1),(f+1);
grouping
----------
0
0
0
0
(4 rows)
select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
from gstest2 group by cube (a,b) order by rsum, a, b;
a | b | sum | rsum
---+---+-----+------
1 | 1 | 8 | 8
1 | 2 | 2 | 10
1 | | 10 | 20
2 | 2 | 2 | 22
2 | | 2 | 24
| 1 | 8 | 32
| 2 | 4 | 36
| | 12 | 48
(8 rows)
select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b);
a | b | sum
---+---+-----
1 | 1 | 21
1 | 2 | 25
1 | 3 | 14
1 | | 60
2 | 3 | 15
2 | | 15
3 | 3 | 16
3 | 4 | 17
3 | | 33
4 | 1 | 37
4 | | 37
| | 145
(12 rows)
select a, b, sum(v.x)
from (values (1),(2)) v(x), gstest_data(v.x)
group by cube (a,b) order by a,b;
a | b | sum
---+---+-----
1 | 1 | 1
1 | 2 | 1
1 | 3 | 1
1 | | 3
2 | 1 | 2
2 | 2 | 2
2 | 3 | 2
2 | | 6
| 1 | 3
| 2 | 3
| 3 | 3
| | 9
(12 rows)
-- Agg level check. This query should error out.
select (select grouping(a,b) from gstest2) from gstest2 group by a,b;
ERROR: arguments to GROUPING must be grouping expressions of the associated query level
LINE 1: select (select grouping(a,b) from gstest2) from gstest2 grou...
^
--Nested queries
select a, b, sum(c), count(*) from gstest2 group by grouping sets (rollup(a,b),a);
a | b | sum | count
---+---+-----+-------
1 | 1 | 8 | 7
1 | 2 | 2 | 1
1 | | 10 | 8
1 | | 10 | 8
2 | 2 | 2 | 1
2 | | 2 | 1
2 | | 2 | 1
| | 12 | 9
(8 rows)
-- HAVING queries
select ten, sum(distinct four) from onek a
group by grouping sets((ten,four),(ten))
having exists (select 1 from onek b where sum(distinct a.four) = b.four);
ten | sum
-----+-----
0 | 0
0 | 2
0 | 2
1 | 1
1 | 3
2 | 0
2 | 2
2 | 2
3 | 1
3 | 3
4 | 0
4 | 2
4 | 2
5 | 1
5 | 3
6 | 0
6 | 2
6 | 2
7 | 1
7 | 3
8 | 0
8 | 2
8 | 2
9 | 1
9 | 3
(25 rows)
-- FILTER queries
select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
group by rollup(ten);
ten | sum
-----+-----
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
|
(11 rows)
-- More rescan tests
select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten;
a | a | four | ten | count
---+---+------+-----+-------
1 | 1 | 0 | 0 | 50
1 | 1 | 0 | 2 | 50
1 | 1 | 0 | 4 | 50
1 | 1 | 0 | 6 | 50
1 | 1 | 0 | 8 | 50
1 | 1 | 0 | | 250
1 | 1 | 1 | 1 | 50
1 | 1 | 1 | 3 | 50
1 | 1 | 1 | 5 | 50
1 | 1 | 1 | 7 | 50
1 | 1 | 1 | 9 | 50
1 | 1 | 1 | | 250
1 | 1 | 2 | 0 | 50
1 | 1 | 2 | 2 | 50
1 | 1 | 2 | 4 | 50
1 | 1 | 2 | 6 | 50
1 | 1 | 2 | 8 | 50
1 | 1 | 2 | | 250
1 | 1 | 3 | 1 | 50
1 | 1 | 3 | 3 | 50
1 | 1 | 3 | 5 | 50
1 | 1 | 3 | 7 | 50
1 | 1 | 3 | 9 | 50
1 | 1 | 3 | | 250
1 | 1 | | 0 | 100
1 | 1 | | 1 | 100
1 | 1 | | 2 | 100
1 | 1 | | 3 | 100
1 | 1 | | 4 | 100
1 | 1 | | 5 | 100
1 | 1 | | 6 | 100
1 | 1 | | 7 | 100
1 | 1 | | 8 | 100
1 | 1 | | 9 | 100
1 | 1 | | | 1000
2 | 2 | 0 | 0 | 50
2 | 2 | 0 | 2 | 50
2 | 2 | 0 | 4 | 50
2 | 2 | 0 | 6 | 50
2 | 2 | 0 | 8 | 50
2 | 2 | 0 | | 250
2 | 2 | 1 | 1 | 50
2 | 2 | 1 | 3 | 50
2 | 2 | 1 | 5 | 50
2 | 2 | 1 | 7 | 50
2 | 2 | 1 | 9 | 50
2 | 2 | 1 | | 250
2 | 2 | 2 | 0 | 50
2 | 2 | 2 | 2 | 50
2 | 2 | 2 | 4 | 50
2 | 2 | 2 | 6 | 50
2 | 2 | 2 | 8 | 50
2 | 2 | 2 | | 250
2 | 2 | 3 | 1 | 50
2 | 2 | 3 | 3 | 50
2 | 2 | 3 | 5 | 50
2 | 2 | 3 | 7 | 50
2 | 2 | 3 | 9 | 50
2 | 2 | 3 | | 250
2 | 2 | | 0 | 100
2 | 2 | | 1 | 100
2 | 2 | | 2 | 100
2 | 2 | | 3 | 100
2 | 2 | | 4 | 100
2 | 2 | | 5 | 100
2 | 2 | | 6 | 100
2 | 2 | | 7 | 100
2 | 2 | | 8 | 100
2 | 2 | | 9 | 100
2 | 2 | | | 1000
(70 rows)
select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a);
array
------------------------------------------------------------------------------------------------------------------------------------------------------
{"(1,0,0,250)","(1,0,2,250)","(1,0,,500)","(1,1,1,250)","(1,1,3,250)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)","(1,,,1000)"}
{"(2,0,0,250)","(2,0,2,250)","(2,0,,500)","(2,1,1,250)","(2,1,3,250)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)","(2,,,1000)"}
(2 rows)
-- end

View File

@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample
test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets
# ----------
# Another group of parallel tests

View File

@ -86,6 +86,7 @@ test: union
test: case
test: join
test: aggregates
test: groupingsets
test: transactions
ignore: random
test: random

View File

@ -0,0 +1,165 @@
--
-- grouping sets
--
-- test data sources
create temp view gstest1(a,b,v)
as values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),
(2,3,15),
(3,3,16),(3,4,17),
(4,1,18),(4,1,19);
create temp table gstest2 (a integer, b integer, c integer, d integer,
e integer, f integer, g integer, h integer);
copy gstest2 from stdin;
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 2
1 1 1 1 1 1 2 2
1 1 1 1 1 2 2 2
1 1 1 1 2 2 2 2
1 1 1 2 2 2 2 2
1 1 2 2 2 2 2 2
1 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2
\.
create temp table gstest3 (a integer, b integer, c integer, d integer);
copy gstest3 from stdin;
1 1 1 1
2 2 2 2
\.
alter table gstest3 add primary key (a);
create temp table gstest_empty (a integer, b integer, v integer);
create function gstest_data(v integer, out a integer, out b integer)
returns setof record
as $f$
begin
return query select v, i from generate_series(1,3) i;
end;
$f$ language plpgsql;
-- basic functionality
-- simple rollup with multiple plain aggregates, with and without ordering
-- (and with ordering differing from grouping)
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b);
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b) order by a,b;
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b) order by b desc, a;
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by rollup (a,b) order by coalesce(a,0)+coalesce(b,0);
-- various types of ordered aggs
select a, b, grouping(a,b),
array_agg(v order by v),
string_agg(v::text, ':' order by v desc),
percentile_disc(0.5) within group (order by v),
rank(1,2,12) within group (order by a,b,v)
from gstest1 group by rollup (a,b) order by a,b;
-- test usage of grouped columns in direct args of aggs
select grouping(a), a, array_agg(b),
rank(a) within group (order by b nulls first),
rank(a) within group (order by b nulls last)
from (values (1,1),(1,4),(1,5),(3,1),(3,2)) v(a,b)
group by rollup (a) order by a;
-- nesting with window functions
select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
from gstest2 group by rollup (a,b) order by rsum, a, b;
-- empty input: first is 0 rows, second 1, third 3 etc.
select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a);
select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),());
select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),());
select sum(v), count(*) from gstest_empty group by grouping sets ((),(),());
-- empty input with joins tests some important code paths
select t1.a, t2.b, sum(t1.v), count(*) from gstest_empty t1, gstest_empty t2
group by grouping sets ((t1.a,t2.b),());
-- simple joins, var resolution, GROUPING on join vars
select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
from gstest1 t1, gstest2 t2
group by grouping sets ((t1.a, t2.b), ());
select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a)
from gstest1 t1 join gstest2 t2 on (t1.a=t2.a)
group by grouping sets ((t1.a, t2.b), ());
select a, b, grouping(a, b), sum(t1.v), max(t2.c)
from gstest1 t1 join gstest2 t2 using (a,b)
group by grouping sets ((a, b), ());
-- check that functionally dependent cols are not nulled
select a, d, grouping(a,b,c)
from gstest3
group by grouping sets ((a,b), (a,c));
-- simple rescan tests
select a, b, sum(v.x)
from (values (1),(2)) v(x), gstest_data(v.x)
group by rollup (a,b);
select *
from (values (1),(2)) v(x),
lateral (select a, b, sum(v.x) from gstest_data(v.x) group by rollup (a,b)) s;
-- min max optimisation should still work with GROUP BY ()
explain (costs off)
select min(unique1) from tenk1 GROUP BY ();
-- Views with GROUPING SET queries
CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c)
from gstest2 group by rollup ((a,b,c),(c,d));
select pg_get_viewdef('gstest_view'::regclass, true);
-- Nested queries with 3 or more levels of nesting
select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
select(select (select grouping(e,f) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
select(select (select grouping(c) from (values (1)) v2(c) GROUP BY c) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f);
-- Combinations of operations
select a, b, c, d from gstest2 group by rollup(a,b),grouping sets(c,d);
select a, b from (values (1,2),(2,3)) v(a,b) group by a,b, grouping sets(a);
-- Tests for chained aggregates
select a, b, grouping(a,b), sum(v), count(*), max(v)
from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2));
select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP((e+1),(f+1));
select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY CUBE((e+1),(f+1)) ORDER BY (e+1),(f+1);
select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum
from gstest2 group by cube (a,b) order by rsum, a, b;
select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b);
select a, b, sum(v.x)
from (values (1),(2)) v(x), gstest_data(v.x)
group by cube (a,b) order by a,b;
-- Agg level check. This query should error out.
select (select grouping(a,b) from gstest2) from gstest2 group by a,b;
--Nested queries
select a, b, sum(c), count(*) from gstest2 group by grouping sets (rollup(a,b),a);
-- HAVING queries
select ten, sum(distinct four) from onek a
group by grouping sets((ten,four),(ten))
having exists (select 1 from onek b where sum(distinct a.four) = b.four);
-- FILTER queries
select ten, sum(distinct four) filter (where four::text ~ '123') from onek a
group by rollup(ten);
-- More rescan tests
select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten;
select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a);
-- end