diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2019-01-11 15:53:34 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2019-01-11 15:54:06 -0500 |
commit | 1db5667bac63d0be825c375c4aeb8fb29b9a50bd (patch) | |
tree | c51e041eaf9cce2a39592eb376211f7408319e10 /src/backend/optimizer/plan/subselect.c | |
parent | 8b89a88618dff78dec25f08ee0ca83824b69b34b (diff) | |
download | postgresql-1db5667bac63d0be825c375c4aeb8fb29b9a50bd.tar.gz postgresql-1db5667bac63d0be825c375c4aeb8fb29b9a50bd.zip |
Avoid sharing PARAM_EXEC slots between different levels of NestLoop.
Up to now, createplan.c attempted to share PARAM_EXEC slots for
NestLoopParams across different plan levels, if the same underlying Var
was being fed down to different righthand-side subplan trees by different
NestLoops. This was, I think, more of an artifact of using subselect.c's
PlannerParamItem infrastructure than an explicit design goal, but anyway
that was the end result.
This works well enough as long as the plan tree is executing synchronously,
but the feature whereby Gather can execute the parallelized subplan locally
breaks it. An upper NestLoop node might execute for a row retrieved from
a parallel worker, and assign a value for a PARAM_EXEC slot from that row,
while the leader's copy of the parallelized subplan is suspended with a
different active value of the row the Var comes from. When control
eventually returns to the leader's subplan, it gets the wrong answers if
the same PARAM_EXEC slot is being used within the subplan, as reported
in bug #15577 from Bartosz Polnik.
This is pretty reminiscent of the problem fixed in commit 46c508fbc, and
the proper fix seems to be the same: don't try to share PARAM_EXEC slots
across different levels of controlling NestLoop nodes.
This requires decoupling NestLoopParam handling from PlannerParamItem
handling, although the logic remains somewhat similar. To avoid bizarre
division of labor between subselect.c and createplan.c, I decided to move
all the param-slot-assignment logic for both cases out of those files
and put it into a new file paramassign.c. Hopefully it's a bit better
documented now, too.
A regression test case for this might be nice, but we don't know a
test case that triggers the problem with a suitably small amount
of data.
Back-patch to 9.6 where we added Gather nodes. It's conceivable that
related problems exist in older branches; but without some evidence
for that, I'll leave the older branches alone.
Discussion: https://postgr.es/m/15577-ca61ab18904af852@postgresql.org
Diffstat (limited to 'src/backend/optimizer/plan/subselect.c')
-rw-r--r-- | src/backend/optimizer/plan/subselect.c | 396 |
1 files changed, 26 insertions, 370 deletions
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index cb1ea86afb2..64272dd6500 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1,7 +1,10 @@ /*------------------------------------------------------------------------- * * subselect.c - * Planning routines for subselects and parameters. + * Planning routines for subselects. + * + * This module deals with SubLinks and CTEs, but not subquery RTEs (i.e., + * not sub-SELECT-in-FROM cases). * * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -22,6 +25,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/paramassign.h" #include "optimizer/pathnode.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" @@ -87,355 +91,6 @@ static bool finalize_agg_primnode(Node *node, finalize_primnode_context *context /* - * Select a PARAM_EXEC number to identify the given Var as a parameter for - * the current subquery, or for a nestloop's inner scan. - * If the Var already has a param in the current context, return that one. - */ -static int -assign_param_for_var(PlannerInfo *root, Var *var) -{ - ListCell *ppl; - PlannerParamItem *pitem; - Index levelsup; - - /* Find the query level the Var belongs to */ - for (levelsup = var->varlevelsup; levelsup > 0; levelsup--) - root = root->parent_root; - - /* If there's already a matching PlannerParamItem there, just use it */ - foreach(ppl, root->plan_params) - { - pitem = (PlannerParamItem *) lfirst(ppl); - if (IsA(pitem->item, Var)) - { - Var *pvar = (Var *) pitem->item; - - /* - * This comparison must match _equalVar(), except for ignoring - * varlevelsup. Note that _equalVar() ignores the location. - */ - if (pvar->varno == var->varno && - pvar->varattno == var->varattno && - pvar->vartype == var->vartype && - pvar->vartypmod == var->vartypmod && - pvar->varcollid == var->varcollid && - pvar->varnoold == var->varnoold && - pvar->varoattno == var->varoattno) - return pitem->paramId; - } - } - - /* Nope, so make a new one */ - var = copyObject(var); - var->varlevelsup = 0; - - pitem = makeNode(PlannerParamItem); - pitem->item = (Node *) var; - pitem->paramId = list_length(root->glob->paramExecTypes); - root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, - var->vartype); - - root->plan_params = lappend(root->plan_params, pitem); - - return pitem->paramId; -} - -/* - * Generate a Param node to replace the given Var, - * which is expected to have varlevelsup > 0 (ie, it is not local). - */ -static Param * -replace_outer_var(PlannerInfo *root, Var *var) -{ - Param *retval; - int i; - - Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level); - - /* Find the Var in the appropriate plan_params, or add it if not present */ - i = assign_param_for_var(root, var); - - retval = makeNode(Param); - retval->paramkind = PARAM_EXEC; - retval->paramid = i; - retval->paramtype = var->vartype; - retval->paramtypmod = var->vartypmod; - retval->paramcollid = var->varcollid; - retval->location = var->location; - - return retval; -} - -/* - * Generate a Param node to replace the given Var, which will be supplied - * from an upper NestLoop join node. - * - * This is effectively the same as replace_outer_var, except that we expect - * the Var to be local to the current query level. - */ -Param * -assign_nestloop_param_var(PlannerInfo *root, Var *var) -{ - Param *retval; - int i; - - Assert(var->varlevelsup == 0); - - i = assign_param_for_var(root, var); - - retval = makeNode(Param); - retval->paramkind = PARAM_EXEC; - retval->paramid = i; - retval->paramtype = var->vartype; - retval->paramtypmod = var->vartypmod; - retval->paramcollid = var->varcollid; - retval->location = var->location; - - return retval; -} - -/* - * Select a PARAM_EXEC number to identify the given PlaceHolderVar as a - * parameter for the current subquery, or for a nestloop's inner scan. - * If the PHV already has a param in the current context, return that one. - * - * This is just like assign_param_for_var, except for PlaceHolderVars. - */ -static int -assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) -{ - ListCell *ppl; - PlannerParamItem *pitem; - Index levelsup; - - /* Find the query level the PHV belongs to */ - for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--) - root = root->parent_root; - - /* If there's already a matching PlannerParamItem there, just use it */ - foreach(ppl, root->plan_params) - { - pitem = (PlannerParamItem *) lfirst(ppl); - if (IsA(pitem->item, PlaceHolderVar)) - { - PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item; - - /* We assume comparing the PHIDs is sufficient */ - if (pphv->phid == phv->phid) - return pitem->paramId; - } - } - - /* Nope, so make a new one */ - phv = copyObject(phv); - if (phv->phlevelsup != 0) - { - IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0); - Assert(phv->phlevelsup == 0); - } - - pitem = makeNode(PlannerParamItem); - pitem->item = (Node *) phv; - pitem->paramId = list_length(root->glob->paramExecTypes); - root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, - exprType((Node *) phv->phexpr)); - - root->plan_params = lappend(root->plan_params, pitem); - - return pitem->paramId; -} - -/* - * Generate a Param node to replace the given PlaceHolderVar, - * which is expected to have phlevelsup > 0 (ie, it is not local). - * - * This is just like replace_outer_var, except for PlaceHolderVars. - */ -static Param * -replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) -{ - Param *retval; - int i; - - Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level); - - /* Find the PHV in the appropriate plan_params, or add it if not present */ - i = assign_param_for_placeholdervar(root, phv); - - retval = makeNode(Param); - retval->paramkind = PARAM_EXEC; - retval->paramid = i; - retval->paramtype = exprType((Node *) phv->phexpr); - retval->paramtypmod = exprTypmod((Node *) phv->phexpr); - retval->paramcollid = exprCollation((Node *) phv->phexpr); - retval->location = -1; - - return retval; -} - -/* - * Generate a Param node to replace the given PlaceHolderVar, which will be - * supplied from an upper NestLoop join node. - * - * This is just like assign_nestloop_param_var, except for PlaceHolderVars. - */ -Param * -assign_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) -{ - Param *retval; - int i; - - Assert(phv->phlevelsup == 0); - - i = assign_param_for_placeholdervar(root, phv); - - retval = makeNode(Param); - retval->paramkind = PARAM_EXEC; - retval->paramid = i; - retval->paramtype = exprType((Node *) phv->phexpr); - retval->paramtypmod = exprTypmod((Node *) phv->phexpr); - retval->paramcollid = exprCollation((Node *) phv->phexpr); - retval->location = -1; - - return retval; -} - -/* - * Generate a Param node to replace the given Aggref - * which is expected to have agglevelsup > 0 (ie, it is not local). - */ -static Param * -replace_outer_agg(PlannerInfo *root, Aggref *agg) -{ - Param *retval; - PlannerParamItem *pitem; - Index levelsup; - - Assert(agg->agglevelsup > 0 && agg->agglevelsup < root->query_level); - - /* Find the query level the Aggref belongs to */ - for (levelsup = agg->agglevelsup; levelsup > 0; levelsup--) - root = root->parent_root; - - /* - * It does not seem worthwhile to try to match duplicate outer aggs. Just - * make a new slot every time. - */ - agg = copyObject(agg); - IncrementVarSublevelsUp((Node *) agg, -((int) agg->agglevelsup), 0); - Assert(agg->agglevelsup == 0); - - pitem = makeNode(PlannerParamItem); - pitem->item = (Node *) agg; - pitem->paramId = list_length(root->glob->paramExecTypes); - root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, - agg->aggtype); - - root->plan_params = lappend(root->plan_params, pitem); - - retval = makeNode(Param); - retval->paramkind = PARAM_EXEC; - retval->paramid = pitem->paramId; - retval->paramtype = agg->aggtype; - retval->paramtypmod = -1; - retval->paramcollid = agg->aggcollid; - retval->location = agg->location; - - return retval; -} - -/* - * Generate a Param node to replace the given GroupingFunc expression which is - * expected to have agglevelsup > 0 (ie, it is not local). - */ -static Param * -replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp) -{ - Param *retval; - PlannerParamItem *pitem; - Index levelsup; - Oid ptype; - - Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level); - - /* Find the query level the GroupingFunc belongs to */ - for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--) - root = root->parent_root; - - /* - * It does not seem worthwhile to try to match duplicate outer aggs. Just - * make a new slot every time. - */ - grp = copyObject(grp); - IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0); - Assert(grp->agglevelsup == 0); - ptype = exprType((Node *) grp); - - pitem = makeNode(PlannerParamItem); - pitem->item = (Node *) grp; - pitem->paramId = list_length(root->glob->paramExecTypes); - root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, - ptype); - - root->plan_params = lappend(root->plan_params, pitem); - - retval = makeNode(Param); - retval->paramkind = PARAM_EXEC; - retval->paramid = pitem->paramId; - retval->paramtype = ptype; - retval->paramtypmod = -1; - retval->paramcollid = InvalidOid; - retval->location = grp->location; - - return retval; -} - -/* - * Generate a new Param node that will not conflict with any other. - * - * This is used to create Params representing subplan outputs. - * We don't need to build a PlannerParamItem for such a Param, but we do - * need to make sure we record the type in paramExecTypes (otherwise, - * there won't be a slot allocated for it). - */ -static Param * -generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod, - Oid paramcollation) -{ - Param *retval; - - retval = makeNode(Param); - retval->paramkind = PARAM_EXEC; - retval->paramid = list_length(root->glob->paramExecTypes); - root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, - paramtype); - retval->paramtype = paramtype; - retval->paramtypmod = paramtypmod; - retval->paramcollid = paramcollation; - retval->location = -1; - - return retval; -} - -/* - * Assign a (nonnegative) PARAM_EXEC ID for a special parameter (one that - * is not actually used to carry a value at runtime). Such parameters are - * used for special runtime signaling purposes, such as connecting a - * recursive union node to its worktable scan node or forcing plan - * re-evaluation within the EvalPlanQual mechanism. No actual Param node - * exists with this ID, however. - */ -int -SS_assign_special_param(PlannerInfo *root) -{ - int paramId = list_length(root->glob->paramExecTypes); - - root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, - InvalidOid); - return paramId; -} - -/* * Get the datatype/typmod/collation of the first column of the plan's output. * * This information is stored for ARRAY_SUBLINK execution and for @@ -716,7 +371,7 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, Param *prm; Assert(testexpr == NULL); - prm = generate_new_param(root, BOOLOID, -1, InvalidOid); + prm = generate_new_exec_param(root, BOOLOID, -1, InvalidOid); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -728,10 +383,10 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, Assert(!te->resjunk); Assert(testexpr == NULL); - prm = generate_new_param(root, - exprType((Node *) te->expr), - exprTypmod((Node *) te->expr), - exprCollation((Node *) te->expr)); + prm = generate_new_exec_param(root, + exprType((Node *) te->expr), + exprTypmod((Node *) te->expr), + exprCollation((Node *) te->expr)); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -748,10 +403,10 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, if (!OidIsValid(arraytype)) elog(ERROR, "could not find array type for datatype %s", format_type_be(exprType((Node *) te->expr))); - prm = generate_new_param(root, - arraytype, - exprTypmod((Node *) te->expr), - exprCollation((Node *) te->expr)); + prm = generate_new_exec_param(root, + arraytype, + exprTypmod((Node *) te->expr), + exprCollation((Node *) te->expr)); splan->setParam = list_make1_int(prm->paramid); isInitPlan = true; result = (Node *) prm; @@ -930,10 +585,10 @@ generate_subquery_params(PlannerInfo *root, List *tlist, List **paramIds) if (tent->resjunk) continue; - param = generate_new_param(root, - exprType((Node *) tent->expr), - exprTypmod((Node *) tent->expr), - exprCollation((Node *) tent->expr)); + param = generate_new_exec_param(root, + exprType((Node *) tent->expr), + exprTypmod((Node *) tent->expr), + exprCollation((Node *) tent->expr)); result = lappend(result, param); ids = lappend_int(ids, param->paramid); } @@ -1257,7 +912,7 @@ SS_process_ctes(PlannerInfo *root) * ParamExecData slot for this param ID for communication among * multiple CteScan nodes that might be scanning this CTE.) */ - paramid = SS_assign_special_param(root); + paramid = assign_special_exec_param(root); splan->setParam = list_make1_int(paramid); /* @@ -1863,10 +1518,10 @@ convert_EXISTS_to_ANY(PlannerInfo *root, Query *subselect, Param *param; cc = lnext(cc); - param = generate_new_param(root, - exprType(rightarg), - exprTypmod(rightarg), - exprCollation(rightarg)); + param = generate_new_exec_param(root, + exprType(rightarg), + exprTypmod(rightarg), + exprCollation(rightarg)); tlist = lappend(tlist, makeTargetEntry((Expr *) rightarg, resno++, @@ -2917,7 +2572,7 @@ finalize_primnode(Node *node, finalize_primnode_context *context) * parameter change signaling since we always re-evaluate the subplan. * Note that this wouldn't work too well if there might be uses of the * same param IDs elsewhere in the plan, but that can't happen because - * generate_new_param never tries to merge params. + * generate_new_exec_param never tries to merge params. */ foreach(lc, subplan->paramIds) { @@ -2983,7 +2638,8 @@ SS_make_initplan_output_param(PlannerInfo *root, Oid resulttype, int32 resulttypmod, Oid resultcollation) { - return generate_new_param(root, resulttype, resulttypmod, resultcollation); + return generate_new_exec_param(root, resulttype, + resulttypmod, resultcollation); } /* |