diff options
Diffstat (limited to 'src/backend/parser')
| -rw-r--r-- | src/backend/parser/analyze.c | 5 | ||||
| -rw-r--r-- | src/backend/parser/gram.y | 123 | ||||
| -rw-r--r-- | src/backend/parser/parse_agg.c | 723 | ||||
| -rw-r--r-- | src/backend/parser/parse_clause.c | 504 | ||||
| -rw-r--r-- | src/backend/parser/parse_expr.c | 5 | ||||
| -rw-r--r-- | src/backend/parser/parse_target.c | 4 |
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) { |
