{
Bitmapset *rels_used = NULL;
PlanState *ps;
+ ListCell *lc;
/* Set up ExplainState fields associated with this plan tree */
Assert(queryDesc->plannedstmt != NULL);
es->deparse_cxt = deparse_context_for_plan_tree(queryDesc->plannedstmt,
es->rtable_names);
es->printed_subplans = NULL;
+ es->rtable_size = list_length(es->rtable);
+ foreach(lc, es->rtable)
+ {
+ RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
+
+ if (rte->rtekind == RTE_GROUP)
+ {
+ es->rtable_size--;
+ break;
+ }
+ }
/*
* Sometimes we mark a Gather node as "invisible", which means that it's
context = set_deparse_context_plan(es->deparse_cxt,
plan,
ancestors);
- useprefix = list_length(es->rtable) > 1;
+ useprefix = es->rtable_size > 1;
/* Deparse each result column (we now include resjunk ones) */
foreach(lc, plan->targetlist)
{
bool useprefix;
- useprefix = (list_length(es->rtable) > 1 || es->verbose);
+ useprefix = (es->rtable_size > 1 || es->verbose);
show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
}
context = set_deparse_context_plan(es->deparse_cxt,
planstate->plan,
ancestors);
- useprefix = (list_length(es->rtable) > 1 || es->verbose);
+ useprefix = (es->rtable_size > 1 || es->verbose);
ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
context = set_deparse_context_plan(es->deparse_cxt,
plan,
ancestors);
- useprefix = (list_length(es->rtable) > 1 || es->verbose);
+ useprefix = (es->rtable_size > 1 || es->verbose);
for (keyno = 0; keyno < nkeys; keyno++)
{
context = set_deparse_context_plan(es->deparse_cxt,
planstate->plan,
ancestors);
- useprefix = list_length(es->rtable) > 1;
+ useprefix = es->rtable_size > 1;
/* Get the tablesample method name */
method_name = get_func_name(tsc->tsmhandler);
* It's hard to imagine having a memoize node with fewer than 2 RTEs, but
* let's just keep the same useprefix logic as elsewhere in this file.
*/
- useprefix = list_length(es->rtable) > 1 || es->verbose;
+ useprefix = es->rtable_size > 1 || es->verbose;
/* Set up deparsing context */
context = set_deparse_context_plan(es->deparse_cxt,
case RTE_RESULT:
/* nothing to do */
break;
+ case RTE_GROUP:
+ if (!(flags & QTW_IGNORE_GROUPEXPRS))
+ if (WALK(rte->groupexprs))
+ return true;
+ break;
}
if (WALK(rte->securityQuals))
case RTE_RESULT:
/* nothing to do */
break;
+ case RTE_GROUP:
+ if (!(flags & QTW_IGNORE_GROUPEXPRS))
+ MUTATE(newrte->groupexprs, rte->groupexprs, List *);
+ else
+ {
+ /* else, copy grouping exprs as-is */
+ newrte->groupexprs = copyObject(rte->groupexprs);
+ }
+ break;
}
MUTATE(newrte->securityQuals, rte->securityQuals, List *);
newrt = lappend(newrt, newrte);
case RTE_RESULT:
/* no extra fields */
break;
+ case RTE_GROUP:
+ WRITE_NODE_FIELD(groupexprs);
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
break;
printf("%d\t%s\t[result]",
i, rte->eref->aliasname);
break;
+ case RTE_GROUP:
+ printf("%d\t%s\t[group]",
+ i, rte->eref->aliasname);
+ break;
default:
printf("%d\t%s\t[unknown rtekind]",
i, rte->eref->aliasname);
case RTE_RESULT:
/* no extra fields */
break;
+ case RTE_GROUP:
+ READ_NODE_FIELD(groupexprs);
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d",
(int) local_node->rtekind);
case RTE_RESULT:
/* RESULT RTEs, in themselves, are no problem. */
break;
+ case RTE_GROUP:
+ /* Shouldn't happen; we're only considering baserels here. */
+ Assert(false);
+ return;
}
/*
#define EXPRKIND_ARBITER_ELEM 10
#define EXPRKIND_TABLEFUNC 11
#define EXPRKIND_TABLEFUNC_LATERAL 12
+#define EXPRKIND_GROUPEXPR 13
/*
* Data specific to grouping sets
*/
root->hasJoinRTEs = false;
root->hasLateralRTEs = false;
+ root->group_rtindex = 0;
hasOuterJoins = false;
hasResultRTEs = false;
foreach(l, parse->rtable)
case RTE_RESULT:
hasResultRTEs = true;
break;
+ case RTE_GROUP:
+ Assert(parse->hasGroupRTE);
+ root->group_rtindex = list_cell_number(parse->rtable, l) + 1;
+ break;
default:
/* No work here for other RTE types */
break;
preprocess_expression(root, (Node *) parse->targetList,
EXPRKIND_TARGET);
- /* Constant-folding might have removed all set-returning functions */
- if (parse->hasTargetSRFs)
- parse->hasTargetSRFs = expression_returns_set((Node *) parse->targetList);
-
newWithCheckOptions = NIL;
foreach(l, parse->withCheckOptions)
{
rte->values_lists = (List *)
preprocess_expression(root, (Node *) rte->values_lists, kind);
}
+ else if (rte->rtekind == RTE_GROUP)
+ {
+ /* Preprocess the groupexprs list fully */
+ rte->groupexprs = (List *)
+ preprocess_expression(root, (Node *) rte->groupexprs,
+ EXPRKIND_GROUPEXPR);
+ }
/*
* Process each element of the securityQuals list as if it were a
}
}
+ /*
+ * Replace any Vars in the subquery's targetlist and havingQual that
+ * reference GROUP outputs with the underlying grouping expressions.
+ *
+ * Note that we need to perform this replacement after we've preprocessed
+ * the grouping expressions. This is to ensure that there is only one
+ * instance of SubPlan for each SubLink contained within the grouping
+ * expressions.
+ */
+ if (parse->hasGroupRTE)
+ {
+ parse->targetList = (List *)
+ flatten_group_exprs(root, root->parse, (Node *) parse->targetList);
+ parse->havingQual =
+ flatten_group_exprs(root, root->parse, parse->havingQual);
+ }
+
+ /* Constant-folding might have removed all set-returning functions */
+ if (parse->hasTargetSRFs)
+ parse->hasTargetSRFs = expression_returns_set((Node *) parse->targetList);
+
/*
* In some cases we may want to transfer a HAVING clause into WHERE. We
* cannot do so if the HAVING clause contains aggregates (obviously) or
* don't emit a bogus aggregated row. (This could be done better, but it
* seems not worth optimizing.)
*
+ * Note that a HAVING clause may contain expressions that are not fully
+ * preprocessed. This can happen if these expressions are part of
+ * grouping items. In such cases, they are replaced with GROUP Vars in
+ * the parser and then replaced back after we've done with expression
+ * preprocessing on havingQual. This is not an issue if the clause
+ * remains in HAVING, because these expressions will be matched to lower
+ * target items in setrefs.c. However, if the clause is moved or copied
+ * into WHERE, we need to ensure that these expressions are fully
+ * preprocessed.
+ *
* Note that both havingQual and parse->jointree->quals are in
* implicitly-ANDed-list form at this point, even though they are declared
* as Node *.
}
else if (parse->groupClause && !parse->groupingSets)
{
- /* move it to WHERE */
+ Node *whereclause;
+
+ /* Preprocess the HAVING clause fully */
+ whereclause = preprocess_expression(root, havingclause,
+ EXPRKIND_QUAL);
+ /* ... and move it to WHERE */
parse->jointree->quals = (Node *)
- lappend((List *) parse->jointree->quals, havingclause);
+ list_concat((List *) parse->jointree->quals,
+ (List *) whereclause);
}
else
{
- /* put a copy in WHERE, keep it in HAVING */
+ Node *whereclause;
+
+ /* Preprocess the HAVING clause fully */
+ whereclause = preprocess_expression(root, copyObject(havingclause),
+ EXPRKIND_QUAL);
+ /* ... and put a copy in WHERE */
parse->jointree->quals = (Node *)
- lappend((List *) parse->jointree->quals,
- copyObject(havingclause));
+ list_concat((List *) parse->jointree->quals,
+ (List *) whereclause);
+ /* ... and also keep it in HAVING */
newHaving = lappend(newHaving, havingclause);
}
}
newrte->coltypes = NIL;
newrte->coltypmods = NIL;
newrte->colcollations = NIL;
+ newrte->groupexprs = NIL;
newrte->securityQuals = NIL;
glob->finalrtable = lappend(glob->finalrtable, newrte);
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
+ case RTE_GROUP:
/* these can't contain any lateral references */
break;
}
}
/*
- * Replace references in the joinaliasvars lists of join RTEs.
+ * Replace references in the joinaliasvars lists of join RTEs and the
+ * groupexprs list of group RTE.
*/
foreach(lc, parse->rtable)
{
otherrte->joinaliasvars = (List *)
pullup_replace_vars((Node *) otherrte->joinaliasvars,
rvcontext);
+ else if (otherrte->rtekind == RTE_GROUP)
+ otherrte->groupexprs = (List *)
+ pullup_replace_vars((Node *) otherrte->groupexprs,
+ rvcontext);
}
}
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
+ case RTE_GROUP:
/* these shouldn't be marked LATERAL */
Assert(false);
break;
pull_var_clause_context *context);
static Node *flatten_join_alias_vars_mutator(Node *node,
flatten_join_alias_vars_context *context);
+static Node *flatten_group_exprs_mutator(Node *node,
+ flatten_join_alias_vars_context *context);
static Node *add_nullingrels_if_needed(PlannerInfo *root, Node *newnode,
Var *oldvar);
static bool is_standard_join_alias_expression(Node *newnode, Var *oldvar);
}
/* Already-planned tree not supported */
Assert(!IsA(node, SubPlan));
+ Assert(!IsA(node, AlternativeSubPlan));
/* Shouldn't need to handle these planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, PlaceHolderInfo));
(void *) context);
}
+/*
+ * flatten_group_exprs
+ * Replace Vars that reference GROUP outputs with the underlying grouping
+ * expressions.
+ */
+Node *
+flatten_group_exprs(PlannerInfo *root, Query *query, Node *node)
+{
+ flatten_join_alias_vars_context context;
+
+ /*
+ * We do not expect this to be applied to the whole Query, only to
+ * expressions or LATERAL subqueries. Hence, if the top node is a Query,
+ * it's okay to immediately increment sublevels_up.
+ */
+ Assert(node != (Node *) query);
+
+ context.root = root;
+ context.query = query;
+ context.sublevels_up = 0;
+ /* flag whether grouping expressions could possibly contain SubLinks */
+ context.possible_sublink = query->hasSubLinks;
+ /* if hasSubLinks is already true, no need to work hard */
+ context.inserted_sublink = query->hasSubLinks;
+
+ return flatten_group_exprs_mutator(node, &context);
+}
+
+static Node *
+flatten_group_exprs_mutator(Node *node,
+ flatten_join_alias_vars_context *context)
+{
+ if (node == NULL)
+ return NULL;
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+ RangeTblEntry *rte;
+ Node *newvar;
+
+ /* No change unless Var belongs to the GROUP of the target level */
+ if (var->varlevelsup != context->sublevels_up)
+ return node; /* no need to copy, really */
+ rte = rt_fetch(var->varno, context->query->rtable);
+ if (rte->rtekind != RTE_GROUP)
+ return node;
+
+ /* Expand group exprs reference */
+ Assert(var->varattno > 0);
+ newvar = (Node *) list_nth(rte->groupexprs, var->varattno - 1);
+ Assert(newvar != NULL);
+ newvar = copyObject(newvar);
+
+ /*
+ * If we are expanding an expr carried down from an upper query, must
+ * adjust its varlevelsup fields.
+ */
+ if (context->sublevels_up != 0)
+ IncrementVarSublevelsUp(newvar, context->sublevels_up, 0);
+
+ /* Preserve original Var's location, if possible */
+ if (IsA(newvar, Var))
+ ((Var *) newvar)->location = var->location;
+
+ /* Detect if we are adding a sublink to query */
+ if (context->possible_sublink && !context->inserted_sublink)
+ context->inserted_sublink = checkExprHasSubLink(newvar);
+
+ return newvar;
+ }
+
+ 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; there are no grouped vars there. But we should check
+ * direct arguments as though they weren't in an aggregate.
+ */
+ agg = copyObject(agg);
+ agg->aggdirectargs = (List *)
+ flatten_group_exprs_mutator((Node *) agg->aggdirectargs, context);
+
+ return (Node *) agg;
+ }
+
+ /*
+ * We can skip recursing into aggregates of higher levels altogether,
+ * since they could not possibly contain Vars 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 node;
+ }
+
+ if (IsA(node, GroupingFunc))
+ {
+ GroupingFunc *grp = (GroupingFunc *) node;
+
+ /*
+ * If we find a GroupingFunc node of the original or higher level, do
+ * not recurse into its arguments; there are no grouped vars there.
+ */
+ if ((int) grp->agglevelsup >= context->sublevels_up)
+ return node;
+ }
+
+ if (IsA(node, Query))
+ {
+ /* Recurse into RTE subquery or not-yet-planned sublink subquery */
+ Query *newnode;
+ bool save_inserted_sublink;
+
+ context->sublevels_up++;
+ save_inserted_sublink = context->inserted_sublink;
+ context->inserted_sublink = ((Query *) node)->hasSubLinks;
+ newnode = query_tree_mutator((Query *) node,
+ flatten_group_exprs_mutator,
+ (void *) context,
+ QTW_IGNORE_GROUPEXPRS);
+ newnode->hasSubLinks |= context->inserted_sublink;
+ context->inserted_sublink = save_inserted_sublink;
+ context->sublevels_up--;
+ return (Node *) newnode;
+ }
+
+ return expression_tree_mutator(node, flatten_group_exprs_mutator,
+ (void *) context);
+}
+
/*
* Add oldvar's varnullingrels, if any, to a flattened join alias expression.
* The newnode has been copied, so we can modify it freely.
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
bool hasJoinRTEs;
List *groupClauses;
List *groupClauseCommonVars;
+ List *gset_common;
bool have_non_var_grouping;
List **func_grouped_rels;
int sublevels_up;
bool in_agg_direct_args;
-} check_ungrouped_columns_context;
+} substitute_grouped_columns_context;
static int check_agg_arguments(ParseState *pstate,
List *directargs,
Expr *filter);
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, List *groupClauseCommonVars,
- bool have_non_var_grouping,
- List **func_grouped_rels);
-static bool check_ungrouped_columns_walker(Node *node,
- check_ungrouped_columns_context *context);
+static Node *substitute_grouped_columns(Node *node, ParseState *pstate, Query *qry,
+ List *groupClauses, List *groupClauseCommonVars,
+ List *gset_common,
+ bool have_non_var_grouping,
+ List **func_grouped_rels);
+static Node *substitute_grouped_columns_mutator(Node *node,
+ substitute_grouped_columns_context *context);
static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
List *groupClauses, bool hasJoinRTEs,
bool have_non_var_grouping);
static bool finalize_grouping_exprs_walker(Node *node,
- check_ungrouped_columns_context *context);
+ substitute_grouped_columns_context *context);
+static Var *buildGroupedVar(int attnum, Index ressortgroupref,
+ substitute_grouped_columns_context *context);
static void check_agglevels_and_constraints(ParseState *pstate, Node *expr);
static List *expand_groupingset_node(GroupingSet *gs);
static Node *make_agg_arg(Oid argtype, Oid argcollation);
/*
* parseCheckAggregates
- * Check for aggregates where they shouldn't be and improper grouping.
+ * Check for aggregates where they shouldn't be and improper grouping, and
+ * replace grouped variables in the targetlist and HAVING clause with Vars
+ * that reference the RTE_GROUP RTE.
* This function should be called after the target list and qualifications
* are finalized.
*
/*
* Build a list of the acceptable GROUP BY expressions for use by
- * check_ungrouped_columns().
+ * substitute_grouped_columns().
*
* We get the TLE, not just the expr, because GROUPING wants to know the
* sortgroupref.
}
/*
- * Check the targetlist and HAVING clause for ungrouped variables.
+ * If there are any acceptable GROUP BY expressions, build an RTE and
+ * nsitem for the result of the grouping step.
+ */
+ if (groupClauses)
+ {
+ pstate->p_grouping_nsitem =
+ addRangeTableEntryForGroup(pstate, groupClauses);
+
+ /* Set qry->rtable again in case it was previously NIL */
+ qry->rtable = pstate->p_rtable;
+ /* Mark the Query as having RTE_GROUP RTE */
+ qry->hasGroupRTE = true;
+ }
+
+ /*
+ * Replace grouped variables in the targetlist and HAVING clause with Vars
+ * that reference the RTE_GROUP RTE. Emit an error message if we find any
+ * ungrouped variables.
*
* Note: because we check resjunk tlist elements as well as regular ones,
* this will also find ungrouped variables that came from ORDER BY and
have_non_var_grouping);
if (hasJoinRTEs)
clause = flatten_join_alias_vars(NULL, qry, clause);
- check_ungrouped_columns(clause, pstate, qry,
- groupClauses, groupClauseCommonVars,
- have_non_var_grouping,
- &func_grouped_rels);
+ qry->targetList = (List *)
+ substitute_grouped_columns(clause, pstate, qry,
+ groupClauses, groupClauseCommonVars,
+ gset_common,
+ have_non_var_grouping,
+ &func_grouped_rels);
clause = (Node *) qry->havingQual;
finalize_grouping_exprs(clause, pstate, qry,
have_non_var_grouping);
if (hasJoinRTEs)
clause = flatten_join_alias_vars(NULL, qry, clause);
- check_ungrouped_columns(clause, pstate, qry,
- groupClauses, groupClauseCommonVars,
- have_non_var_grouping,
- &func_grouped_rels);
+ qry->havingQual =
+ substitute_grouped_columns(clause, pstate, qry,
+ groupClauses, groupClauseCommonVars,
+ gset_common,
+ have_non_var_grouping,
+ &func_grouped_rels);
/*
* Per spec, aggregates can't appear in a recursive term.
}
/*
- * check_ungrouped_columns -
- * Scan the given expression tree for ungrouped variables (variables
- * that are not listed in the groupClauses list and are not within
- * the arguments of aggregate functions). Emit a suitable error message
- * if any are found.
+ * substitute_grouped_columns -
+ * Scan the given expression tree for grouped variables (variables that
+ * are listed in the groupClauses list) and replace them with Vars that
+ * reference the RTE_GROUP RTE. Emit a suitable error message if any
+ * ungrouped variables (variables that are not listed in the groupClauses
+ * list and are not within the arguments of aggregate functions) are
+ * found.
*
* NOTE: we assume that the given clause has been transformed suitably for
- * parser output. This means we can use expression_tree_walker.
+ * parser output. This means we can use expression_tree_mutator.
*
* NOTE: we recognize grouping expressions in the main query, but only
* grouping Vars in subqueries. For example, this will be rejected,
* This appears to require a whole custom version of equal(), which is
* way more pain than the feature seems worth.
*/
-static void
-check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
- List *groupClauses, List *groupClauseCommonVars,
- bool have_non_var_grouping,
- List **func_grouped_rels)
+static Node *
+substitute_grouped_columns(Node *node, ParseState *pstate, Query *qry,
+ List *groupClauses, List *groupClauseCommonVars,
+ List *gset_common,
+ bool have_non_var_grouping,
+ List **func_grouped_rels)
{
- check_ungrouped_columns_context context;
+ substitute_grouped_columns_context context;
context.pstate = pstate;
context.qry = qry;
context.hasJoinRTEs = false; /* assume caller flattened join Vars */
context.groupClauses = groupClauses;
context.groupClauseCommonVars = groupClauseCommonVars;
+ context.gset_common = gset_common;
context.have_non_var_grouping = have_non_var_grouping;
context.func_grouped_rels = func_grouped_rels;
context.sublevels_up = 0;
context.in_agg_direct_args = false;
- check_ungrouped_columns_walker(node, &context);
+ return substitute_grouped_columns_mutator(node, &context);
}
-static bool
-check_ungrouped_columns_walker(Node *node,
- check_ungrouped_columns_context *context)
+static Node *
+substitute_grouped_columns_mutator(Node *node,
+ substitute_grouped_columns_context *context)
{
ListCell *gl;
if (node == NULL)
- return false;
+ return NULL;
if (IsA(node, Const) ||
IsA(node, Param))
- return false; /* constants are always acceptable */
+ return node; /* constants are always acceptable */
if (IsA(node, Aggref))
{
/*
* If we find an aggregate call of the original level, do not
* recurse into its normal arguments, ORDER BY arguments, or
- * filter; ungrouped vars there are not an error. But we should
- * check direct arguments as though they weren't in an aggregate.
- * We set a special flag in the context to help produce a useful
+ * filter; grouped vars there do not need to be replaced and
+ * ungrouped vars there are not an error. But we should check
+ * direct arguments as though they weren't in an aggregate. We
+ * set a special flag in the context to help produce a useful
* error message for ungrouped vars in direct arguments.
*/
- bool result;
+ agg = copyObject(agg);
Assert(!context->in_agg_direct_args);
context->in_agg_direct_args = true;
- result = check_ungrouped_columns_walker((Node *) agg->aggdirectargs,
- context);
+ agg->aggdirectargs = (List *)
+ substitute_grouped_columns_mutator((Node *) agg->aggdirectargs,
+ context);
context->in_agg_direct_args = false;
- return result;
+ return (Node *) agg;
}
/*
* levels, however.
*/
if ((int) agg->agglevelsup > context->sublevels_up)
- return false;
+ return node;
}
if (IsA(node, GroupingFunc))
/* handled GroupingFunc separately, no need to recheck at this level */
if ((int) grp->agglevelsup >= context->sublevels_up)
- return false;
+ return node;
}
/*
*/
if (context->have_non_var_grouping && context->sublevels_up == 0)
{
+ int attnum = 0;
+
foreach(gl, context->groupClauses)
{
- TargetEntry *tle = lfirst(gl);
+ TargetEntry *tle = (TargetEntry *) lfirst(gl);
+ attnum++;
if (equal(node, tle->expr))
- return false; /* acceptable, do not descend more */
+ {
+ /* acceptable, replace it with a GROUP Var */
+ return (Node *) buildGroupedVar(attnum,
+ tle->ressortgroupref,
+ context);
+ }
}
}
char *attname;
if (var->varlevelsup != context->sublevels_up)
- return false; /* it's not local to my query, ignore */
+ return node; /* it's not local to my query, ignore */
/*
* Check for a match, if we didn't do it above.
*/
if (!context->have_non_var_grouping || context->sublevels_up != 0)
{
+ int attnum = 0;
+
foreach(gl, context->groupClauses)
{
- Var *gvar = (Var *) ((TargetEntry *) lfirst(gl))->expr;
+ TargetEntry *tle = (TargetEntry *) lfirst(gl);
+ Var *gvar = (Var *) tle->expr;
+ attnum++;
if (IsA(gvar, Var) &&
gvar->varno == var->varno &&
gvar->varattno == var->varattno &&
gvar->varlevelsup == 0)
- return false; /* acceptable, we're okay */
+ {
+ /* acceptable, replace it with a GROUP Var */
+ return (Node *) buildGroupedVar(attnum,
+ tle->ressortgroupref,
+ context);
+ }
}
}
* the constraintDeps list.
*/
if (list_member_int(*context->func_grouped_rels, var->varno))
- return false; /* previously proven acceptable */
+ return node; /* previously proven acceptable */
Assert(var->varno > 0 &&
(int) var->varno <= list_length(context->pstate->p_rtable));
{
*context->func_grouped_rels =
lappend_int(*context->func_grouped_rels, var->varno);
- return false; /* acceptable */
+ return node; /* acceptable */
}
}
if (IsA(node, Query))
{
/* Recurse into subselects */
- bool result;
+ Query *newnode;
context->sublevels_up++;
- result = query_tree_walker((Query *) node,
- check_ungrouped_columns_walker,
- (void *) context,
- 0);
+ newnode = query_tree_mutator((Query *) node,
+ substitute_grouped_columns_mutator,
+ (void *) context,
+ 0);
context->sublevels_up--;
- return result;
+ return (Node *) newnode;
}
- return expression_tree_walker(node, check_ungrouped_columns_walker,
- (void *) context);
+ return expression_tree_mutator(node, substitute_grouped_columns_mutator,
+ (void *) context);
}
/*
* 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
+ * This is split out from substitute_grouped_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
+ * substitute_grouped_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.
*/
List *groupClauses, bool hasJoinRTEs,
bool have_non_var_grouping)
{
- check_ungrouped_columns_context context;
+ substitute_grouped_columns_context context;
context.pstate = pstate;
context.qry = qry;
context.hasJoinRTEs = hasJoinRTEs;
context.groupClauses = groupClauses;
context.groupClauseCommonVars = NIL;
+ context.gset_common = NIL;
context.have_non_var_grouping = have_non_var_grouping;
context.func_grouped_rels = NULL;
context.sublevels_up = 0;
static bool
finalize_grouping_exprs_walker(Node *node,
- check_ungrouped_columns_context *context)
+ substitute_grouped_columns_context *context)
{
ListCell *gl;
(void *) context);
}
+/*
+ * buildGroupedVar -
+ * build a Var node that references the RTE_GROUP RTE
+ */
+static Var *
+buildGroupedVar(int attnum, Index ressortgroupref,
+ substitute_grouped_columns_context *context)
+{
+ Var *var;
+ ParseNamespaceItem *grouping_nsitem = context->pstate->p_grouping_nsitem;
+ ParseNamespaceColumn *nscol = grouping_nsitem->p_nscolumns + attnum - 1;
+
+ Assert(nscol->p_varno == grouping_nsitem->p_rtindex);
+ Assert(nscol->p_varattno == attnum);
+ var = makeVar(nscol->p_varno,
+ nscol->p_varattno,
+ nscol->p_vartype,
+ nscol->p_vartypmod,
+ nscol->p_varcollid,
+ context->sublevels_up);
+ /* makeVar doesn't offer parameters for these, so set by hand: */
+ var->varnosyn = nscol->p_varnosyn;
+ var->varattnosyn = nscol->p_varattnosyn;
+
+ if (context->qry->groupingSets &&
+ !list_member_int(context->gset_common, ressortgroupref))
+ var->varnullingrels =
+ bms_add_member(var->varnullingrels, grouping_nsitem->p_rtindex);
+
+ return var;
+}
+
/*
* Given a GroupingSet node, expand it and return a list of lists.
tupdesc);
}
+/*
+ * Add an entry for grouping step to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
+ */
+ParseNamespaceItem *
+addRangeTableEntryForGroup(ParseState *pstate,
+ List *groupClauses)
+{
+ RangeTblEntry *rte = makeNode(RangeTblEntry);
+ Alias *eref;
+ List *groupexprs;
+ List *coltypes,
+ *coltypmods,
+ *colcollations;
+ ListCell *lc;
+ ParseNamespaceItem *nsitem;
+
+ Assert(pstate != NULL);
+
+ rte->rtekind = RTE_GROUP;
+ rte->alias = NULL;
+
+ eref = makeAlias("*GROUP*", NIL);
+
+ /* fill in any unspecified alias columns, and extract column type info */
+ groupexprs = NIL;
+ coltypes = coltypmods = colcollations = NIL;
+ foreach(lc, groupClauses)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc);
+ char *colname = te->resname ? pstrdup(te->resname) : "?column?";
+
+ eref->colnames = lappend(eref->colnames, makeString(colname));
+
+ groupexprs = lappend(groupexprs, copyObject(te->expr));
+
+ coltypes = lappend_oid(coltypes,
+ exprType((Node *) te->expr));
+ coltypmods = lappend_int(coltypmods,
+ exprTypmod((Node *) te->expr));
+ colcollations = lappend_oid(colcollations,
+ exprCollation((Node *) te->expr));
+ }
+
+ rte->eref = eref;
+ rte->groupexprs = groupexprs;
+
+ /*
+ * Set flags.
+ *
+ * The grouping step is never checked for access rights, so no need to
+ * perform addRTEPermissionInfo().
+ */
+ rte->lateral = false;
+ rte->inFromCl = false;
+
+ /*
+ * Add completed RTE to pstate's range table list, so that we know its
+ * index. But we don't add it to the join list --- caller must do that if
+ * appropriate.
+ */
+ pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+ /*
+ * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+ * list --- caller must do that if appropriate.
+ */
+ nsitem = buildNSItemFromLists(rte, list_length(pstate->p_rtable),
+ coltypes, coltypmods, colcollations);
+
+ return nsitem;
+}
+
/*
* Has the specified refname been selected FOR UPDATE/FOR SHARE?
}
break;
case RTE_RESULT:
+ case RTE_GROUP:
/* These expose no columns, so nothing to do */
break;
default:
case RTE_TABLEFUNC:
case RTE_VALUES:
case RTE_CTE:
+ case RTE_GROUP:
/*
- * Subselect, Table Functions, Values, CTE RTEs never have dropped
- * columns
+ * Subselect, Table Functions, Values, CTE, GROUP RTEs never have
+ * dropped columns
*/
result = false;
break;
tle->resorigcol = ste->resorigcol;
}
break;
+ case RTE_GROUP:
+ /* We couldn't get here: the RTE_GROUP RTE has not been added */
+ break;
}
}
/* else fall through to inspect the expression */
}
break;
+ case RTE_GROUP:
+
+ /*
+ * We couldn't get here: the RTE_GROUP RTE has not been added.
+ */
+ break;
}
/*
{
deparse_context context;
deparse_namespace dpns;
+ int rtable_size;
/* Guard against excessively long or deeply-nested queries */
CHECK_FOR_INTERRUPTS();
check_stack_depth();
+ rtable_size = query->hasGroupRTE ?
+ list_length(query->rtable) - 1 :
+ list_length(query->rtable);
+
+ /*
+ * Replace any Vars in the query's targetlist and havingQual that
+ * reference GROUP outputs with the underlying grouping expressions.
+ */
+ if (query->hasGroupRTE)
+ {
+ query->targetList = (List *)
+ flatten_group_exprs(NULL, query, (Node *) query->targetList);
+ query->havingQual =
+ flatten_group_exprs(NULL, query, query->havingQual);
+ }
+
/*
* Before we begin to examine the query, acquire locks on referenced
* relations, and fix up deleted columns in JOIN RTEs. This ensures
context.targetList = NIL;
context.windowClause = NIL;
context.varprefix = (parentnamespace != NIL ||
- list_length(query->rtable) != 1);
+ rtable_size != 1);
context.prettyFlags = prettyFlags;
context.wrapColumn = wrapColumn;
context.indentLevel = startIndent;
}
}
break;
+ case RTE_GROUP:
+
+ /*
+ * We couldn't get here: any Vars that reference the RTE_GROUP RTE
+ * should have been replaced with the underlying grouping
+ * expressions.
+ */
+ break;
}
/*
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202409041
+#define CATALOG_VERSION_NO 202409101
#endif
List *deparse_cxt; /* context list for deparsing expressions */
Bitmapset *printed_subplans; /* ids of SubPlans we've printed */
bool hide_workers; /* set if we find an invisible Gather */
+ int rtable_size; /* length of rtable excluding the RTE_GROUP
+ * entry */
/* state related to the current plan node */
ExplainWorkersState *workers_state; /* needed if parallel plan */
} ExplainState;
#define QTW_DONT_COPY_QUERY 0x40 /* do not copy top Query */
#define QTW_EXAMINE_SORTGROUP 0x80 /* include SortGroupClause lists */
+#define QTW_IGNORE_GROUPEXPRS 0x100 /* GROUP expressions list */
+
/* callback function for check_functions_in_node */
typedef bool (*check_function_callback) (Oid func_id, void *context);
bool hasForUpdate pg_node_attr(query_jumble_ignore);
/* rewriter has applied some RLS policy */
bool hasRowSecurity pg_node_attr(query_jumble_ignore);
+ /* parser has added an RTE_GROUP RTE */
+ bool hasGroupRTE pg_node_attr(query_jumble_ignore);
/* is a RETURN statement */
bool isReturn pg_node_attr(query_jumble_ignore);
RTE_RESULT, /* RTE represents an empty FROM clause; such
* RTEs are added by the planner, they're not
* present during parsing or rewriting */
+ RTE_GROUP, /* the grouping step */
} RTEKind;
typedef struct RangeTblEntry
/* estimated or actual from caller */
Cardinality enrtuples pg_node_attr(query_jumble_ignore);
+ /*
+ * Fields valid for a GROUP RTE (else NIL):
+ */
+ /* list of grouping expressions */
+ List *groupexprs pg_node_attr(query_jumble_ignore);
+
/*
* Fields valid in all RTEs:
*/
/* true if planning a recursive WITH item */
bool hasRecursion;
+ /*
+ * The rangetable index for the RTE_GROUP RTE, or 0 if there is no
+ * RTE_GROUP RTE.
+ */
+ int group_rtindex;
+
/*
* Information about aggregates. Filled by preprocess_aggrefs().
*/
extern int locate_var_of_level(Node *node, int levelsup);
extern List *pull_var_clause(Node *node, int flags);
extern Node *flatten_join_alias_vars(PlannerInfo *root, Query *query, Node *node);
+extern Node *flatten_group_exprs(PlannerInfo *root, Query *query, Node *node);
#endif /* OPTIMIZER_H */
*
* p_target_nsitem: target relation's ParseNamespaceItem.
*
+ * p_grouping_nsitem: the ParseNamespaceItem that represents the grouping step.
+ *
* p_is_insert: true to process assignment expressions like INSERT, false
* to process them like UPDATE. (Note this can change intra-statement, for
* cases like INSERT ON CONFLICT UPDATE.)
CommonTableExpr *p_parent_cte; /* this query's containing CTE */
Relation p_target_relation; /* INSERT/UPDATE/DELETE/MERGE target rel */
ParseNamespaceItem *p_target_nsitem; /* target rel's NSItem, or NULL */
+ ParseNamespaceItem *p_grouping_nsitem; /* NSItem for grouping, or NULL */
bool p_is_insert; /* process assignment like INSERT not UPDATE */
List *p_windowdefs; /* raw representations of window clauses */
ParseExprKind p_expr_kind; /* what kind of expression we're parsing */
extern ParseNamespaceItem *addRangeTableEntryForENR(ParseState *pstate,
RangeVar *rv,
bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForGroup(ParseState *pstate,
+ List *groupClauses);
extern RTEPermissionInfo *addRTEPermissionInfo(List **rteperminfos,
RangeTblEntry *rte);
extern RTEPermissionInfo *getRTEPermissionInfo(List *rteperminfos,
0
(1 row)
+-- test handling of subqueries in grouping sets
+create temp table gstest5(id integer primary key, v integer);
+insert into gstest5 select i, i from generate_series(1,5)i;
+explain (verbose, costs off)
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s
+from gstest5 t1
+group by grouping sets(v, s)
+order by case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end
+ nulls first;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: (GROUPING((SubPlan 1))), ((SubPlan 3)), (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END), t1.v
+ Sort Key: (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END) NULLS FIRST
+ -> HashAggregate
+ Output: GROUPING((SubPlan 1)), ((SubPlan 3)), CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END, t1.v
+ Hash Key: t1.v
+ Hash Key: (SubPlan 3)
+ -> Seq Scan on pg_temp.gstest5 t1
+ Output: (SubPlan 3), t1.v, t1.id
+ SubPlan 3
+ -> Bitmap Heap Scan on pg_temp.gstest5 t2
+ Output: t1.v
+ Recheck Cond: (t2.id = t1.id)
+ -> Bitmap Index Scan on gstest5_pkey
+ Index Cond: (t2.id = t1.id)
+(15 rows)
+
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s
+from gstest5 t1
+group by grouping sets(v, s)
+order by case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end
+ nulls first;
+ grouping | s
+----------+---
+ 1 |
+ 1 |
+ 1 |
+ 1 |
+ 1 |
+ 0 | 1
+ 0 | 2
+ 0 | 3
+ 0 | 4
+ 0 | 5
+(10 rows)
+
+explain (verbose, costs off)
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s,
+ case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end as o
+from gstest5 t1
+group by grouping sets(v, s)
+order by o nulls first;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: (GROUPING((SubPlan 1))), ((SubPlan 3)), (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END), t1.v
+ Sort Key: (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END) NULLS FIRST
+ -> HashAggregate
+ Output: GROUPING((SubPlan 1)), ((SubPlan 3)), CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END, t1.v
+ Hash Key: t1.v
+ Hash Key: (SubPlan 3)
+ -> Seq Scan on pg_temp.gstest5 t1
+ Output: (SubPlan 3), t1.v, t1.id
+ SubPlan 3
+ -> Bitmap Heap Scan on pg_temp.gstest5 t2
+ Output: t1.v
+ Recheck Cond: (t2.id = t1.id)
+ -> Bitmap Index Scan on gstest5_pkey
+ Index Cond: (t2.id = t1.id)
+(15 rows)
+
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s,
+ case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end as o
+from gstest5 t1
+group by grouping sets(v, s)
+order by o nulls first;
+ grouping | s | o
+----------+---+---
+ 1 | |
+ 1 | |
+ 1 | |
+ 1 | |
+ 1 | |
+ 0 | 1 | 1
+ 0 | 2 | 2
+ 0 | 3 | 3
+ 0 | 4 | 4
+ 0 | 5 | 5
+(10 rows)
+
+-- test handling of expressions that should match lower target items
+explain (costs off)
+select a < b and b < 3 from (values (1, 2)) t(a, b) group by rollup(a < b and b < 3) having a < b and b < 3;
+ QUERY PLAN
+-----------------------------------
+ MixedAggregate
+ Hash Key: ((1 < 2) AND (2 < 3))
+ Group Key: ()
+ Filter: (((1 < 2) AND (2 < 3)))
+ -> Result
+(5 rows)
+
+select a < b and b < 3 from (values (1, 2)) t(a, b) group by rollup(a < b and b < 3) having a < b and b < 3;
+ ?column?
+----------
+ t
+(1 row)
+
+explain (costs off)
+select not a from (values(true)) t(a) group by rollup(not a) having not not a;
+ QUERY PLAN
+------------------------------
+ MixedAggregate
+ Hash Key: (NOT true)
+ Group Key: ()
+ Filter: (NOT ((NOT true)))
+ -> Result
+(5 rows)
+
+select not a from (values(true)) t(a) group by rollup(not a) having not not a;
+ ?column?
+----------
+ f
+(1 row)
+
-- end
select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1;
+-- test handling of subqueries in grouping sets
+create temp table gstest5(id integer primary key, v integer);
+insert into gstest5 select i, i from generate_series(1,5)i;
+
+explain (verbose, costs off)
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s
+from gstest5 t1
+group by grouping sets(v, s)
+order by case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end
+ nulls first;
+
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s
+from gstest5 t1
+group by grouping sets(v, s)
+order by case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end
+ nulls first;
+
+explain (verbose, costs off)
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s,
+ case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end as o
+from gstest5 t1
+group by grouping sets(v, s)
+order by o nulls first;
+
+select grouping((select t1.v from gstest5 t2 where id = t1.id)),
+ (select t1.v from gstest5 t2 where id = t1.id) as s,
+ case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0
+ then (select t1.v from gstest5 t2 where id = t1.id)
+ else null end as o
+from gstest5 t1
+group by grouping sets(v, s)
+order by o nulls first;
+
+-- test handling of expressions that should match lower target items
+explain (costs off)
+select a < b and b < 3 from (values (1, 2)) t(a, b) group by rollup(a < b and b < 3) having a < b and b < 3;
+select a < b and b < 3 from (values (1, 2)) t(a, b) group by rollup(a < b and b < 3) having a < b and b < 3;
+
+explain (costs off)
+select not a from (values(true)) t(a) group by rollup(not a) having not not a;
+select not a from (values(true)) t(a) group by rollup(not a) having not not a;
+
-- end
check_network_data
check_object_relabel_type
check_password_hook_type
-check_ungrouped_columns_context
child_process_kind
chr
cmpEntriesArg
string
substitute_actual_parameters_context
substitute_actual_srf_parameters_context
+substitute_grouped_columns_context
substitute_phv_relids_context
subxids_array_status
symbol