summaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/analyze.c5
-rw-r--r--src/backend/parser/gram.y123
-rw-r--r--src/backend/parser/parse_agg.c723
-rw-r--r--src/backend/parser/parse_clause.c504
-rw-r--r--src/backend/parser/parse_expr.c5
-rw-r--r--src/backend/parser/parse_target.c4
6 files changed, 1266 insertions, 98 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 3eb4feabfd6..82c9abfa915 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -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)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 14397830686..46f2229b68c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7b0e66807d4..1e3f2e0ffa2 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -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 =
@@ -1085,6 +1333,395 @@ check_ungrouped_columns_walker(Node *node,
}
/*
+ * 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.
*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 6b1bbe57d0e..a90bcf40c9d 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -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;
}
+/*-------------------------------------------------------------------------
+ * Flatten out parenthesized sublists in grouping lists, and some cases
+ * of nested grouping sets.
+ *
+ * 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)).
+ *
+ * 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.
+ *-------------------------------------------------------------------------
+ */
+static Node *
+flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets)
+{
+ /* just in case of pathological input */
+ check_stack_depth();
+
+ if (expr == (Node *) NIL)
+ return (Node *) NIL;
+
+ switch (expr->type)
+ {
+ 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 (hasGroupingSets)
+ *hasGroupingSets = true;
+
+ /*
+ * 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;
+}
+
/*
- * transformGroupClause -
- * transform a GROUP BY clause
+ * Transform a single expression within a GROUP BY clause or grouping set.
*
- * GROUP BY items will be added to the targetlist (as resjunk columns)
- * if not already present, so the targetlist must be passed by reference.
+ * 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.
*
- * This is also used for window PARTITION BY clauses (which act almost the
- * same, but are always interpreted per SQL99 rules).
+ * 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
*/
-List *
-transformGroupClause(ParseState *pstate, List *grouplist,
- List **targetlist, List *sortClause,
- ParseExprKind exprKind, bool useSQL99)
+static Index
+transformGroupClauseExpr(List **flatresult, Bitmapset *seen_local,
+ ParseState *pstate, Node *gexpr,
+ List **targetlist, List *sortClause,
+ ParseExprKind exprKind, bool useSQL99, bool toplevel)
{
- List *result = NIL;
- ListCell *gl;
+ TargetEntry *tle;
+ bool found = false;
+
+ if (useSQL99)
+ tle = findTargetlistEntrySQL99(pstate, gexpr,
+ targetlist, exprKind);
+ else
+ tle = findTargetlistEntrySQL92(pstate, gexpr,
+ targetlist, exprKind);
- foreach(gl, grouplist)
+ if (tle->ressortgroupref > 0)
{
- Node *gexpr = (Node *) lfirst(gl);
- TargetEntry *tle;
- bool found = false;
+ ListCell *sl;
- if (useSQL99)
- tle = findTargetlistEntrySQL99(pstate, gexpr,
- targetlist, exprKind);
- else
- tle = findTargetlistEntrySQL92(pstate, gexpr,
- targetlist, exprKind);
+ /*
+ * 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;
- /* Eliminate duplicates (GROUP BY x, x) */
- if (targetIsInSortList(tle, InvalidOid, result))
- continue;
+ /*
+ * 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);
+ SortGroupClause *grpc = copyObject(sc);
+ if (!toplevel)
+ grpc->nulls_first = false;
+ *flatresult = lappend(*flatresult, grpc);
+ found = true;
+ break;
+ }
+ }
+ }
- if (sc->tleSortGroupRef == tle->ressortgroupref)
- {
- result = lappend(result, copyObject(sc));
- found = true;
+ /*
+ * If no match in ORDER BY, just add it to the result using default
+ * sort/group semantics.
+ */
+ if (!found)
+ *flatresult = addTargetToGroupList(pstate, tle,
+ *flatresult, *targetlist,
+ exprLocation(gexpr),
+ true);
+
+ /*
+ * _something_ must have assigned us a sortgroupref by now...
+ */
+
+ return tle->ressortgroupref;
+}
+
+/*
+ * Transform a list of expressions within a GROUP BY clause or grouping set.
+ *
+ * The list of expressions belongs to a single clause within which duplicates
+ * can be safely eliminated.
+ *
+ * Returns an integer list of ressortgroupref values.
+ *
+ * flatresult reference to flat list of SortGroupClause nodes
+ * pstate ParseState
+ * list nodes to transform
+ * targetlist reference to TargetEntry list
+ * sortClause ORDER BY clause (SortGroupClause nodes)
+ * exprKind expression kind
+ * useSQL99 SQL99 rather than SQL92 syntax
+ * toplevel false if within any grouping set
+ */
+static List *
+transformGroupClauseList(List **flatresult,
+ ParseState *pstate, List *list,
+ List **targetlist, List *sortClause,
+ ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+ Bitmapset *seen_local = NULL;
+ List *result = NIL;
+ ListCell *gl;
+
+ foreach(gl, list)
+ {
+ Node *gexpr = (Node *) lfirst(gl);
+
+ Index ref = transformGroupClauseExpr(flatresult,
+ seen_local,
+ pstate,
+ gexpr,
+ targetlist,
+ sortClause,
+ exprKind,
+ useSQL99,
+ toplevel);
+ if (ref > 0)
+ {
+ seen_local = bms_add_member(seen_local, ref);
+ result = lappend_int(result, ref);
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Transform a grouping set and (recursively) its content.
+ *
+ * The grouping set might be a GROUPING SETS node with other grouping sets
+ * inside it, but SETS within SETS have already been flattened out before
+ * reaching here.
+ *
+ * Returns the transformed node, which now contains SIMPLE nodes with lists
+ * of ressortgrouprefs rather than expressions.
+ *
+ * flatresult reference to flat list of SortGroupClause nodes
+ * pstate ParseState
+ * gset grouping set to transform
+ * targetlist reference to TargetEntry list
+ * sortClause ORDER BY clause (SortGroupClause nodes)
+ * exprKind expression kind
+ * useSQL99 SQL99 rather than SQL92 syntax
+ * toplevel false if within any grouping set
+ */
+static Node *
+transformGroupingSet(List **flatresult,
+ ParseState *pstate, GroupingSet *gset,
+ List **targetlist, List *sortClause,
+ ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+ ListCell *gl;
+ List *content = NIL;
+
+ Assert(toplevel || gset->kind != GROUPING_SET_SETS);
+
+ foreach(gl, gset->content)
+ {
+ Node *n = lfirst(gl);
+
+ if (IsA(n, List))
+ {
+ List *l = transformGroupClauseList(flatresult,
+ pstate, (List *) n,
+ targetlist, sortClause,
+ exprKind, useSQL99, false);
+
+ content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
+ l,
+ exprLocation(n)));
+ }
+ else if (IsA(n, GroupingSet))
+ {
+ GroupingSet *gset2 = (GroupingSet *) lfirst(gl);
+
+ content = lappend(content, transformGroupingSet(flatresult,
+ pstate, gset2,
+ targetlist, sortClause,
+ exprKind, useSQL99, false));
+ }
+ else
+ {
+ Index ref = transformGroupClauseExpr(flatresult,
+ NULL,
+ pstate,
+ n,
+ targetlist,
+ sortClause,
+ exprKind,
+ useSQL99,
+ false);
+
+ content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
+ list_make1_int(ref),
+ exprLocation(n)));
+ }
+ }
+
+ /* Arbitrarily cap the size of CUBE, which has exponential growth */
+ if (gset->kind == GROUPING_SET_CUBE)
+ {
+ if (list_length(content) > 12)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("CUBE is limited to 12 elements"),
+ parser_errposition(pstate, gset->location)));
+ }
+
+ return (Node *) makeGroupingSet(gset->kind, content, gset->location);
+}
+
+
+/*
+ * transformGroupClause -
+ * transform a GROUP BY clause
+ *
+ * GROUP BY items will be added to the targetlist (as resjunk columns)
+ * if not already present, so the targetlist must be passed by reference.
+ *
+ * This is also used for window PARTITION BY clauses (which act almost the
+ * same, but are always interpreted per SQL99 rules).
+ *
+ * Grouping sets make this a lot more complex than it was. Our goal here is
+ * twofold: we make a flat list of SortGroupClause nodes referencing each
+ * distinct expression used for grouping, with those expressions added to the
+ * targetlist if needed. At the same time, we build the groupingSets tree,
+ * which stores only ressortgrouprefs as integer lists inside GroupingSet nodes
+ * (possibly nested, but limited in depth: a GROUPING_SET_SETS node can contain
+ * nested SIMPLE, CUBE or ROLLUP nodes, but not more sets - we flatten that
+ * out; while CUBE and ROLLUP can contain only SIMPLE nodes).
+ *
+ * We skip much of the hard work if there are no grouping sets.
+ *
+ * One subtlety is that the groupClause list can end up empty while the
+ * groupingSets list is not; this happens if there are only empty grouping
+ * sets, or an explicit GROUP BY (). This has the same effect as specifying
+ * aggregates or a HAVING clause with no GROUP BY; the output is one row per
+ * grouping set even if the input is empty.
+ *
+ * Returns the transformed (flat) groupClause.
+ *
+ * pstate ParseState
+ * grouplist clause to transform
+ * groupingSets reference to list to contain the grouping set tree
+ * targetlist reference to TargetEntry list
+ * sortClause ORDER BY clause (SortGroupClause nodes)
+ * exprKind expression kind
+ * useSQL99 SQL99 rather than SQL92 syntax
+ */
+List *
+transformGroupClause(ParseState *pstate, List *grouplist, List **groupingSets,
+ List **targetlist, List *sortClause,
+ ParseExprKind exprKind, bool useSQL99)
+{
+ List *result = NIL;
+ List *flat_grouplist;
+ List *gsets = NIL;
+ ListCell *gl;
+ bool hasGroupingSets = false;
+ Bitmapset *seen_local = NULL;
+
+ /*
+ * Recursively flatten implicit RowExprs. (Technically this is only
+ * needed for GROUP BY, per the syntax rules for grouping sets, but
+ * we do it anyway.)
+ */
+ flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist,
+ true,
+ &hasGroupingSets);
+
+ /*
+ * If the list is now empty, but hasGroupingSets is true, it's because
+ * we elided redundant empty grouping sets. Restore a single empty
+ * grouping set to leave a canonical form: GROUP BY ()
+ */
+
+ if (flat_grouplist == NIL && hasGroupingSets)
+ {
+ flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY,
+ NIL,
+ exprLocation((Node *) grouplist)));
+ }
+
+ foreach(gl, flat_grouplist)
+ {
+ Node *gexpr = (Node *) lfirst(gl);
+
+ if (IsA(gexpr, GroupingSet))
+ {
+ GroupingSet *gset = (GroupingSet *) gexpr;
+
+ switch (gset->kind)
+ {
+ case GROUPING_SET_EMPTY:
+ gsets = lappend(gsets, gset);
+ break;
+ case GROUPING_SET_SIMPLE:
+ /* can't happen */
+ Assert(false);
+ break;
+ case GROUPING_SET_SETS:
+ case GROUPING_SET_CUBE:
+ case GROUPING_SET_ROLLUP:
+ gsets = lappend(gsets,
+ transformGroupingSet(&result,
+ pstate, gset,
+ targetlist, sortClause,
+ exprKind, useSQL99, true));
break;
- }
}
}
+ else
+ {
+ Index ref = transformGroupClauseExpr(&result, seen_local,
+ pstate, gexpr,
+ targetlist, sortClause,
+ exprKind, useSQL99, true);
- /*
- * If no match in ORDER BY, just add it to the result using default
- * sort/group semantics.
- */
- if (!found)
- result = addTargetToGroupList(pstate, tle,
- result, *targetlist,
- exprLocation(gexpr),
- true);
+ if (ref > 0)
+ {
+ seen_local = bms_add_member(seen_local, ref);
+ if (hasGroupingSets)
+ gsets = lappend(gsets,
+ makeGroupingSet(GROUPING_SET_SIMPLE,
+ list_make1_int(ref),
+ exprLocation(gexpr)));
+ }
+ }
}
+ /* parser should prevent this */
+ Assert(gsets == NIL || groupingSets != NULL);
+
+ if (groupingSets)
+ *groupingSets = gsets;
+
return result;
}
@@ -1903,6 +2318,7 @@ transformWindowDefinitions(ParseState *pstate,
true /* force SQL99 rules */ );
partitionClause = transformGroupClause(pstate,
windef->partitionClause,
+ NULL,
targetlist,
orderClause,
EXPR_KIND_WINDOW_PARTITION,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f759606f88b..0ff46dd457c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -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;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 59973ba9c3c..1b3fcd629c1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -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)
{