{
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;