mirror of
https://github.com/postgres/postgres.git
synced 2025-09-03 15:22:11 +03:00
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different GROUP BY clauses at once. Each grouping set returns rows with columns grouped by in other sets set to NULL. This could previously be achieved by doing each grouping as a separate query, conjoined by UNION ALLs. Besides being considerably more concise, grouping sets will in many cases be faster, requiring only one scan over the underlying data. The current implementation of grouping sets only supports using sorting for input. Individual sets that share a sort order are computed in one pass. If there are sets that don't share a sort order, additional sort & aggregation steps are performed. These additional passes are sourced by the previous sort step; thus avoiding repeated scans of the source data. The code is structured in a way that adding support for purely using hash aggregation or a mix of hashing and sorting is possible. Sorting was chosen to be supported first, as it is the most generic method of implementation. Instead of, as in an earlier versions of the patch, representing the chain of sort and aggregation steps as full blown planner and executor nodes, all but the first sort are performed inside the aggregation node itself. This avoids the need to do some unusual gymnastics to handle having to return aggregated and non-aggregated tuples from underlying nodes, as well as having to shut down underlying nodes early to limit memory usage. The optimizer still builds Sort/Agg node to describe each phase, but they're not part of the plan tree, but instead additional data for the aggregation node. They're a convenient and preexisting way to describe aggregation and sorting. The first (and possibly only) sort step is still performed as a separate execution step. That retains similarity with existing group by plans, makes rescans fairly simple, avoids very deep plans (leading to slow explains) and easily allows to avoid the sorting step if the underlying data is sorted by other means. A somewhat ugly side of this patch is having to deal with a grammar ambiguity between the new CUBE keyword and the cube extension/functions named cube (and rollup). To avoid breaking existing deployments of the cube extension it has not been renamed, neither has cube been made a reserved keyword. Instead precedence hacking is used to make GROUP BY cube(..) refer to the CUBE grouping sets feature, and not the function cube(). To actually group by a function cube(), unlikely as that might be, the function name has to be quoted. Needs a catversion bump because stored rules may change. Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
This commit is contained in:
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -42,7 +42,9 @@ typedef struct
|
||||
{
|
||||
ParseState *pstate;
|
||||
Query *qry;
|
||||
PlannerInfo *root;
|
||||
List *groupClauses;
|
||||
List *groupClauseCommonVars;
|
||||
bool have_non_var_grouping;
|
||||
List **func_grouped_rels;
|
||||
int sublevels_up;
|
||||
@@ -56,11 +58,18 @@ static int check_agg_arguments(ParseState *pstate,
|
||||
static bool check_agg_arguments_walker(Node *node,
|
||||
check_agg_arguments_context *context);
|
||||
static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
|
||||
List *groupClauses, bool have_non_var_grouping,
|
||||
List *groupClauses, List *groupClauseVars,
|
||||
bool have_non_var_grouping,
|
||||
List **func_grouped_rels);
|
||||
static bool check_ungrouped_columns_walker(Node *node,
|
||||
check_ungrouped_columns_context *context);
|
||||
|
||||
static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
|
||||
List *groupClauses, PlannerInfo *root,
|
||||
bool have_non_var_grouping);
|
||||
static bool finalize_grouping_exprs_walker(Node *node,
|
||||
check_ungrouped_columns_context *context);
|
||||
static void check_agglevels_and_constraints(ParseState *pstate,Node *expr);
|
||||
static List *expand_groupingset_node(GroupingSet *gs);
|
||||
|
||||
/*
|
||||
* transformAggregateCall -
|
||||
@@ -96,10 +105,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
|
||||
List *tdistinct = NIL;
|
||||
AttrNumber attno = 1;
|
||||
int save_next_resno;
|
||||
int min_varlevel;
|
||||
ListCell *lc;
|
||||
const char *err;
|
||||
bool errkind;
|
||||
|
||||
if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
|
||||
{
|
||||
@@ -214,15 +220,97 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
|
||||
agg->aggorder = torder;
|
||||
agg->aggdistinct = tdistinct;
|
||||
|
||||
check_agglevels_and_constraints(pstate, (Node *) agg);
|
||||
}
|
||||
|
||||
/*
|
||||
* transformGroupingFunc
|
||||
* Transform a GROUPING expression
|
||||
*
|
||||
* GROUPING() behaves very like an aggregate. Processing of levels and nesting
|
||||
* is done as for aggregates. We set p_hasAggs for these expressions too.
|
||||
*/
|
||||
Node *
|
||||
transformGroupingFunc(ParseState *pstate, GroupingFunc *p)
|
||||
{
|
||||
ListCell *lc;
|
||||
List *args = p->args;
|
||||
List *result_list = NIL;
|
||||
GroupingFunc *result = makeNode(GroupingFunc);
|
||||
|
||||
if (list_length(args) > 31)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||
errmsg("GROUPING must have fewer than 32 arguments"),
|
||||
parser_errposition(pstate, p->location)));
|
||||
|
||||
foreach(lc, args)
|
||||
{
|
||||
Node *current_result;
|
||||
|
||||
current_result = transformExpr(pstate, (Node*) lfirst(lc), pstate->p_expr_kind);
|
||||
|
||||
/* acceptability of expressions is checked later */
|
||||
|
||||
result_list = lappend(result_list, current_result);
|
||||
}
|
||||
|
||||
result->args = result_list;
|
||||
result->location = p->location;
|
||||
|
||||
check_agglevels_and_constraints(pstate, (Node *) result);
|
||||
|
||||
return (Node *) result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Aggregate functions and grouping operations (which are combined in the spec
|
||||
* as <set function specification>) are very similar with regard to level and
|
||||
* nesting restrictions (though we allow a lot more things than the spec does).
|
||||
* Centralise those restrictions here.
|
||||
*/
|
||||
static void
|
||||
check_agglevels_and_constraints(ParseState *pstate, Node *expr)
|
||||
{
|
||||
List *directargs = NIL;
|
||||
List *args = NIL;
|
||||
Expr *filter = NULL;
|
||||
int min_varlevel;
|
||||
int location = -1;
|
||||
Index *p_levelsup;
|
||||
const char *err;
|
||||
bool errkind;
|
||||
bool isAgg = IsA(expr, Aggref);
|
||||
|
||||
if (isAgg)
|
||||
{
|
||||
Aggref *agg = (Aggref *) expr;
|
||||
|
||||
directargs = agg->aggdirectargs;
|
||||
args = agg->args;
|
||||
filter = agg->aggfilter;
|
||||
location = agg->location;
|
||||
p_levelsup = &agg->agglevelsup;
|
||||
}
|
||||
else
|
||||
{
|
||||
GroupingFunc *grp = (GroupingFunc *) expr;
|
||||
|
||||
args = grp->args;
|
||||
location = grp->location;
|
||||
p_levelsup = &grp->agglevelsup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the arguments to compute the aggregate's level and detect
|
||||
* improper nesting.
|
||||
*/
|
||||
min_varlevel = check_agg_arguments(pstate,
|
||||
agg->aggdirectargs,
|
||||
agg->args,
|
||||
agg->aggfilter);
|
||||
agg->agglevelsup = min_varlevel;
|
||||
directargs,
|
||||
args,
|
||||
filter);
|
||||
|
||||
*p_levelsup = min_varlevel;
|
||||
|
||||
/* Mark the correct pstate level as having aggregates */
|
||||
while (min_varlevel-- > 0)
|
||||
@@ -247,20 +335,32 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
|
||||
Assert(false); /* can't happen */
|
||||
break;
|
||||
case EXPR_KIND_OTHER:
|
||||
/* Accept aggregate here; caller must throw error if wanted */
|
||||
/* Accept aggregate/grouping here; caller must throw error if wanted */
|
||||
break;
|
||||
case EXPR_KIND_JOIN_ON:
|
||||
case EXPR_KIND_JOIN_USING:
|
||||
err = _("aggregate functions are not allowed in JOIN conditions");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in JOIN conditions");
|
||||
else
|
||||
err = _("grouping operations are not allowed in JOIN conditions");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_FROM_SUBSELECT:
|
||||
/* Should only be possible in a LATERAL subquery */
|
||||
Assert(pstate->p_lateral_active);
|
||||
/* Aggregate scope rules make it worth being explicit here */
|
||||
err = _("aggregate functions are not allowed in FROM clause of their own query level");
|
||||
/* Aggregate/grouping scope rules make it worth being explicit here */
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in FROM clause of their own query level");
|
||||
else
|
||||
err = _("grouping operations are not allowed in FROM clause of their own query level");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_FROM_FUNCTION:
|
||||
err = _("aggregate functions are not allowed in functions in FROM");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in functions in FROM");
|
||||
else
|
||||
err = _("grouping operations are not allowed in functions in FROM");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_WHERE:
|
||||
errkind = true;
|
||||
@@ -278,10 +378,18 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
|
||||
/* okay */
|
||||
break;
|
||||
case EXPR_KIND_WINDOW_FRAME_RANGE:
|
||||
err = _("aggregate functions are not allowed in window RANGE");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in window RANGE");
|
||||
else
|
||||
err = _("grouping operations are not allowed in window RANGE");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_WINDOW_FRAME_ROWS:
|
||||
err = _("aggregate functions are not allowed in window ROWS");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in window ROWS");
|
||||
else
|
||||
err = _("grouping operations are not allowed in window ROWS");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_SELECT_TARGET:
|
||||
/* okay */
|
||||
@@ -312,26 +420,55 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
|
||||
break;
|
||||
case EXPR_KIND_CHECK_CONSTRAINT:
|
||||
case EXPR_KIND_DOMAIN_CHECK:
|
||||
err = _("aggregate functions are not allowed in check constraints");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in check constraints");
|
||||
else
|
||||
err = _("grouping operations are not allowed in check constraints");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_COLUMN_DEFAULT:
|
||||
case EXPR_KIND_FUNCTION_DEFAULT:
|
||||
err = _("aggregate functions are not allowed in DEFAULT expressions");
|
||||
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in DEFAULT expressions");
|
||||
else
|
||||
err = _("grouping operations are not allowed in DEFAULT expressions");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_INDEX_EXPRESSION:
|
||||
err = _("aggregate functions are not allowed in index expressions");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in index expressions");
|
||||
else
|
||||
err = _("grouping operations are not allowed in index expressions");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_INDEX_PREDICATE:
|
||||
err = _("aggregate functions are not allowed in index predicates");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in index predicates");
|
||||
else
|
||||
err = _("grouping operations are not allowed in index predicates");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_ALTER_COL_TRANSFORM:
|
||||
err = _("aggregate functions are not allowed in transform expressions");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in transform expressions");
|
||||
else
|
||||
err = _("grouping operations are not allowed in transform expressions");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_EXECUTE_PARAMETER:
|
||||
err = _("aggregate functions are not allowed in EXECUTE parameters");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in EXECUTE parameters");
|
||||
else
|
||||
err = _("grouping operations are not allowed in EXECUTE parameters");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_TRIGGER_WHEN:
|
||||
err = _("aggregate functions are not allowed in trigger WHEN conditions");
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in trigger WHEN conditions");
|
||||
else
|
||||
err = _("grouping operations are not allowed in trigger WHEN conditions");
|
||||
|
||||
break;
|
||||
|
||||
/*
|
||||
@@ -342,18 +479,28 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
|
||||
* which is sane anyway.
|
||||
*/
|
||||
}
|
||||
|
||||
if (err)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg_internal("%s", err),
|
||||
parser_errposition(pstate, agg->location)));
|
||||
parser_errposition(pstate, location)));
|
||||
|
||||
if (errkind)
|
||||
{
|
||||
if (isAgg)
|
||||
/* translator: %s is name of a SQL construct, eg GROUP BY */
|
||||
err = _("aggregate functions are not allowed in %s");
|
||||
else
|
||||
/* translator: %s is name of a SQL construct, eg GROUP BY */
|
||||
err = _("grouping operations are not allowed in %s");
|
||||
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
/* translator: %s is name of a SQL construct, eg GROUP BY */
|
||||
errmsg("aggregate functions are not allowed in %s",
|
||||
ParseExprKindName(pstate->p_expr_kind)),
|
||||
parser_errposition(pstate, agg->location)));
|
||||
errmsg_internal(err,
|
||||
ParseExprKindName(pstate->p_expr_kind)),
|
||||
parser_errposition(pstate, location)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -466,7 +613,6 @@ check_agg_arguments(ParseState *pstate,
|
||||
locate_agg_of_level((Node *) directargs,
|
||||
context.min_agglevel))));
|
||||
}
|
||||
|
||||
return agglevel;
|
||||
}
|
||||
|
||||
@@ -507,6 +653,21 @@ check_agg_arguments_walker(Node *node,
|
||||
/* no need to examine args of the inner aggregate */
|
||||
return false;
|
||||
}
|
||||
if (IsA(node, GroupingFunc))
|
||||
{
|
||||
int agglevelsup = ((GroupingFunc *) node)->agglevelsup;
|
||||
|
||||
/* convert levelsup to frame of reference of original query */
|
||||
agglevelsup -= context->sublevels_up;
|
||||
/* ignore local aggs of subqueries */
|
||||
if (agglevelsup >= 0)
|
||||
{
|
||||
if (context->min_agglevel < 0 ||
|
||||
context->min_agglevel > agglevelsup)
|
||||
context->min_agglevel = agglevelsup;
|
||||
}
|
||||
/* Continue and descend into subtree */
|
||||
}
|
||||
/* We can throw error on sight for a window function */
|
||||
if (IsA(node, WindowFunc))
|
||||
ereport(ERROR,
|
||||
@@ -527,6 +688,7 @@ check_agg_arguments_walker(Node *node,
|
||||
context->sublevels_up--;
|
||||
return result;
|
||||
}
|
||||
|
||||
return expression_tree_walker(node,
|
||||
check_agg_arguments_walker,
|
||||
(void *) context);
|
||||
@@ -770,17 +932,66 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
|
||||
void
|
||||
parseCheckAggregates(ParseState *pstate, Query *qry)
|
||||
{
|
||||
List *gset_common = NIL;
|
||||
List *groupClauses = NIL;
|
||||
List *groupClauseCommonVars = NIL;
|
||||
bool have_non_var_grouping;
|
||||
List *func_grouped_rels = NIL;
|
||||
ListCell *l;
|
||||
bool hasJoinRTEs;
|
||||
bool hasSelfRefRTEs;
|
||||
PlannerInfo *root;
|
||||
PlannerInfo *root = NULL;
|
||||
Node *clause;
|
||||
|
||||
/* This should only be called if we found aggregates or grouping */
|
||||
Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual);
|
||||
Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual || qry->groupingSets);
|
||||
|
||||
/*
|
||||
* If we have grouping sets, expand them and find the intersection of all
|
||||
* sets.
|
||||
*/
|
||||
if (qry->groupingSets)
|
||||
{
|
||||
/*
|
||||
* The limit of 4096 is arbitrary and exists simply to avoid resource
|
||||
* issues from pathological constructs.
|
||||
*/
|
||||
List *gsets = expand_grouping_sets(qry->groupingSets, 4096);
|
||||
|
||||
if (!gsets)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
|
||||
errmsg("too many grouping sets present (max 4096)"),
|
||||
parser_errposition(pstate,
|
||||
qry->groupClause
|
||||
? exprLocation((Node *) qry->groupClause)
|
||||
: exprLocation((Node *) qry->groupingSets))));
|
||||
|
||||
/*
|
||||
* The intersection will often be empty, so help things along by
|
||||
* seeding the intersect with the smallest set.
|
||||
*/
|
||||
gset_common = linitial(gsets);
|
||||
|
||||
if (gset_common)
|
||||
{
|
||||
for_each_cell(l, lnext(list_head(gsets)))
|
||||
{
|
||||
gset_common = list_intersection_int(gset_common, lfirst(l));
|
||||
if (!gset_common)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If there was only one grouping set in the expansion, AND if the
|
||||
* groupClause is non-empty (meaning that the grouping set is not empty
|
||||
* either), then we can ditch the grouping set and pretend we just had
|
||||
* a normal GROUP BY.
|
||||
*/
|
||||
if (list_length(gsets) == 1 && qry->groupClause)
|
||||
qry->groupingSets = NIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan the range table to see if there are JOIN or self-reference CTE
|
||||
@@ -800,15 +1011,19 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
|
||||
/*
|
||||
* Build a list of the acceptable GROUP BY expressions for use by
|
||||
* check_ungrouped_columns().
|
||||
*
|
||||
* We get the TLE, not just the expr, because GROUPING wants to know
|
||||
* the sortgroupref.
|
||||
*/
|
||||
foreach(l, qry->groupClause)
|
||||
{
|
||||
SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
|
||||
Node *expr;
|
||||
TargetEntry *expr;
|
||||
|
||||
expr = get_sortgroupclause_expr(grpcl, qry->targetList);
|
||||
expr = get_sortgroupclause_tle(grpcl, qry->targetList);
|
||||
if (expr == NULL)
|
||||
continue; /* probably cannot happen */
|
||||
|
||||
groupClauses = lcons(expr, groupClauses);
|
||||
}
|
||||
|
||||
@@ -830,21 +1045,28 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
|
||||
groupClauses = (List *) flatten_join_alias_vars(root,
|
||||
(Node *) groupClauses);
|
||||
}
|
||||
else
|
||||
root = NULL; /* keep compiler quiet */
|
||||
|
||||
/*
|
||||
* Detect whether any of the grouping expressions aren't simple Vars; if
|
||||
* they're all Vars then we don't have to work so hard in the recursive
|
||||
* scans. (Note we have to flatten aliases before this.)
|
||||
*
|
||||
* Track Vars that are included in all grouping sets separately in
|
||||
* groupClauseCommonVars, since these are the only ones we can use to check
|
||||
* for functional dependencies.
|
||||
*/
|
||||
have_non_var_grouping = false;
|
||||
foreach(l, groupClauses)
|
||||
{
|
||||
if (!IsA((Node *) lfirst(l), Var))
|
||||
TargetEntry *tle = lfirst(l);
|
||||
if (!IsA(tle->expr, Var))
|
||||
{
|
||||
have_non_var_grouping = true;
|
||||
break;
|
||||
}
|
||||
else if (!qry->groupingSets ||
|
||||
list_member_int(gset_common, tle->ressortgroupref))
|
||||
{
|
||||
groupClauseCommonVars = lappend(groupClauseCommonVars, tle->expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,19 +1077,30 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
|
||||
* this will also find ungrouped variables that came from ORDER BY and
|
||||
* WINDOW clauses. For that matter, it's also going to examine the
|
||||
* grouping expressions themselves --- but they'll all pass the test ...
|
||||
*
|
||||
* We also finalize GROUPING expressions, but for that we need to traverse
|
||||
* the original (unflattened) clause in order to modify nodes.
|
||||
*/
|
||||
clause = (Node *) qry->targetList;
|
||||
finalize_grouping_exprs(clause, pstate, qry,
|
||||
groupClauses, root,
|
||||
have_non_var_grouping);
|
||||
if (hasJoinRTEs)
|
||||
clause = flatten_join_alias_vars(root, clause);
|
||||
check_ungrouped_columns(clause, pstate, qry,
|
||||
groupClauses, have_non_var_grouping,
|
||||
groupClauses, groupClauseCommonVars,
|
||||
have_non_var_grouping,
|
||||
&func_grouped_rels);
|
||||
|
||||
clause = (Node *) qry->havingQual;
|
||||
finalize_grouping_exprs(clause, pstate, qry,
|
||||
groupClauses, root,
|
||||
have_non_var_grouping);
|
||||
if (hasJoinRTEs)
|
||||
clause = flatten_join_alias_vars(root, clause);
|
||||
check_ungrouped_columns(clause, pstate, qry,
|
||||
groupClauses, have_non_var_grouping,
|
||||
groupClauses, groupClauseCommonVars,
|
||||
have_non_var_grouping,
|
||||
&func_grouped_rels);
|
||||
|
||||
/*
|
||||
@@ -904,14 +1137,17 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
|
||||
*/
|
||||
static void
|
||||
check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
|
||||
List *groupClauses, bool have_non_var_grouping,
|
||||
List *groupClauses, List *groupClauseCommonVars,
|
||||
bool have_non_var_grouping,
|
||||
List **func_grouped_rels)
|
||||
{
|
||||
check_ungrouped_columns_context context;
|
||||
|
||||
context.pstate = pstate;
|
||||
context.qry = qry;
|
||||
context.root = NULL;
|
||||
context.groupClauses = groupClauses;
|
||||
context.groupClauseCommonVars = groupClauseCommonVars;
|
||||
context.have_non_var_grouping = have_non_var_grouping;
|
||||
context.func_grouped_rels = func_grouped_rels;
|
||||
context.sublevels_up = 0;
|
||||
@@ -965,6 +1201,16 @@ check_ungrouped_columns_walker(Node *node,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsA(node, GroupingFunc))
|
||||
{
|
||||
GroupingFunc *grp = (GroupingFunc *) node;
|
||||
|
||||
/* handled GroupingFunc separately, no need to recheck at this level */
|
||||
|
||||
if ((int) grp->agglevelsup >= context->sublevels_up)
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have any GROUP BY items that are not simple Vars, check to see if
|
||||
* subexpression as a whole matches any GROUP BY item. We need to do this
|
||||
@@ -976,7 +1222,9 @@ check_ungrouped_columns_walker(Node *node,
|
||||
{
|
||||
foreach(gl, context->groupClauses)
|
||||
{
|
||||
if (equal(node, lfirst(gl)))
|
||||
TargetEntry *tle = lfirst(gl);
|
||||
|
||||
if (equal(node, tle->expr))
|
||||
return false; /* acceptable, do not descend more */
|
||||
}
|
||||
}
|
||||
@@ -1003,7 +1251,7 @@ check_ungrouped_columns_walker(Node *node,
|
||||
{
|
||||
foreach(gl, context->groupClauses)
|
||||
{
|
||||
Var *gvar = (Var *) lfirst(gl);
|
||||
Var *gvar = (Var *) ((TargetEntry *) lfirst(gl))->expr;
|
||||
|
||||
if (IsA(gvar, Var) &&
|
||||
gvar->varno == var->varno &&
|
||||
@@ -1040,7 +1288,7 @@ check_ungrouped_columns_walker(Node *node,
|
||||
if (check_functional_grouping(rte->relid,
|
||||
var->varno,
|
||||
0,
|
||||
context->groupClauses,
|
||||
context->groupClauseCommonVars,
|
||||
&context->qry->constraintDeps))
|
||||
{
|
||||
*context->func_grouped_rels =
|
||||
@@ -1084,6 +1332,395 @@ check_ungrouped_columns_walker(Node *node,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
/*
|
||||
* finalize_grouping_exprs -
|
||||
* Scan the given expression tree for GROUPING() and related calls,
|
||||
* and validate and process their arguments.
|
||||
*
|
||||
* This is split out from check_ungrouped_columns above because it needs
|
||||
* to modify the nodes (which it does in-place, not via a mutator) while
|
||||
* check_ungrouped_columns may see only a copy of the original thanks to
|
||||
* flattening of join alias vars. So here, we flatten each individual
|
||||
* GROUPING argument as we see it before comparing it.
|
||||
*/
|
||||
static void
|
||||
finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
|
||||
List *groupClauses, PlannerInfo *root,
|
||||
bool have_non_var_grouping)
|
||||
{
|
||||
check_ungrouped_columns_context context;
|
||||
|
||||
context.pstate = pstate;
|
||||
context.qry = qry;
|
||||
context.root = root;
|
||||
context.groupClauses = groupClauses;
|
||||
context.groupClauseCommonVars = NIL;
|
||||
context.have_non_var_grouping = have_non_var_grouping;
|
||||
context.func_grouped_rels = NULL;
|
||||
context.sublevels_up = 0;
|
||||
context.in_agg_direct_args = false;
|
||||
finalize_grouping_exprs_walker(node, &context);
|
||||
}
|
||||
|
||||
static bool
|
||||
finalize_grouping_exprs_walker(Node *node,
|
||||
check_ungrouped_columns_context *context)
|
||||
{
|
||||
ListCell *gl;
|
||||
|
||||
if (node == NULL)
|
||||
return false;
|
||||
if (IsA(node, Const) ||
|
||||
IsA(node, Param))
|
||||
return false; /* constants are always acceptable */
|
||||
|
||||
if (IsA(node, Aggref))
|
||||
{
|
||||
Aggref *agg = (Aggref *) node;
|
||||
|
||||
if ((int) agg->agglevelsup == context->sublevels_up)
|
||||
{
|
||||
/*
|
||||
* If we find an aggregate call of the original level, do not
|
||||
* recurse into its normal arguments, ORDER BY arguments, or
|
||||
* filter; GROUPING exprs of this level are not allowed there. But
|
||||
* check direct arguments as though they weren't in an aggregate.
|
||||
*/
|
||||
bool result;
|
||||
|
||||
Assert(!context->in_agg_direct_args);
|
||||
context->in_agg_direct_args = true;
|
||||
result = finalize_grouping_exprs_walker((Node *) agg->aggdirectargs,
|
||||
context);
|
||||
context->in_agg_direct_args = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* We can skip recursing into aggregates of higher levels altogether,
|
||||
* since they could not possibly contain exprs of concern to us (see
|
||||
* transformAggregateCall). We do need to look at aggregates of lower
|
||||
* levels, however.
|
||||
*/
|
||||
if ((int) agg->agglevelsup > context->sublevels_up)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsA(node, GroupingFunc))
|
||||
{
|
||||
GroupingFunc *grp = (GroupingFunc *) node;
|
||||
|
||||
/*
|
||||
* We only need to check GroupingFunc nodes at the exact level to which
|
||||
* they belong, since they cannot mix levels in arguments.
|
||||
*/
|
||||
|
||||
if ((int) grp->agglevelsup == context->sublevels_up)
|
||||
{
|
||||
ListCell *lc;
|
||||
List *ref_list = NIL;
|
||||
|
||||
foreach(lc, grp->args)
|
||||
{
|
||||
Node *expr = lfirst(lc);
|
||||
Index ref = 0;
|
||||
|
||||
if (context->root)
|
||||
expr = flatten_join_alias_vars(context->root, expr);
|
||||
|
||||
/*
|
||||
* Each expression must match a grouping entry at the current
|
||||
* query level. Unlike the general expression case, we don't
|
||||
* allow functional dependencies or outer references.
|
||||
*/
|
||||
|
||||
if (IsA(expr, Var))
|
||||
{
|
||||
Var *var = (Var *) expr;
|
||||
|
||||
if (var->varlevelsup == context->sublevels_up)
|
||||
{
|
||||
foreach(gl, context->groupClauses)
|
||||
{
|
||||
TargetEntry *tle = lfirst(gl);
|
||||
Var *gvar = (Var *) tle->expr;
|
||||
|
||||
if (IsA(gvar, Var) &&
|
||||
gvar->varno == var->varno &&
|
||||
gvar->varattno == var->varattno &&
|
||||
gvar->varlevelsup == 0)
|
||||
{
|
||||
ref = tle->ressortgroupref;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (context->have_non_var_grouping &&
|
||||
context->sublevels_up == 0)
|
||||
{
|
||||
foreach(gl, context->groupClauses)
|
||||
{
|
||||
TargetEntry *tle = lfirst(gl);
|
||||
|
||||
if (equal(expr, tle->expr))
|
||||
{
|
||||
ref = tle->ressortgroupref;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ref == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("arguments to GROUPING must be grouping expressions of the associated query level"),
|
||||
parser_errposition(context->pstate,
|
||||
exprLocation(expr))));
|
||||
|
||||
ref_list = lappend_int(ref_list, ref);
|
||||
}
|
||||
|
||||
grp->refs = ref_list;
|
||||
}
|
||||
|
||||
if ((int) grp->agglevelsup > context->sublevels_up)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
/* Recurse into subselects */
|
||||
bool result;
|
||||
|
||||
context->sublevels_up++;
|
||||
result = query_tree_walker((Query *) node,
|
||||
finalize_grouping_exprs_walker,
|
||||
(void *) context,
|
||||
0);
|
||||
context->sublevels_up--;
|
||||
return result;
|
||||
}
|
||||
return expression_tree_walker(node, finalize_grouping_exprs_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Given a GroupingSet node, expand it and return a list of lists.
|
||||
*
|
||||
* For EMPTY nodes, return a list of one empty list.
|
||||
*
|
||||
* For SIMPLE nodes, return a list of one list, which is the node content.
|
||||
*
|
||||
* For CUBE and ROLLUP nodes, return a list of the expansions.
|
||||
*
|
||||
* For SET nodes, recursively expand contained CUBE and ROLLUP.
|
||||
*/
|
||||
static List*
|
||||
expand_groupingset_node(GroupingSet *gs)
|
||||
{
|
||||
List * result = NIL;
|
||||
|
||||
switch (gs->kind)
|
||||
{
|
||||
case GROUPING_SET_EMPTY:
|
||||
result = list_make1(NIL);
|
||||
break;
|
||||
|
||||
case GROUPING_SET_SIMPLE:
|
||||
result = list_make1(gs->content);
|
||||
break;
|
||||
|
||||
case GROUPING_SET_ROLLUP:
|
||||
{
|
||||
List *rollup_val = gs->content;
|
||||
ListCell *lc;
|
||||
int curgroup_size = list_length(gs->content);
|
||||
|
||||
while (curgroup_size > 0)
|
||||
{
|
||||
List *current_result = NIL;
|
||||
int i = curgroup_size;
|
||||
|
||||
foreach(lc, rollup_val)
|
||||
{
|
||||
GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
|
||||
|
||||
Assert(gs_current->kind == GROUPING_SET_SIMPLE);
|
||||
|
||||
current_result
|
||||
= list_concat(current_result,
|
||||
list_copy(gs_current->content));
|
||||
|
||||
/* If we are done with making the current group, break */
|
||||
if (--i == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
result = lappend(result, current_result);
|
||||
--curgroup_size;
|
||||
}
|
||||
|
||||
result = lappend(result, NIL);
|
||||
}
|
||||
break;
|
||||
|
||||
case GROUPING_SET_CUBE:
|
||||
{
|
||||
List *cube_list = gs->content;
|
||||
int number_bits = list_length(cube_list);
|
||||
uint32 num_sets;
|
||||
uint32 i;
|
||||
|
||||
/* parser should cap this much lower */
|
||||
Assert(number_bits < 31);
|
||||
|
||||
num_sets = (1U << number_bits);
|
||||
|
||||
for (i = 0; i < num_sets; i++)
|
||||
{
|
||||
List *current_result = NIL;
|
||||
ListCell *lc;
|
||||
uint32 mask = 1U;
|
||||
|
||||
foreach(lc, cube_list)
|
||||
{
|
||||
GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
|
||||
|
||||
Assert(gs_current->kind == GROUPING_SET_SIMPLE);
|
||||
|
||||
if (mask & i)
|
||||
{
|
||||
current_result
|
||||
= list_concat(current_result,
|
||||
list_copy(gs_current->content));
|
||||
}
|
||||
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
result = lappend(result, current_result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case GROUPING_SET_SETS:
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, gs->content)
|
||||
{
|
||||
List *current_result = expand_groupingset_node(lfirst(lc));
|
||||
|
||||
result = list_concat(result, current_result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
cmp_list_len_asc(const void *a, const void *b)
|
||||
{
|
||||
int la = list_length(*(List*const*)a);
|
||||
int lb = list_length(*(List*const*)b);
|
||||
return (la > lb) ? 1 : (la == lb) ? 0 : -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand a groupingSets clause to a flat list of grouping sets.
|
||||
* The returned list is sorted by length, shortest sets first.
|
||||
*
|
||||
* This is mainly for the planner, but we use it here too to do
|
||||
* some consistency checks.
|
||||
*/
|
||||
List *
|
||||
expand_grouping_sets(List *groupingSets, int limit)
|
||||
{
|
||||
List *expanded_groups = NIL;
|
||||
List *result = NIL;
|
||||
double numsets = 1;
|
||||
ListCell *lc;
|
||||
|
||||
if (groupingSets == NIL)
|
||||
return NIL;
|
||||
|
||||
foreach(lc, groupingSets)
|
||||
{
|
||||
List *current_result = NIL;
|
||||
GroupingSet *gs = lfirst(lc);
|
||||
|
||||
current_result = expand_groupingset_node(gs);
|
||||
|
||||
Assert(current_result != NIL);
|
||||
|
||||
numsets *= list_length(current_result);
|
||||
|
||||
if (limit >= 0 && numsets > limit)
|
||||
return NIL;
|
||||
|
||||
expanded_groups = lappend(expanded_groups, current_result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do cartesian product between sublists of expanded_groups.
|
||||
* While at it, remove any duplicate elements from individual
|
||||
* grouping sets (we must NOT change the number of sets though)
|
||||
*/
|
||||
|
||||
foreach(lc, (List *) linitial(expanded_groups))
|
||||
{
|
||||
result = lappend(result, list_union_int(NIL, (List *) lfirst(lc)));
|
||||
}
|
||||
|
||||
for_each_cell(lc, lnext(list_head(expanded_groups)))
|
||||
{
|
||||
List *p = lfirst(lc);
|
||||
List *new_result = NIL;
|
||||
ListCell *lc2;
|
||||
|
||||
foreach(lc2, result)
|
||||
{
|
||||
List *q = lfirst(lc2);
|
||||
ListCell *lc3;
|
||||
|
||||
foreach(lc3, p)
|
||||
{
|
||||
new_result = lappend(new_result,
|
||||
list_union_int(q, (List *) lfirst(lc3)));
|
||||
}
|
||||
}
|
||||
result = new_result;
|
||||
}
|
||||
|
||||
if (list_length(result) > 1)
|
||||
{
|
||||
int result_len = list_length(result);
|
||||
List **buf = palloc(sizeof(List*) * result_len);
|
||||
List **ptr = buf;
|
||||
|
||||
foreach(lc, result)
|
||||
{
|
||||
*ptr++ = lfirst(lc);
|
||||
}
|
||||
|
||||
qsort(buf, result_len, sizeof(List*), cmp_list_len_asc);
|
||||
|
||||
result = NIL;
|
||||
ptr = buf;
|
||||
|
||||
while (result_len-- > 0)
|
||||
result = lappend(result, *ptr++);
|
||||
|
||||
pfree(buf);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_aggregate_argtypes
|
||||
* Identify the specific datatypes passed to an aggregate call.
|
||||
|
@@ -15,6 +15,8 @@
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "miscadmin.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "access/htup_details.h"
|
||||
@@ -43,7 +45,6 @@
|
||||
#include "utils/rel.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
/* Convenience macro for the most common makeNamespaceItem() case */
|
||||
#define makeDefaultNSItem(rte) makeNamespaceItem(rte, true, true, false, true)
|
||||
|
||||
@@ -1725,40 +1726,181 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
|
||||
return target_result;
|
||||
}
|
||||
|
||||
/*
|
||||
* transformGroupClause -
|
||||
* transform a GROUP BY clause
|
||||
/*-------------------------------------------------------------------------
|
||||
* Flatten out parenthesized sublists in grouping lists, and some cases
|
||||
* of nested grouping sets.
|
||||
*
|
||||
* GROUP BY items will be added to the targetlist (as resjunk columns)
|
||||
* if not already present, so the targetlist must be passed by reference.
|
||||
* Inside a grouping set (ROLLUP, CUBE, or GROUPING SETS), we expect the
|
||||
* content to be nested no more than 2 deep: i.e. ROLLUP((a,b),(c,d)) is
|
||||
* ok, but ROLLUP((a,(b,c)),d) is flattened to ((a,b,c),d), which we then
|
||||
* normalize to ((a,b,c),(d)).
|
||||
*
|
||||
* This is also used for window PARTITION BY clauses (which act almost the
|
||||
* same, but are always interpreted per SQL99 rules).
|
||||
* CUBE or ROLLUP can be nested inside GROUPING SETS (but not the reverse),
|
||||
* and we leave that alone if we find it. But if we see GROUPING SETS inside
|
||||
* GROUPING SETS, we can flatten and normalize as follows:
|
||||
* GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g))
|
||||
* becomes
|
||||
* GROUPING SETS ((a), (b,c), (c,d), (e), (f,g))
|
||||
*
|
||||
* This is per the spec's syntax transformations, but these are the only such
|
||||
* transformations we do in parse analysis, so that queries retain the
|
||||
* originally specified grouping set syntax for CUBE and ROLLUP as much as
|
||||
* possible when deparsed. (Full expansion of the result into a list of
|
||||
* grouping sets is left to the planner.)
|
||||
*
|
||||
* When we're done, the resulting list should contain only these possible
|
||||
* elements:
|
||||
* - an expression
|
||||
* - a CUBE or ROLLUP with a list of expressions nested 2 deep
|
||||
* - a GROUPING SET containing any of:
|
||||
* - expression lists
|
||||
* - empty grouping sets
|
||||
* - CUBE or ROLLUP nodes with lists nested 2 deep
|
||||
* The return is a new list, but doesn't deep-copy the old nodes except for
|
||||
* GroupingSet nodes.
|
||||
*
|
||||
* As a side effect, flag whether the list has any GroupingSet nodes.
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
List *
|
||||
transformGroupClause(ParseState *pstate, List *grouplist,
|
||||
List **targetlist, List *sortClause,
|
||||
ParseExprKind exprKind, bool useSQL99)
|
||||
static Node *
|
||||
flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets)
|
||||
{
|
||||
List *result = NIL;
|
||||
ListCell *gl;
|
||||
/* just in case of pathological input */
|
||||
check_stack_depth();
|
||||
|
||||
foreach(gl, grouplist)
|
||||
if (expr == (Node *) NIL)
|
||||
return (Node *) NIL;
|
||||
|
||||
switch (expr->type)
|
||||
{
|
||||
Node *gexpr = (Node *) lfirst(gl);
|
||||
TargetEntry *tle;
|
||||
bool found = false;
|
||||
case T_RowExpr:
|
||||
{
|
||||
RowExpr *r = (RowExpr *) expr;
|
||||
if (r->row_format == COERCE_IMPLICIT_CAST)
|
||||
return flatten_grouping_sets((Node *) r->args,
|
||||
false, NULL);
|
||||
}
|
||||
break;
|
||||
case T_GroupingSet:
|
||||
{
|
||||
GroupingSet *gset = (GroupingSet *) expr;
|
||||
ListCell *l2;
|
||||
List *result_set = NIL;
|
||||
|
||||
if (useSQL99)
|
||||
tle = findTargetlistEntrySQL99(pstate, gexpr,
|
||||
targetlist, exprKind);
|
||||
else
|
||||
tle = findTargetlistEntrySQL92(pstate, gexpr,
|
||||
targetlist, exprKind);
|
||||
if (hasGroupingSets)
|
||||
*hasGroupingSets = true;
|
||||
|
||||
/* Eliminate duplicates (GROUP BY x, x) */
|
||||
if (targetIsInSortList(tle, InvalidOid, result))
|
||||
continue;
|
||||
/*
|
||||
* at the top level, we skip over all empty grouping sets; the
|
||||
* caller can supply the canonical GROUP BY () if nothing is left.
|
||||
*/
|
||||
|
||||
if (toplevel && gset->kind == GROUPING_SET_EMPTY)
|
||||
return (Node *) NIL;
|
||||
|
||||
foreach(l2, gset->content)
|
||||
{
|
||||
Node *n2 = flatten_grouping_sets(lfirst(l2), false, NULL);
|
||||
|
||||
result_set = lappend(result_set, n2);
|
||||
}
|
||||
|
||||
/*
|
||||
* At top level, keep the grouping set node; but if we're in a nested
|
||||
* grouping set, then we need to concat the flattened result into the
|
||||
* outer list if it's simply nested.
|
||||
*/
|
||||
|
||||
if (toplevel || (gset->kind != GROUPING_SET_SETS))
|
||||
{
|
||||
return (Node *) makeGroupingSet(gset->kind, result_set, gset->location);
|
||||
}
|
||||
else
|
||||
return (Node *) result_set;
|
||||
}
|
||||
case T_List:
|
||||
{
|
||||
List *result = NIL;
|
||||
ListCell *l;
|
||||
|
||||
foreach(l, (List *)expr)
|
||||
{
|
||||
Node *n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets);
|
||||
if (n != (Node *) NIL)
|
||||
{
|
||||
if (IsA(n,List))
|
||||
result = list_concat(result, (List *) n);
|
||||
else
|
||||
result = lappend(result, n);
|
||||
}
|
||||
}
|
||||
|
||||
return (Node *) result;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Transform a single expression within a GROUP BY clause or grouping set.
|
||||
*
|
||||
* The expression is added to the targetlist if not already present, and to the
|
||||
* flatresult list (which will become the groupClause) if not already present
|
||||
* there. The sortClause is consulted for operator and sort order hints.
|
||||
*
|
||||
* Returns the ressortgroupref of the expression.
|
||||
*
|
||||
* flatresult reference to flat list of SortGroupClause nodes
|
||||
* seen_local bitmapset of sortgrouprefs already seen at the local level
|
||||
* pstate ParseState
|
||||
* gexpr node to transform
|
||||
* targetlist reference to TargetEntry list
|
||||
* sortClause ORDER BY clause (SortGroupClause nodes)
|
||||
* exprKind expression kind
|
||||
* useSQL99 SQL99 rather than SQL92 syntax
|
||||
* toplevel false if within any grouping set
|
||||
*/
|
||||
static Index
|
||||
transformGroupClauseExpr(List **flatresult, Bitmapset *seen_local,
|
||||
ParseState *pstate, Node *gexpr,
|
||||
List **targetlist, List *sortClause,
|
||||
ParseExprKind exprKind, bool useSQL99, bool toplevel)
|
||||
{
|
||||
TargetEntry *tle;
|
||||
bool found = false;
|
||||
|
||||
if (useSQL99)
|
||||
tle = findTargetlistEntrySQL99(pstate, gexpr,
|
||||
targetlist, exprKind);
|
||||
else
|
||||
tle = findTargetlistEntrySQL92(pstate, gexpr,
|
||||
targetlist, exprKind);
|
||||
|
||||
if (tle->ressortgroupref > 0)
|
||||
{
|
||||
ListCell *sl;
|
||||
|
||||
/*
|
||||
* Eliminate duplicates (GROUP BY x, x) but only at local level.
|
||||
* (Duplicates in grouping sets can affect the number of returned
|
||||
* rows, so can't be dropped indiscriminately.)
|
||||
*
|
||||
* Since we don't care about anything except the sortgroupref,
|
||||
* we can use a bitmapset rather than scanning lists.
|
||||
*/
|
||||
if (bms_is_member(tle->ressortgroupref,seen_local))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If we're already in the flat clause list, we don't need
|
||||
* to consider adding ourselves again.
|
||||
*/
|
||||
found = targetIsInSortList(tle, InvalidOid, *flatresult);
|
||||
if (found)
|
||||
return tle->ressortgroupref;
|
||||
|
||||
/*
|
||||
* If the GROUP BY tlist entry also appears in ORDER BY, copy operator
|
||||
@@ -1770,35 +1912,308 @@ transformGroupClause(ParseState *pstate, List *grouplist,
|
||||
* sort step, and it allows the user to choose the equality semantics
|
||||
* used by GROUP BY, should she be working with a datatype that has
|
||||
* more than one equality operator.
|
||||
*
|
||||
* If we're in a grouping set, though, we force our requested ordering
|
||||
* to be NULLS LAST, because if we have any hope of using a sorted agg
|
||||
* for the job, we're going to be tacking on generated NULL values
|
||||
* after the corresponding groups. If the user demands nulls first,
|
||||
* another sort step is going to be inevitable, but that's the
|
||||
* planner's problem.
|
||||
*/
|
||||
if (tle->ressortgroupref > 0)
|
||||
|
||||
foreach(sl, sortClause)
|
||||
{
|
||||
ListCell *sl;
|
||||
SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
|
||||
|
||||
foreach(sl, sortClause)
|
||||
if (sc->tleSortGroupRef == tle->ressortgroupref)
|
||||
{
|
||||
SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
|
||||
|
||||
if (sc->tleSortGroupRef == tle->ressortgroupref)
|
||||
{
|
||||
result = lappend(result, copyObject(sc));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
SortGroupClause *grpc = copyObject(sc);
|
||||
if (!toplevel)
|
||||
grpc->nulls_first = false;
|
||||
*flatresult = lappend(*flatresult, grpc);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If no match in ORDER BY, just add it to the result using default
|
||||
* sort/group semantics.
|
||||
*/
|
||||
if (!found)
|
||||
result = addTargetToGroupList(pstate, tle,
|
||||
result, *targetlist,
|
||||
exprLocation(gexpr),
|
||||
true);
|
||||
}
|
||||
|
||||
/*
|
||||
* If no match in ORDER BY, just add it to the result using default
|
||||
* sort/group semantics.
|
||||
*/
|
||||
if (!found)
|
||||
*flatresult = addTargetToGroupList(pstate, tle,
|
||||
*flatresult, *targetlist,
|
||||
exprLocation(gexpr),
|
||||
true);
|
||||
|
||||
/*
|
||||
* _something_ must have assigned us a sortgroupref by now...
|
||||
*/
|
||||
|
||||
return tle->ressortgroupref;
|
||||
}
|
||||
|
||||
/*
|
||||
* Transform a list of expressions within a GROUP BY clause or grouping set.
|
||||
*
|
||||
* The list of expressions belongs to a single clause within which duplicates
|
||||
* can be safely eliminated.
|
||||
*
|
||||
* Returns an integer list of ressortgroupref values.
|
||||
*
|
||||
* flatresult reference to flat list of SortGroupClause nodes
|
||||
* pstate ParseState
|
||||
* list nodes to transform
|
||||
* targetlist reference to TargetEntry list
|
||||
* sortClause ORDER BY clause (SortGroupClause nodes)
|
||||
* exprKind expression kind
|
||||
* useSQL99 SQL99 rather than SQL92 syntax
|
||||
* toplevel false if within any grouping set
|
||||
*/
|
||||
static List *
|
||||
transformGroupClauseList(List **flatresult,
|
||||
ParseState *pstate, List *list,
|
||||
List **targetlist, List *sortClause,
|
||||
ParseExprKind exprKind, bool useSQL99, bool toplevel)
|
||||
{
|
||||
Bitmapset *seen_local = NULL;
|
||||
List *result = NIL;
|
||||
ListCell *gl;
|
||||
|
||||
foreach(gl, list)
|
||||
{
|
||||
Node *gexpr = (Node *) lfirst(gl);
|
||||
|
||||
Index ref = transformGroupClauseExpr(flatresult,
|
||||
seen_local,
|
||||
pstate,
|
||||
gexpr,
|
||||
targetlist,
|
||||
sortClause,
|
||||
exprKind,
|
||||
useSQL99,
|
||||
toplevel);
|
||||
if (ref > 0)
|
||||
{
|
||||
seen_local = bms_add_member(seen_local, ref);
|
||||
result = lappend_int(result, ref);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Transform a grouping set and (recursively) its content.
|
||||
*
|
||||
* The grouping set might be a GROUPING SETS node with other grouping sets
|
||||
* inside it, but SETS within SETS have already been flattened out before
|
||||
* reaching here.
|
||||
*
|
||||
* Returns the transformed node, which now contains SIMPLE nodes with lists
|
||||
* of ressortgrouprefs rather than expressions.
|
||||
*
|
||||
* flatresult reference to flat list of SortGroupClause nodes
|
||||
* pstate ParseState
|
||||
* gset grouping set to transform
|
||||
* targetlist reference to TargetEntry list
|
||||
* sortClause ORDER BY clause (SortGroupClause nodes)
|
||||
* exprKind expression kind
|
||||
* useSQL99 SQL99 rather than SQL92 syntax
|
||||
* toplevel false if within any grouping set
|
||||
*/
|
||||
static Node *
|
||||
transformGroupingSet(List **flatresult,
|
||||
ParseState *pstate, GroupingSet *gset,
|
||||
List **targetlist, List *sortClause,
|
||||
ParseExprKind exprKind, bool useSQL99, bool toplevel)
|
||||
{
|
||||
ListCell *gl;
|
||||
List *content = NIL;
|
||||
|
||||
Assert(toplevel || gset->kind != GROUPING_SET_SETS);
|
||||
|
||||
foreach(gl, gset->content)
|
||||
{
|
||||
Node *n = lfirst(gl);
|
||||
|
||||
if (IsA(n, List))
|
||||
{
|
||||
List *l = transformGroupClauseList(flatresult,
|
||||
pstate, (List *) n,
|
||||
targetlist, sortClause,
|
||||
exprKind, useSQL99, false);
|
||||
|
||||
content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
|
||||
l,
|
||||
exprLocation(n)));
|
||||
}
|
||||
else if (IsA(n, GroupingSet))
|
||||
{
|
||||
GroupingSet *gset2 = (GroupingSet *) lfirst(gl);
|
||||
|
||||
content = lappend(content, transformGroupingSet(flatresult,
|
||||
pstate, gset2,
|
||||
targetlist, sortClause,
|
||||
exprKind, useSQL99, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
Index ref = transformGroupClauseExpr(flatresult,
|
||||
NULL,
|
||||
pstate,
|
||||
n,
|
||||
targetlist,
|
||||
sortClause,
|
||||
exprKind,
|
||||
useSQL99,
|
||||
false);
|
||||
|
||||
content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
|
||||
list_make1_int(ref),
|
||||
exprLocation(n)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Arbitrarily cap the size of CUBE, which has exponential growth */
|
||||
if (gset->kind == GROUPING_SET_CUBE)
|
||||
{
|
||||
if (list_length(content) > 12)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_TOO_MANY_COLUMNS),
|
||||
errmsg("CUBE is limited to 12 elements"),
|
||||
parser_errposition(pstate, gset->location)));
|
||||
}
|
||||
|
||||
return (Node *) makeGroupingSet(gset->kind, content, gset->location);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* transformGroupClause -
|
||||
* transform a GROUP BY clause
|
||||
*
|
||||
* GROUP BY items will be added to the targetlist (as resjunk columns)
|
||||
* if not already present, so the targetlist must be passed by reference.
|
||||
*
|
||||
* This is also used for window PARTITION BY clauses (which act almost the
|
||||
* same, but are always interpreted per SQL99 rules).
|
||||
*
|
||||
* Grouping sets make this a lot more complex than it was. Our goal here is
|
||||
* twofold: we make a flat list of SortGroupClause nodes referencing each
|
||||
* distinct expression used for grouping, with those expressions added to the
|
||||
* targetlist if needed. At the same time, we build the groupingSets tree,
|
||||
* which stores only ressortgrouprefs as integer lists inside GroupingSet nodes
|
||||
* (possibly nested, but limited in depth: a GROUPING_SET_SETS node can contain
|
||||
* nested SIMPLE, CUBE or ROLLUP nodes, but not more sets - we flatten that
|
||||
* out; while CUBE and ROLLUP can contain only SIMPLE nodes).
|
||||
*
|
||||
* We skip much of the hard work if there are no grouping sets.
|
||||
*
|
||||
* One subtlety is that the groupClause list can end up empty while the
|
||||
* groupingSets list is not; this happens if there are only empty grouping
|
||||
* sets, or an explicit GROUP BY (). This has the same effect as specifying
|
||||
* aggregates or a HAVING clause with no GROUP BY; the output is one row per
|
||||
* grouping set even if the input is empty.
|
||||
*
|
||||
* Returns the transformed (flat) groupClause.
|
||||
*
|
||||
* pstate ParseState
|
||||
* grouplist clause to transform
|
||||
* groupingSets reference to list to contain the grouping set tree
|
||||
* targetlist reference to TargetEntry list
|
||||
* sortClause ORDER BY clause (SortGroupClause nodes)
|
||||
* exprKind expression kind
|
||||
* useSQL99 SQL99 rather than SQL92 syntax
|
||||
*/
|
||||
List *
|
||||
transformGroupClause(ParseState *pstate, List *grouplist, List **groupingSets,
|
||||
List **targetlist, List *sortClause,
|
||||
ParseExprKind exprKind, bool useSQL99)
|
||||
{
|
||||
List *result = NIL;
|
||||
List *flat_grouplist;
|
||||
List *gsets = NIL;
|
||||
ListCell *gl;
|
||||
bool hasGroupingSets = false;
|
||||
Bitmapset *seen_local = NULL;
|
||||
|
||||
/*
|
||||
* Recursively flatten implicit RowExprs. (Technically this is only
|
||||
* needed for GROUP BY, per the syntax rules for grouping sets, but
|
||||
* we do it anyway.)
|
||||
*/
|
||||
flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist,
|
||||
true,
|
||||
&hasGroupingSets);
|
||||
|
||||
/*
|
||||
* If the list is now empty, but hasGroupingSets is true, it's because
|
||||
* we elided redundant empty grouping sets. Restore a single empty
|
||||
* grouping set to leave a canonical form: GROUP BY ()
|
||||
*/
|
||||
|
||||
if (flat_grouplist == NIL && hasGroupingSets)
|
||||
{
|
||||
flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY,
|
||||
NIL,
|
||||
exprLocation((Node *) grouplist)));
|
||||
}
|
||||
|
||||
foreach(gl, flat_grouplist)
|
||||
{
|
||||
Node *gexpr = (Node *) lfirst(gl);
|
||||
|
||||
if (IsA(gexpr, GroupingSet))
|
||||
{
|
||||
GroupingSet *gset = (GroupingSet *) gexpr;
|
||||
|
||||
switch (gset->kind)
|
||||
{
|
||||
case GROUPING_SET_EMPTY:
|
||||
gsets = lappend(gsets, gset);
|
||||
break;
|
||||
case GROUPING_SET_SIMPLE:
|
||||
/* can't happen */
|
||||
Assert(false);
|
||||
break;
|
||||
case GROUPING_SET_SETS:
|
||||
case GROUPING_SET_CUBE:
|
||||
case GROUPING_SET_ROLLUP:
|
||||
gsets = lappend(gsets,
|
||||
transformGroupingSet(&result,
|
||||
pstate, gset,
|
||||
targetlist, sortClause,
|
||||
exprKind, useSQL99, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Index ref = transformGroupClauseExpr(&result, seen_local,
|
||||
pstate, gexpr,
|
||||
targetlist, sortClause,
|
||||
exprKind, useSQL99, true);
|
||||
|
||||
if (ref > 0)
|
||||
{
|
||||
seen_local = bms_add_member(seen_local, ref);
|
||||
if (hasGroupingSets)
|
||||
gsets = lappend(gsets,
|
||||
makeGroupingSet(GROUPING_SET_SIMPLE,
|
||||
list_make1_int(ref),
|
||||
exprLocation(gexpr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* parser should prevent this */
|
||||
Assert(gsets == NIL || groupingSets != NULL);
|
||||
|
||||
if (groupingSets)
|
||||
*groupingSets = gsets;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1903,6 +2318,7 @@ transformWindowDefinitions(ParseState *pstate,
|
||||
true /* force SQL99 rules */ );
|
||||
partitionClause = transformGroupClause(pstate,
|
||||
windef->partitionClause,
|
||||
NULL,
|
||||
targetlist,
|
||||
orderClause,
|
||||
EXPR_KIND_WINDOW_PARTITION,
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
{
|
||||
|
Reference in New Issue
Block a user