diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 1f2bb6cc72f1242f14d55eee7cdc8e0e0d0775a9..02a0f62a53a4e3d06a3ad48d523e959d5d6b2ab7 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1666,7 +1666,6 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node) WRITE_NODE_TYPE("PLANNERGLOBAL"); /* NB: this isn't a complete set of fields */ - WRITE_NODE_FIELD(paramlist); WRITE_NODE_FIELD(subplans); WRITE_BITMAPSET_FIELD(rewindPlanIDs); WRITE_NODE_FIELD(finalrtable); @@ -1674,6 +1673,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node) WRITE_NODE_FIELD(resultRelations); WRITE_NODE_FIELD(relationOids); WRITE_NODE_FIELD(invalItems); + WRITE_INT_FIELD(nParamExec); WRITE_UINT_FIELD(lastPHId); WRITE_UINT_FIELD(lastRowMarkId); WRITE_BOOL_FIELD(transientPlan); @@ -1688,6 +1688,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_NODE_FIELD(parse); WRITE_NODE_FIELD(glob); WRITE_UINT_FIELD(query_level); + WRITE_NODE_FIELD(plan_params); WRITE_BITMAPSET_FIELD(all_baserels); WRITE_NODE_FIELD(join_rel_list); WRITE_INT_FIELD(join_cur_level); @@ -1754,6 +1755,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_FLOAT_FIELD(allvisfrac, "%.6f"); WRITE_NODE_FIELD(subplan); WRITE_NODE_FIELD(subroot); + WRITE_NODE_FIELD(subplan_params); /* we don't try to print fdwroutine or fdw_private */ WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(joininfo); @@ -1950,7 +1952,7 @@ _outPlannerParamItem(StringInfo str, const PlannerParamItem *node) WRITE_NODE_TYPE("PLANNERPARAMITEM"); WRITE_NODE_FIELD(item); - WRITE_UINT_FIELD(abslevel); + WRITE_INT_FIELD(paramId); } /***************************************************************************** diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 69a1b93b33746370457bff2daf4d4ece66535803..458dae0489c029bd743c75c82f8e5102067e89bf 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1145,6 +1145,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, else tuple_fraction = root->tuple_fraction; + /* plan_params should not be in use in current query level */ + Assert(root->plan_params == NIL); + /* Generate the plan for the subquery */ rel->subplan = subquery_planner(root->glob, subquery, root, @@ -1152,6 +1155,10 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, &subroot); rel->subroot = subroot; + /* Isolate the params needed by this specific subplan */ + rel->subplan_params = root->plan_params; + root->plan_params = NIL; + /* * It's possible that constraint exclusion proved the subquery empty. If * so, it's convenient to turn it back into a dummy path so that we will diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 5d3b293b88a3ee030adae2260520eda69caad4b7..030f420c90eb37946ee333250de54af61d9b82d7 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -84,7 +84,8 @@ static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, Plan *outer_plan, Plan *inner_plan); static Node *replace_nestloop_params(PlannerInfo *root, Node *expr); static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); -static void identify_nestloop_extparams(PlannerInfo *root, Plan *subplan); +static void process_subquery_nestloop_params(PlannerInfo *root, + List *subplan_params); static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path); static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path); static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol); @@ -188,6 +189,9 @@ create_plan(PlannerInfo *root, Path *best_path) { Plan *plan; + /* plan_params should not be in use in current query level */ + Assert(root->plan_params == NIL); + /* Initialize this module's private workspace in PlannerInfo */ root->curOuterRels = NULL; root->curOuterParams = NIL; @@ -199,6 +203,12 @@ create_plan(PlannerInfo *root, Path *best_path) if (root->curOuterParams != NIL) elog(ERROR, "failed to assign all NestLoopParams to plan nodes"); + /* + * Reset plan_params to ensure param IDs used for nestloop params are not + * re-used later + */ + root->plan_params = NIL; + return plan; } @@ -1662,7 +1672,8 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path, { scan_clauses = (List *) replace_nestloop_params(root, (Node *) scan_clauses); - identify_nestloop_extparams(root, best_path->parent->subplan); + process_subquery_nestloop_params(root, + best_path->parent->subplan_params); } scan_plan = make_subqueryscan(tlist, @@ -2620,30 +2631,26 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root) } /* - * identify_nestloop_extparams - * Identify extParams of a parameterized subquery that need to be fed + * process_subquery_nestloop_params + * Handle params of a parameterized subquery that need to be fed * from an outer nestloop. * + * Currently, that would be *all* params that a subquery in FROM has demanded + * from the current query level, since they must be LATERAL references. + * * The subplan's references to the outer variables are already represented * as PARAM_EXEC Params, so we need not modify the subplan here. What we * do need to do is add entries to root->curOuterParams to signal the parent * nestloop plan node that it must provide these values. */ static void -identify_nestloop_extparams(PlannerInfo *root, Plan *subplan) +process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params) { - Bitmapset *tmpset; - int paramid; + ListCell *ppl; - /* Examine each extParam of the subquery's plan */ - tmpset = bms_copy(subplan->extParam); - while ((paramid = bms_first_member(tmpset)) >= 0) + foreach(ppl, subplan_params) { - PlannerParamItem *pitem = list_nth(root->glob->paramlist, paramid); - - /* Ignore anything coming from an upper query level */ - if (pitem->abslevel != root->query_level) - continue; + PlannerParamItem *pitem = (PlannerParamItem *) lfirst(ppl); if (IsA(pitem->item, Var)) { @@ -2651,14 +2658,14 @@ identify_nestloop_extparams(PlannerInfo *root, Plan *subplan) NestLoopParam *nlp; ListCell *lc; - /* If not from a nestloop outer rel, nothing to do */ + /* If not from a nestloop outer rel, complain */ if (!bms_is_member(var->varno, root->curOuterRels)) - continue; + elog(ERROR, "non-LATERAL parameter required by subquery"); /* Is this param already listed in root->curOuterParams? */ foreach(lc, root->curOuterParams) { nlp = (NestLoopParam *) lfirst(lc); - if (nlp->paramno == paramid) + if (nlp->paramno == pitem->paramId) { Assert(equal(var, nlp->paramval)); /* Present, so nothing to do */ @@ -2669,7 +2676,7 @@ identify_nestloop_extparams(PlannerInfo *root, Plan *subplan) { /* No, so add it */ nlp = makeNode(NestLoopParam); - nlp->paramno = paramid; + nlp->paramno = pitem->paramId; nlp->paramval = copyObject(var); root->curOuterParams = lappend(root->curOuterParams, nlp); } @@ -2680,22 +2687,15 @@ identify_nestloop_extparams(PlannerInfo *root, Plan *subplan) NestLoopParam *nlp; ListCell *lc; - /* - * If not from a nestloop outer rel, nothing to do. We use - * bms_overlap as a cheap/quick test to see if the PHV might be - * evaluated in the outer rels, and then grab its PlaceHolderInfo - * to tell for sure. - */ - if (!bms_overlap(phv->phrels, root->curOuterRels)) - continue; + /* If not from a nestloop outer rel, complain */ if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at, root->curOuterRels)) - continue; + elog(ERROR, "non-LATERAL parameter required by subquery"); /* Is this param already listed in root->curOuterParams? */ foreach(lc, root->curOuterParams) { nlp = (NestLoopParam *) lfirst(lc); - if (nlp->paramno == paramid) + if (nlp->paramno == pitem->paramId) { Assert(equal(phv, nlp->paramval)); /* Present, so nothing to do */ @@ -2706,13 +2706,14 @@ identify_nestloop_extparams(PlannerInfo *root, Plan *subplan) { /* No, so add it */ nlp = makeNode(NestLoopParam); - nlp->paramno = paramid; + nlp->paramno = pitem->paramId; nlp->paramval = copyObject(phv); root->curOuterParams = lappend(root->curOuterParams, nlp); } } + else + elog(ERROR, "unexpected type of subquery parameter"); } - bms_free(tmpset); } /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 4284eed47b929f7c9cca8c75bcde3487fc13c65f..385e64647ea0dea00c15889064421a01ad2dab79 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -155,7 +155,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) glob = makeNode(PlannerGlobal); glob->boundParams = boundParams; - glob->paramlist = NIL; glob->subplans = NIL; glob->subroots = NIL; glob->rewindPlanIDs = NULL; @@ -164,6 +163,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) glob->resultRelations = NIL; glob->relationOids = NIL; glob->invalItems = NIL; + glob->nParamExec = 0; glob->lastPHId = 0; glob->lastRowMarkId = 0; glob->transientPlan = false; @@ -243,7 +243,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->rowMarks = glob->finalrowmarks; result->relationOids = glob->relationOids; result->invalItems = glob->invalItems; - result->nParamExec = list_length(glob->paramlist); + result->nParamExec = glob->nParamExec; return result; } @@ -295,6 +295,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->glob = glob; root->query_level = parent_root ? parent_root->query_level + 1 : 1; root->parent_root = parent_root; + root->plan_params = NIL; root->planner_cxt = CurrentMemoryContext; root->init_plans = NIL; root->cte_plan_ids = NIL; @@ -586,7 +587,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, * and attach the initPlans to the top plan node. */ if (list_length(glob->subplans) != num_old_subplans || - root->glob->paramlist != NIL) + root->glob->nParamExec > 0) SS_finalize_plan(root, plan, true); /* Return internal info if caller wants it */ diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 4c2e82128181b8bbbfb4c527b0db8eed1714b6df..ce7583f1e92d435a8a5986b91952b4da3646a558 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -54,6 +54,7 @@ typedef struct finalize_primnode_context static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, + List *plan_params, SubLinkType subLinkType, Node *testexpr, bool adjust_testexpr, bool unknownEqFalse); static List *generate_subquery_params(PlannerInfo *root, List *tlist, @@ -82,35 +83,42 @@ static bool finalize_primnode(Node *node, finalize_primnode_context *context); /* - * Select a PARAM_EXEC number to identify the given Var. - * If the Var already has a param slot, return that one. + * 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 abslevel; - int i; + Index levelsup; - abslevel = root->query_level - var->varlevelsup; + /* Find the query level the Var belongs to */ + for (levelsup = var->varlevelsup; levelsup > 0; levelsup--) + root = root->parent_root; - /* If there's already a paramlist entry for this same Var, just use it */ - i = 0; - foreach(ppl, root->glob->paramlist) + /* If there's already a matching PlannerParamItem there, just use it */ + foreach(ppl, root->plan_params) { pitem = (PlannerParamItem *) lfirst(ppl); - if (pitem->abslevel == abslevel && IsA(pitem->item, Var)) + 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) - return i; + pvar->vartypmod == var->vartypmod && + pvar->varcollid == var->varcollid && + pvar->varnoold == var->varnoold && + pvar->varoattno == var->varoattno) + return pitem->paramId; } - i++; } /* Nope, so make a new one */ @@ -119,12 +127,11 @@ assign_param_for_var(PlannerInfo *root, Var *var) pitem = makeNode(PlannerParamItem); pitem->item = (Node *) var; - pitem->abslevel = abslevel; + pitem->paramId = root->glob->nParamExec++; - root->glob->paramlist = lappend(root->glob->paramlist, pitem); + root->plan_params = lappend(root->plan_params, pitem); - /* i is already the correct list index for the new item */ - return i; + return pitem->paramId; } /* @@ -139,16 +146,7 @@ replace_outer_var(PlannerInfo *root, Var *var) Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level); - /* - * Find the Var in root->glob->paramlist, or add it if not present. - * - * NOTE: in sufficiently complex querytrees, it is possible for the same - * varno/abslevel to refer to different RTEs in different parts of the - * parsetree, so that different fields might end up sharing the same Param - * number. As long as we check the vartype/typmod as well, I believe that - * this sort of aliasing will cause no trouble. The correct field should - * get stored into the Param slot at execution in each part of the tree. - */ + /* Find the Var in the appropriate plan_params, or add it if not present */ i = assign_param_for_var(root, var); retval = makeNode(Param); @@ -157,7 +155,7 @@ replace_outer_var(PlannerInfo *root, Var *var) retval->paramtype = var->vartype; retval->paramtypmod = var->vartypmod; retval->paramcollid = var->varcollid; - retval->location = -1; + retval->location = var->location; return retval; } @@ -166,8 +164,7 @@ replace_outer_var(PlannerInfo *root, Var *var) * Generate a Param node to replace the given Var, which will be supplied * from an upper NestLoop join node. * - * Because we allow nestloop and subquery Params to alias each other, - * this is effectively the same as replace_outer_var, except that we expect + * This is effectively the same as replace_outer_var, except that we expect * the Var to be local to the current query level. */ Param * @@ -186,14 +183,15 @@ assign_nestloop_param_var(PlannerInfo *root, Var *var) retval->paramtype = var->vartype; retval->paramtypmod = var->vartypmod; retval->paramcollid = var->varcollid; - retval->location = -1; + retval->location = var->location; return retval; } /* - * Select a PARAM_EXEC number to identify the given PlaceHolderVar. - * If the PlaceHolderVar already has a param slot, return that one. + * 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. */ @@ -202,25 +200,24 @@ assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) { ListCell *ppl; PlannerParamItem *pitem; - Index abslevel; - int i; + Index levelsup; - abslevel = root->query_level - phv->phlevelsup; + /* Find the query level the PHV belongs to */ + for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--) + root = root->parent_root; - /* If there's already a paramlist entry for this same PHV, just use it */ - i = 0; - foreach(ppl, root->glob->paramlist) + /* If there's already a matching PlannerParamItem there, just use it */ + foreach(ppl, root->plan_params) { pitem = (PlannerParamItem *) lfirst(ppl); - if (pitem->abslevel == abslevel && IsA(pitem->item, PlaceHolderVar)) + if (IsA(pitem->item, PlaceHolderVar)) { PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item; /* We assume comparing the PHIDs is sufficient */ if (pphv->phid == phv->phid) - return i; + return pitem->paramId; } - i++; } /* Nope, so make a new one */ @@ -233,12 +230,11 @@ assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) pitem = makeNode(PlannerParamItem); pitem->item = (Node *) phv; - pitem->abslevel = abslevel; + pitem->paramId = root->glob->nParamExec++; - root->glob->paramlist = lappend(root->glob->paramlist, pitem); + root->plan_params = lappend(root->plan_params, pitem); - /* i is already the correct list index for the new item */ - return i; + return pitem->paramId; } /* @@ -255,10 +251,7 @@ replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level); - /* - * Find the PlaceHolderVar in root->glob->paramlist, or add it if not - * present. - */ + /* Find the PHV in the appropriate plan_params, or add it if not present */ i = assign_param_for_placeholdervar(root, phv); retval = makeNode(Param); @@ -308,11 +301,13 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) { Param *retval; PlannerParamItem *pitem; - Index abslevel; - int i; + Index levelsup; Assert(agg->agglevelsup > 0 && agg->agglevelsup < root->query_level); - abslevel = root->query_level - agg->agglevelsup; + + /* 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 @@ -324,18 +319,17 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) pitem = makeNode(PlannerParamItem); pitem->item = (Node *) agg; - pitem->abslevel = abslevel; + pitem->paramId = root->glob->nParamExec++; - root->glob->paramlist = lappend(root->glob->paramlist, pitem); - i = list_length(root->glob->paramlist) - 1; + root->plan_params = lappend(root->plan_params, pitem); retval = makeNode(Param); retval->paramkind = PARAM_EXEC; - retval->paramid = i; + retval->paramid = pitem->paramId; retval->paramtype = agg->aggtype; retval->paramtypmod = -1; retval->paramcollid = agg->aggcollid; - retval->location = -1; + retval->location = agg->location; return retval; } @@ -343,29 +337,24 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg) /* * Generate a new Param node that will not conflict with any other. * - * This is used to allocate PARAM_EXEC slots for subplan outputs. + * 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 record the PARAM_EXEC slot number as being allocated. */ static Param * generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod, Oid paramcollation) { Param *retval; - PlannerParamItem *pitem; retval = makeNode(Param); retval->paramkind = PARAM_EXEC; - retval->paramid = list_length(root->glob->paramlist); + retval->paramid = root->glob->nParamExec++; retval->paramtype = paramtype; retval->paramtypmod = paramtypmod; retval->paramcollid = paramcollation; retval->location = -1; - pitem = makeNode(PlannerParamItem); - pitem->item = (Node *) retval; - pitem->abslevel = root->query_level; - - root->glob->paramlist = lappend(root->glob->paramlist, pitem); - return retval; } @@ -374,17 +363,13 @@ generate_new_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod, * 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. + * re-evaluation within the EvalPlanQual mechanism. No actual Param node + * exists with this ID, however. */ int SS_assign_special_param(PlannerInfo *root) { - Param *param; - - /* We generate a Param of datatype INTERNAL */ - param = generate_new_param(root, INTERNALOID, -1, InvalidOid); - /* ... but the caller only cares about its ID */ - return param->paramid; + return root->glob->nParamExec++; } /* @@ -445,6 +430,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType, double tuple_fraction; Plan *plan; PlannerInfo *subroot; + List *plan_params; Node *result; /* @@ -488,6 +474,9 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType, else tuple_fraction = 0.0; /* default behavior */ + /* plan_params should not be in use in current query level */ + Assert(root->plan_params == NIL); + /* * Generate the plan for the subquery. */ @@ -496,8 +485,12 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType, false, tuple_fraction, &subroot); + /* Isolate the params needed by this specific subplan */ + plan_params = root->plan_params; + root->plan_params = NIL; + /* And convert to SubPlan or InitPlan format. */ - result = build_subplan(root, plan, subroot, + result = build_subplan(root, plan, subroot, plan_params, subLinkType, testexpr, true, isTopQual); /* @@ -530,6 +523,10 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType, false, 0.0, &subroot); + /* Isolate the params needed by this specific subplan */ + plan_params = root->plan_params; + root->plan_params = NIL; + /* Now we can check if it'll fit in work_mem */ if (subplan_is_hashable(plan)) { @@ -538,6 +535,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType, /* OK, convert to SubPlan format. */ hashplan = (SubPlan *) build_subplan(root, plan, subroot, + plan_params, ANY_SUBLINK, newtestexpr, false, true); /* Check we got what we expected */ @@ -566,14 +564,14 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, SubLinkType subLinkType, */ static Node * build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, + List *plan_params, SubLinkType subLinkType, Node *testexpr, bool adjust_testexpr, bool unknownEqFalse) { Node *result; SubPlan *splan; bool isInitPlan; - Bitmapset *tmpset; - int paramid; + ListCell *lc; /* * Initialize the SubPlan node. Note plan_id, plan_name, and cost fields @@ -595,36 +593,26 @@ build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot, * Make parParam and args lists of param IDs and expressions that current * query level will pass to this child plan. */ - tmpset = bms_copy(plan->extParam); - while ((paramid = bms_first_member(tmpset)) >= 0) + foreach(lc, plan_params) { - PlannerParamItem *pitem = list_nth(root->glob->paramlist, paramid); - - if (pitem->abslevel == root->query_level) - { - Node *arg; - - /* - * The Var, PlaceHolderVar, or Aggref has already been adjusted to - * have the correct varlevelsup, phlevelsup, or agglevelsup. We - * probably don't even need to copy it again, but be safe. - */ - arg = copyObject(pitem->item); + PlannerParamItem *pitem = (PlannerParamItem *) lfirst(lc); + Node *arg = pitem->item; - /* - * If it's a PlaceHolderVar or Aggref, its arguments might contain - * SubLinks, which have not yet been processed (see the comments - * for SS_replace_correlation_vars). Do that now. - */ - if (IsA(arg, PlaceHolderVar) || - IsA(arg, Aggref)) - arg = SS_process_sublinks(root, arg, false); + /* + * The Var, PlaceHolderVar, or Aggref has already been adjusted to + * have the correct varlevelsup, phlevelsup, or agglevelsup. + * + * If it's a PlaceHolderVar or Aggref, its arguments might contain + * SubLinks, which have not yet been processed (see the comments for + * SS_replace_correlation_vars). Do that now. + */ + if (IsA(arg, PlaceHolderVar) || + IsA(arg, Aggref)) + arg = SS_process_sublinks(root, arg, false); - splan->parParam = lappend_int(splan->parParam, paramid); - splan->args = lappend(splan->args, arg); - } + splan->parParam = lappend_int(splan->parParam, pitem->paramId); + splan->args = lappend(splan->args, arg); } - bms_free(tmpset); /* * Un-correlated or undirect correlated plans of EXISTS, EXPR, ARRAY, or @@ -1045,9 +1033,7 @@ SS_process_ctes(PlannerInfo *root) Plan *plan; PlannerInfo *subroot; SubPlan *splan; - Bitmapset *tmpset; int paramid; - Param *prm; /* * Ignore SELECT CTEs that are not actually referenced anywhere. @@ -1065,6 +1051,9 @@ SS_process_ctes(PlannerInfo *root) */ subquery = (Query *) copyObject(cte->ctequery); + /* plan_params should not be in use in current query level */ + Assert(root->plan_params == NIL); + /* * Generate the plan for the CTE query. Always plan for full * retrieval --- we don't have enough info to predict otherwise. @@ -1074,6 +1063,14 @@ SS_process_ctes(PlannerInfo *root) cte->cterecursive, 0.0, &subroot); + /* + * Since the current query level doesn't yet contain any RTEs, it + * should not be possible for the CTE to have requested parameters of + * this level. + */ + if (root->plan_params) + elog(ERROR, "unexpected outer reference in CTE query"); + /* * Make a SubPlan node for it. This is just enough unlike * build_subplan that we can't share code. @@ -1093,35 +1090,22 @@ SS_process_ctes(PlannerInfo *root) splan->args = NIL; /* - * Make parParam and args lists of param IDs and expressions that - * current query level will pass to this child plan. Even though this - * is an initplan, there could be side-references to earlier - * initplan's outputs, specifically their CTE output parameters. + * The node can't have any inputs (since it's an initplan), so the + * parParam and args lists remain empty. (It could contain references + * to earlier CTEs' output param IDs, but CTE outputs are not + * propagated via the args list.) */ - tmpset = bms_copy(plan->extParam); - while ((paramid = bms_first_member(tmpset)) >= 0) - { - PlannerParamItem *pitem = list_nth(root->glob->paramlist, paramid); - - if (pitem->abslevel == root->query_level) - { - prm = (Param *) pitem->item; - if (!IsA(prm, Param) || - prm->paramtype != INTERNALOID) - elog(ERROR, "bogus local parameter passed to WITH query"); - - splan->parParam = lappend_int(splan->parParam, paramid); - splan->args = lappend(splan->args, copyObject(prm)); - } - } - bms_free(tmpset); /* - * Assign a param to represent the query output. We only really care - * about reserving a parameter ID number. + * Assign a param ID to represent the CTE's output. No ordinary + * "evaluation" of this param slot ever happens, but we use the param + * ID for setParam/chgParam signaling just as if the CTE plan were + * returning a simple scalar output. (Also, the executor abuses the + * ParamExecData slot for this param ID for communication among + * multiple CteScan nodes that might be scanning this CTE.) */ - prm = generate_new_param(root, INTERNALOID, -1, InvalidOid); - splan->setParam = list_make1_int(prm->paramid); + paramid = SS_assign_special_param(root); + splan->setParam = list_make1_int(paramid); /* * Add the subplan and its PlannerInfo to the global lists. @@ -1932,7 +1916,7 @@ SS_finalize_plan(PlannerInfo *root, Plan *plan, bool attach_initplans) *initExtParam, *initSetParam; Cost initplan_cost; - int paramid; + PlannerInfo *proot; ListCell *l; /* @@ -1964,31 +1948,36 @@ SS_finalize_plan(PlannerInfo *root, Plan *plan, bool attach_initplans) /* * Now determine the set of params that are validly referenceable in this * query level; to wit, those available from outer query levels plus the - * output parameters of any initPlans. (We do not include output + * output parameters of any local initPlans. (We do not include output * parameters of regular subplans. Those should only appear within the * testexpr of SubPlan nodes, and are taken care of locally within * finalize_primnode. Likewise, special parameters that are generated by * nodes such as ModifyTable are handled within finalize_plan.) - * - * Note: this is a bit overly generous since some parameters of upper - * query levels might belong to query subtrees that don't include this - * query, or might be nestloop params that won't be passed down at all. - * However, valid_params is only a debugging crosscheck, so it doesn't - * seem worth expending lots of cycles to try to be exact. */ valid_params = bms_copy(initSetParam); - paramid = 0; - foreach(l, root->glob->paramlist) + for (proot = root->parent_root; proot != NULL; proot = proot->parent_root) { - PlannerParamItem *pitem = (PlannerParamItem *) lfirst(l); - - if (pitem->abslevel < root->query_level) + /* Include ordinary Var/PHV/Aggref params */ + foreach(l, proot->plan_params) { - /* valid outer-level parameter */ - valid_params = bms_add_member(valid_params, paramid); + PlannerParamItem *pitem = (PlannerParamItem *) lfirst(l); + + valid_params = bms_add_member(valid_params, pitem->paramId); } + /* Include any outputs of outer-level initPlans */ + foreach(l, proot->init_plans) + { + SubPlan *initsubplan = (SubPlan *) lfirst(l); + ListCell *l2; - paramid++; + foreach(l2, initsubplan->setParam) + { + valid_params = bms_add_member(valid_params, lfirst_int(l2)); + } + } + /* Include worktable ID, if a recursive query is being planned */ + if (proot->wt_param_id >= 0) + valid_params = bms_add_member(valid_params, proot->wt_param_id); } /* diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 3d1fcdc16c94fc314c8b5af05399794782a1bbbe..44b5d6758e7ef49ebb3816996f47cd6dbac208a7 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -799,6 +799,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->glob = root->glob; subroot->query_level = root->query_level; subroot->parent_root = root->parent_root; + subroot->plan_params = NIL; subroot->planner_cxt = CurrentMemoryContext; subroot->init_plans = NIL; subroot->cte_plan_ids = NIL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 94373b087c3ec2fe572a51e34321bc773c66ec20..9e154e1120bed7cddd9e5827d5a5b566210c86af 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -238,6 +238,9 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, */ rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL); + /* plan_params should not be in use in current query level */ + Assert(root->plan_params == NIL); + /* * Generate plan for primitive subquery */ @@ -250,6 +253,13 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, rel->subplan = subplan; rel->subroot = subroot; + /* + * It should not be possible for the primitive query to contain any + * cross-references to other primitive queries in the setop tree. + */ + if (root->plan_params) + elog(ERROR, "unexpected outer reference in set operation subquery"); + /* * Estimate number of groups if caller wants it. If the subquery used * grouping or aggregation, its output is probably mostly unique diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 3878c3752fef56e43bbe12d8c1613698993d0d1d..f724714e49fe03d34a837cd8edd157664d3098a4 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -119,6 +119,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->allvisfrac = 0; rel->subplan = NULL; rel->subroot = NULL; + rel->subplan_params = NIL; rel->fdwroutine = NULL; rel->fdw_private = NULL; rel->baserestrictinfo = NIL; @@ -379,6 +380,7 @@ build_join_rel(PlannerInfo *root, joinrel->allvisfrac = 0; joinrel->subplan = NULL; joinrel->subroot = NULL; + joinrel->subplan_params = NIL; joinrel->fdwroutine = NULL; joinrel->fdw_private = NULL; joinrel->baserestrictinfo = NIL; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index af9425ad9b309fbecd65d1a83a229ddfd447f382..2b2742d7ef5bbc1ae2ddd3e2bf81f986e80524d9 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -75,8 +75,6 @@ typedef struct PlannerGlobal ParamListInfo boundParams; /* Param values provided to planner() */ - List *paramlist; /* to keep track of cross-level Params */ - List *subplans; /* Plans for SubPlan nodes */ List *subroots; /* PlannerInfos for SubPlan nodes */ @@ -93,6 +91,8 @@ typedef struct PlannerGlobal List *invalItems; /* other dependencies, as PlanInvalItems */ + int nParamExec; /* number of PARAM_EXEC Params used */ + Index lastPHId; /* highest PlaceHolderVar ID assigned */ Index lastRowMarkId; /* highest PlanRowMark ID assigned */ @@ -127,6 +127,8 @@ typedef struct PlannerInfo struct PlannerInfo *parent_root; /* NULL at outermost Query */ + List *plan_params; /* list of PlannerParamItems, see below */ + /* * simple_rel_array holds pointers to "base rels" and "other rels" (see * comments for RelOptInfo for more info). It is indexed by rangetable @@ -344,6 +346,7 @@ typedef struct PlannerInfo * allvisfrac - fraction of disk pages that are marked all-visible * subplan - plan for subquery (NULL if it's not a subquery) * subroot - PlannerInfo for subquery (NULL if it's not a subquery) + * subplan_params - list of PlannerParamItems to be passed to subquery * fdwroutine - function hooks for FDW, if foreign table (else NULL) * fdw_private - private state for FDW, if foreign table (else NULL) * @@ -436,6 +439,7 @@ typedef struct RelOptInfo /* use "struct Plan" to avoid including plannodes.h here */ struct Plan *subplan; /* if subquery */ PlannerInfo *subroot; /* if subquery */ + List *subplan_params; /* if subquery */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; /* if foreign table */ void *fdw_private; /* if foreign table */ @@ -1507,23 +1511,26 @@ typedef struct MinMaxAggInfo } MinMaxAggInfo; /* - * glob->paramlist keeps track of the PARAM_EXEC slots that we have decided - * we need for the query. At runtime these slots are used to pass values - * around from one plan node to another. They can be used to pass values - * down into subqueries (for outer references in subqueries), or up out of - * subqueries (for the results of a subplan), or from a NestLoop plan node - * into its inner relation (when the inner scan is parameterized with values - * from the outer relation). The n'th entry in the list (n counts from 0) - * corresponds to Param->paramid = n. - * - * Each paramlist item shows the absolute query level it is associated with, - * where the outermost query is level 1 and nested subqueries have higher - * numbers. The item the parameter slot represents can be one of four kinds: - * - * A Var: the slot represents a variable of that level that must be passed + * At runtime, PARAM_EXEC slots are used to pass values around from one plan + * node to another. They can be used to pass values down into subqueries (for + * outer references in subqueries), or up out of subqueries (for the results + * of a subplan), or from a NestLoop plan node into its inner relation (when + * the inner scan is parameterized with values from the outer relation). + * The planner is responsible for assigning nonconflicting PARAM_EXEC IDs to + * the PARAM_EXEC Params it generates. + * + * Outer references are managed via root->plan_params, which is a list of + * PlannerParamItems. While planning a subquery, each parent query level's + * plan_params contains the values required from it by the current subquery. + * During create_plan(), we use plan_params to track values that must be + * passed from outer to inner sides of NestLoop plan nodes. + * + * The item a PlannerParamItem represents can be one of three kinds: + * + * A Var: the slot represents a variable of this level that must be passed * down because subqueries have outer references to it, or must be passed - * from a NestLoop node of that level to its inner scan. The varlevelsup - * value in the Var will always be zero. + * from a NestLoop node to its inner scan. The varlevelsup value in the Var + * will always be zero. * * A PlaceHolderVar: this works much like the Var case, except that the * entry is a PlaceHolderVar node with a contained expression. The PHV @@ -1535,25 +1542,27 @@ typedef struct MinMaxAggInfo * subquery. The Aggref itself has agglevelsup = 0, and its argument tree * is adjusted to match in level. * - * A Param: the slot holds the result of a subplan (it is a setParam item - * for that subplan). The absolute level shown for such items corresponds - * to the parent query of the subplan. - * * Note: we detect duplicate Var and PlaceHolderVar parameters and coalesce - * them into one slot, but we do not bother to do this for Aggrefs, and it - * would be incorrect to do so for Param slots. Duplicate detection is - * actually *necessary* for NestLoop parameters since it serves to match up - * the usage of a Param (in the inner scan) with the assignment of the value - * (in the NestLoop node). This might result in the same PARAM_EXEC slot being - * used by multiple NestLoop nodes or SubPlan nodes, but no harm is done since - * the same value would be assigned anyway. + * them into one slot, but we do not bother to do that for Aggrefs. + * The scope of duplicate-elimination only extends across the set of + * parameters passed from one query level into a single subquery, or for + * nestloop parameters across the set of nestloop parameters used in a single + * query level. So there is no possibility of a PARAM_EXEC slot being used + * for conflicting purposes. + * + * In addition, PARAM_EXEC slots are assigned for Params representing outputs + * from subplans (values that are setParam items for those subplans). These + * IDs need not be tracked via PlannerParamItems, since we do not need any + * duplicate-elimination nor later processing of the represented expressions. + * Instead, we just record the assignment of the slot number by incrementing + * root->glob->nParamExec. */ typedef struct PlannerParamItem { NodeTag type; - Node *item; /* the Var, PlaceHolderVar, Aggref, or Param */ - Index abslevel; /* its absolute query level */ + Node *item; /* the Var, PlaceHolderVar, or Aggref */ + int paramId; /* its assigned PARAM_EXEC slot number */ } PlannerParamItem; /* diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index a7491bf6b93038b8b23368516d0681a563fa9831..38cfb8c7276c9310a60375f0f70d56b8c14eb970 100644 --- a/src/test/regress/expected/with.out +++ b/src/test/regress/expected/with.out @@ -1209,6 +1209,27 @@ SELECT * FROM outermost; ERROR: recursive reference to query "outermost" must not appear within a subquery LINE 2: WITH innermost as (SELECT 2 FROM outermost) ^ +-- +-- This test will fail with the old implementation of PARAM_EXEC parameter +-- assignment, because the "q1" Var passed down to A's targetlist subselect +-- looks exactly like the "A.id" Var passed down to C's subselect, causing +-- the old code to give them the same runtime PARAM_EXEC slot. But the +-- lifespans of the two parameters overlap, thanks to B also reading A. +-- +with +A as ( select q2 as id, (select q1) as x from int8_tbl ), +B as ( select id, row_number() over (partition by id) as r from A ), +C as ( select A.id, array(select B.id from B where B.id = A.id) from A ) +select * from C; + id | array +-------------------+------------------------------------- + 456 | {456} + 4567890123456789 | {4567890123456789,4567890123456789} + 123 | {123} + 4567890123456789 | {4567890123456789,4567890123456789} + -4567890123456789 | {-4567890123456789} +(5 rows) + -- -- Test CTEs read in non-initialization orders -- diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql index 684c8f13db1a692c78ba4b87355a03377bec83c0..609f51b0c95a43dd9b485a1534f35c666807d646 100644 --- a/src/test/regress/sql/with.sql +++ b/src/test/regress/sql/with.sql @@ -574,6 +574,20 @@ WITH RECURSIVE outermost(x) AS ( ) SELECT * FROM outermost; +-- +-- This test will fail with the old implementation of PARAM_EXEC parameter +-- assignment, because the "q1" Var passed down to A's targetlist subselect +-- looks exactly like the "A.id" Var passed down to C's subselect, causing +-- the old code to give them the same runtime PARAM_EXEC slot. But the +-- lifespans of the two parameters overlap, thanks to B also reading A. +-- + +with +A as ( select q2 as id, (select q1) as x from int8_tbl ), +B as ( select id, row_number() over (partition by id) as r from A ), +C as ( select A.id, array(select B.id from B where B.id = A.id) from A ) +select * from C; + -- -- Test CTEs read in non-initialization orders --