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
This commit is contained in:
Tom Lane 2019-01-11 15:53:34 -05:00
parent 8b89a88618
commit 1db5667bac
7 changed files with 678 additions and 553 deletions

View File

@ -19,7 +19,6 @@
#include <limits.h>
#include <math.h>
#include "access/stratnum.h"
#include "access/sysattr.h"
#include "catalog/pg_class.h"
#include "foreign/fdwapi.h"
@ -29,11 +28,11 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/paramassign.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/predtest.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/subselect.h"
@ -151,8 +150,6 @@ static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path)
static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path);
static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
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);
@ -310,7 +307,7 @@ create_plan(PlannerInfo *root, Path *best_path)
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
/* Initialize this module's private workspace in PlannerInfo */
/* Initialize this module's workspace in PlannerInfo */
root->curOuterRels = NULL;
root->curOuterParams = NIL;
@ -1554,7 +1551,7 @@ create_gather_plan(PlannerInfo *root, GatherPath *best_path)
gather_plan = make_gather(tlist,
NIL,
best_path->num_workers,
SS_assign_special_param(root),
assign_special_exec_param(root),
best_path->single_copy,
subplan);
@ -1590,7 +1587,7 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
copy_generic_path_info(&gm_plan->plan, &best_path->path);
/* Assign the rescan Param. */
gm_plan->rescan_param = SS_assign_special_param(root);
gm_plan->rescan_param = assign_special_exec_param(root);
/* Gather Merge is pointless with no pathkeys; use Gather instead. */
Assert(pathkeys != NIL);
@ -3774,9 +3771,6 @@ create_nestloop_plan(PlannerInfo *root,
Relids outerrelids;
List *nestParams;
Relids saveOuterRels = root->curOuterRels;
ListCell *cell;
ListCell *prev;
ListCell *next;
/* NestLoop can project, so no need to be picky about child tlists */
outer_plan = create_plan_recurse(root, best_path->outerjoinpath, 0);
@ -3820,38 +3814,10 @@ create_nestloop_plan(PlannerInfo *root,
/*
* Identify any nestloop parameters that should be supplied by this join
* node, and move them from root->curOuterParams to the nestParams list.
* node, and remove them from root->curOuterParams.
*/
outerrelids = best_path->outerjoinpath->parent->relids;
nestParams = NIL;
prev = NULL;
for (cell = list_head(root->curOuterParams); cell; cell = next)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(cell);
next = lnext(cell);
if (IsA(nlp->paramval, Var) &&
bms_is_member(nlp->paramval->varno, outerrelids))
{
root->curOuterParams = list_delete_cell(root->curOuterParams,
cell, prev);
nestParams = lappend(nestParams, nlp);
}
else if (IsA(nlp->paramval, PlaceHolderVar) &&
bms_overlap(((PlaceHolderVar *) nlp->paramval)->phrels,
outerrelids) &&
bms_is_subset(find_placeholder_info(root,
(PlaceHolderVar *) nlp->paramval,
false)->ph_eval_at,
outerrelids))
{
root->curOuterParams = list_delete_cell(root->curOuterParams,
cell, prev);
nestParams = lappend(nestParams, nlp);
}
else
prev = cell;
}
nestParams = identify_current_nestloop_params(root, outerrelids);
join_plan = make_nestloop(tlist,
joinclauses,
@ -4351,42 +4317,18 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
if (IsA(node, Var))
{
Var *var = (Var *) node;
Param *param;
NestLoopParam *nlp;
ListCell *lc;
/* Upper-level Vars should be long gone at this point */
Assert(var->varlevelsup == 0);
/* If not to be replaced, we can just return the Var unmodified */
if (!bms_is_member(var->varno, root->curOuterRels))
return node;
/* Create a Param representing the Var */
param = assign_nestloop_param_var(root, var);
/* Is this param already listed in root->curOuterParams? */
foreach(lc, root->curOuterParams)
{
nlp = (NestLoopParam *) lfirst(lc);
if (nlp->paramno == param->paramid)
{
Assert(equal(var, nlp->paramval));
/* Present, so we can just return the Param */
return (Node *) param;
}
}
/* No, so add it */
nlp = makeNode(NestLoopParam);
nlp->paramno = param->paramid;
nlp->paramval = var;
root->curOuterParams = lappend(root->curOuterParams, nlp);
/* And return the replacement Param */
return (Node *) param;
/* Replace the Var with a nestloop Param */
return (Node *) replace_nestloop_param_var(root, var);
}
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
Param *param;
NestLoopParam *nlp;
ListCell *lc;
/* Upper-level PlaceHolderVars should be long gone at this point */
Assert(phv->phlevelsup == 0);
@ -4423,118 +4365,14 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
root);
return (Node *) newphv;
}
/* Create a Param representing the PlaceHolderVar */
param = assign_nestloop_param_placeholdervar(root, phv);
/* Is this param already listed in root->curOuterParams? */
foreach(lc, root->curOuterParams)
{
nlp = (NestLoopParam *) lfirst(lc);
if (nlp->paramno == param->paramid)
{
Assert(equal(phv, nlp->paramval));
/* Present, so we can just return the Param */
return (Node *) param;
}
}
/* No, so add it */
nlp = makeNode(NestLoopParam);
nlp->paramno = param->paramid;
nlp->paramval = (Var *) phv;
root->curOuterParams = lappend(root->curOuterParams, nlp);
/* And return the replacement Param */
return (Node *) param;
/* Replace the PlaceHolderVar with a nestloop Param */
return (Node *) replace_nestloop_param_placeholdervar(root, phv);
}
return expression_tree_mutator(node,
replace_nestloop_params_mutator,
(void *) root);
}
/*
* 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
process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params)
{
ListCell *ppl;
foreach(ppl, subplan_params)
{
PlannerParamItem *pitem = (PlannerParamItem *) lfirst(ppl);
if (IsA(pitem->item, Var))
{
Var *var = (Var *) pitem->item;
NestLoopParam *nlp;
ListCell *lc;
/* If not from a nestloop outer rel, complain */
if (!bms_is_member(var->varno, root->curOuterRels))
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 == pitem->paramId)
{
Assert(equal(var, nlp->paramval));
/* Present, so nothing to do */
break;
}
}
if (lc == NULL)
{
/* No, so add it */
nlp = makeNode(NestLoopParam);
nlp->paramno = pitem->paramId;
nlp->paramval = copyObject(var);
root->curOuterParams = lappend(root->curOuterParams, nlp);
}
}
else if (IsA(pitem->item, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item;
NestLoopParam *nlp;
ListCell *lc;
/* If not from a nestloop outer rel, complain */
if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
root->curOuterRels))
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 == pitem->paramId)
{
Assert(equal(phv, nlp->paramval));
/* Present, so nothing to do */
break;
}
}
if (lc == NULL)
{
/* No, so add it */
nlp = makeNode(NestLoopParam);
nlp->paramno = pitem->paramId;
nlp->paramval = (Var *) copyObject(phv);
root->curOuterParams = lappend(root->curOuterParams, nlp);
}
}
else
elog(ERROR, "unexpected type of subquery parameter");
}
}
/*
* fix_indexqual_references
* Adjust indexqual clauses to the form the executor's indexqual

View File

@ -41,6 +41,7 @@
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/inherit.h"
#include "optimizer/paramassign.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/plancat.h"
@ -635,7 +636,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->inhTargetKind = INHKIND_NONE;
root->hasRecursion = hasRecursion;
if (hasRecursion)
root->wt_param_id = SS_assign_special_param(root);
root->wt_param_id = assign_special_exec_param(root);
else
root->wt_param_id = -1;
root->non_recursive_path = NULL;
@ -1616,7 +1617,7 @@ inheritance_planner(PlannerInfo *root)
returningLists,
rowMarks,
NULL,
SS_assign_special_param(root)));
assign_special_exec_param(root)));
}
/*--------------------
@ -2131,7 +2132,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
{
path = (Path *) create_lockrows_path(root, final_rel, path,
root->rowMarks,
SS_assign_special_param(root));
assign_special_exec_param(root));
}
/*
@ -2204,7 +2205,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
returningLists,
rowMarks,
parse->onConflict,
SS_assign_special_param(root));
assign_special_exec_param(root));
}
/* And shove it into final_rel */

View File

@ -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"
@ -86,355 +90,6 @@ static bool finalize_primnode(Node *node, finalize_primnode_context *context);
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.
*
@ -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);
}
/*

View File

@ -12,8 +12,8 @@ subdir = src/backend/optimizer/util
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
OBJS = appendinfo.o clauses.o inherit.o joininfo.o orclauses.o pathnode.o \
placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \
var.o
OBJS = appendinfo.o clauses.o inherit.o joininfo.o orclauses.o \
paramassign.o pathnode.o placeholder.o plancat.o predtest.o \
relnode.o restrictinfo.o tlist.o var.o
include $(top_srcdir)/src/backend/common.mk

View File

@ -0,0 +1,599 @@
/*-------------------------------------------------------------------------
*
* paramassign.c
* Functions for assigning PARAM_EXEC slots during planning.
*
* This module is responsible for managing three planner data structures:
*
* root->glob->paramExecTypes: records actual assignments of PARAM_EXEC slots.
* The i'th list element holds the data type OID of the i'th parameter slot.
* (Elements can be InvalidOid if they represent slots that are needed for
* chgParam signaling, but will never hold a value at runtime.) This list is
* global to the whole plan since the executor has only one PARAM_EXEC array.
* Assignments are permanent for the plan: we never remove entries once added.
*
* root->plan_params: a list of PlannerParamItem nodes, recording Vars and
* PlaceHolderVars that the root's query level needs to supply to lower-level
* subqueries, along with the PARAM_EXEC number to use for each such value.
* Elements are added to this list while planning a subquery, and the list
* is reset to empty after completion of each subquery.
*
* root->curOuterParams: a list of NestLoopParam nodes, recording Vars and
* PlaceHolderVars that some outer level of nestloop needs to pass down to
* a lower-level plan node in its righthand side. Elements are added to this
* list as createplan.c creates lower Plan nodes that need such Params, and
* are removed when it creates a NestLoop Plan node that will supply those
* values.
*
* The latter two data structures are used to prevent creating multiple
* PARAM_EXEC slots (each requiring work to fill) when the same upper
* SubPlan or NestLoop supplies a value that is referenced in more than
* one place in its child plan nodes. However, when the same Var has to
* be supplied to different subplan trees by different SubPlan or NestLoop
* parent nodes, we don't recognize any commonality; a fresh plan_params or
* curOuterParams entry will be made (since the old one has been removed
* when we finished processing the earlier SubPlan or NestLoop) and a fresh
* PARAM_EXEC number will be assigned. At one time we tried to avoid
* allocating duplicate PARAM_EXEC numbers in such cases, but it's harder
* than it seems to avoid bugs due to overlapping Param lifetimes, so we
* don't risk that anymore. Minimizing the number of PARAM_EXEC slots
* doesn't really save much executor work anyway.
*
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/optimizer/util/paramassign.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "nodes/nodeFuncs.h"
#include "nodes/plannodes.h"
#include "optimizer/paramassign.h"
#include "optimizer/placeholder.h"
#include "rewrite/rewriteManip.h"
/*
* Select a PARAM_EXEC number to identify the given Var as a parameter for
* the current subquery. (It might already have one.)
* Record the need for the Var in the proper upper-level root->plan_params.
*/
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).
* Record the need for the Var in the proper upper-level root->plan_params.
*/
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;
}
/*
* Select a PARAM_EXEC number to identify the given PlaceHolderVar as a
* parameter for the current subquery. (It might already have one.)
* Record the need for the PHV in the proper upper-level root->plan_params.
*
* 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);
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).
* Record the need for the PHV in the proper upper-level root->plan_params.
*
* This is just like replace_outer_var, except for PlaceHolderVars.
*/
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 Aggref
* which is expected to have agglevelsup > 0 (ie, it is not local).
* Record the need for the Aggref in the proper upper-level root->plan_params.
*/
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 de-duplicate references to 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).
* Record the need for the GroupingFunc in the proper upper-level
* root->plan_params.
*/
Param *
replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp)
{
Param *retval;
PlannerParamItem *pitem;
Index levelsup;
Oid ptype = exprType((Node *) grp);
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 de-duplicate references to outer
* aggs. Just make a new slot every time.
*/
grp = copyObject(grp);
IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0);
Assert(grp->agglevelsup == 0);
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 Param node to replace the given Var,
* which is expected to come from some upper NestLoop plan node.
* Record the need for the Var in root->curOuterParams.
*/
Param *
replace_nestloop_param_var(PlannerInfo *root, Var *var)
{
Param *param;
NestLoopParam *nlp;
ListCell *lc;
/* Is this Var already listed in root->curOuterParams? */
foreach(lc, root->curOuterParams)
{
nlp = (NestLoopParam *) lfirst(lc);
if (equal(var, nlp->paramval))
{
/* Yes, so just make a Param referencing this NLP's slot */
param = makeNode(Param);
param->paramkind = PARAM_EXEC;
param->paramid = nlp->paramno;
param->paramtype = var->vartype;
param->paramtypmod = var->vartypmod;
param->paramcollid = var->varcollid;
param->location = var->location;
return param;
}
}
/* No, so assign a PARAM_EXEC slot for a new NLP */
param = generate_new_exec_param(root,
var->vartype,
var->vartypmod,
var->varcollid);
param->location = var->location;
/* Add it to the list of required NLPs */
nlp = makeNode(NestLoopParam);
nlp->paramno = param->paramid;
nlp->paramval = copyObject(var);
root->curOuterParams = lappend(root->curOuterParams, nlp);
/* And return the replacement Param */
return param;
}
/*
* Generate a Param node to replace the given PlaceHolderVar,
* which is expected to come from some upper NestLoop plan node.
* Record the need for the PHV in root->curOuterParams.
*
* This is just like replace_nestloop_param_var, except for PlaceHolderVars.
*/
Param *
replace_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv)
{
Param *param;
NestLoopParam *nlp;
ListCell *lc;
/* Is this PHV already listed in root->curOuterParams? */
foreach(lc, root->curOuterParams)
{
nlp = (NestLoopParam *) lfirst(lc);
if (equal(phv, nlp->paramval))
{
/* Yes, so just make a Param referencing this NLP's slot */
param = makeNode(Param);
param->paramkind = PARAM_EXEC;
param->paramid = nlp->paramno;
param->paramtype = exprType((Node *) phv->phexpr);
param->paramtypmod = exprTypmod((Node *) phv->phexpr);
param->paramcollid = exprCollation((Node *) phv->phexpr);
param->location = -1;
return param;
}
}
/* No, so assign a PARAM_EXEC slot for a new NLP */
param = generate_new_exec_param(root,
exprType((Node *) phv->phexpr),
exprTypmod((Node *) phv->phexpr),
exprCollation((Node *) phv->phexpr));
/* Add it to the list of required NLPs */
nlp = makeNode(NestLoopParam);
nlp->paramno = param->paramid;
nlp->paramval = (Var *) copyObject(phv);
root->curOuterParams = lappend(root->curOuterParams, nlp);
/* And return the replacement Param */
return param;
}
/*
* 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.
*
* subplan_params is a list of PlannerParamItems that we intend to pass to
* a subquery-in-FROM. (This was constructed in root->plan_params while
* planning the subquery, but isn't there anymore when this is called.)
*
* The subplan's references to the outer variables are already represented
* as PARAM_EXEC Params, since that conversion was done by the routines above
* while planning the subquery. So we need not modify the subplan or the
* PlannerParamItems 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. This differs from replace_nestloop_param_var in
* that the PARAM_EXEC slots to use have already been determined.
*
* Note that we also use root->curOuterRels as an implicit parameter for
* sanity checks.
*/
void
process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params)
{
ListCell *lc;
foreach(lc, subplan_params)
{
PlannerParamItem *pitem = castNode(PlannerParamItem, lfirst(lc));
if (IsA(pitem->item, Var))
{
Var *var = (Var *) pitem->item;
NestLoopParam *nlp;
ListCell *lc;
/* If not from a nestloop outer rel, complain */
if (!bms_is_member(var->varno, root->curOuterRels))
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 == pitem->paramId)
{
Assert(equal(var, nlp->paramval));
/* Present, so nothing to do */
break;
}
}
if (lc == NULL)
{
/* No, so add it */
nlp = makeNode(NestLoopParam);
nlp->paramno = pitem->paramId;
nlp->paramval = copyObject(var);
root->curOuterParams = lappend(root->curOuterParams, nlp);
}
}
else if (IsA(pitem->item, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item;
NestLoopParam *nlp;
ListCell *lc;
/* If not from a nestloop outer rel, complain */
if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
root->curOuterRels))
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 == pitem->paramId)
{
Assert(equal(phv, nlp->paramval));
/* Present, so nothing to do */
break;
}
}
if (lc == NULL)
{
/* No, so add it */
nlp = makeNode(NestLoopParam);
nlp->paramno = pitem->paramId;
nlp->paramval = (Var *) copyObject(phv);
root->curOuterParams = lappend(root->curOuterParams, nlp);
}
}
else
elog(ERROR, "unexpected type of subquery parameter");
}
}
/*
* Identify any NestLoopParams that should be supplied by a NestLoop plan
* node with the specified lefthand rels. Remove them from the active
* root->curOuterParams list and return them as the result list.
*/
List *
identify_current_nestloop_params(PlannerInfo *root, Relids leftrelids)
{
List *result;
ListCell *cell;
ListCell *prev;
ListCell *next;
result = NIL;
prev = NULL;
for (cell = list_head(root->curOuterParams); cell; cell = next)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(cell);
next = lnext(cell);
/*
* We are looking for Vars and PHVs that can be supplied by the
* lefthand rels. The "bms_overlap" test is just an optimization to
* allow skipping find_placeholder_info() if the PHV couldn't match.
*/
if (IsA(nlp->paramval, Var) &&
bms_is_member(nlp->paramval->varno, leftrelids))
{
root->curOuterParams = list_delete_cell(root->curOuterParams,
cell, prev);
result = lappend(result, nlp);
}
else if (IsA(nlp->paramval, PlaceHolderVar) &&
bms_overlap(((PlaceHolderVar *) nlp->paramval)->phrels,
leftrelids) &&
bms_is_subset(find_placeholder_info(root,
(PlaceHolderVar *) nlp->paramval,
false)->ph_eval_at,
leftrelids))
{
root->curOuterParams = list_delete_cell(root->curOuterParams,
cell, prev);
result = lappend(result, nlp);
}
else
prev = cell;
}
return result;
}
/*
* Generate a new Param node that will not conflict with any other.
*
* This is used to create Params representing subplan outputs or
* NestLoop parameters.
*
* 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).
*/
Param *
generate_new_exec_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
assign_special_exec_param(PlannerInfo *root)
{
int paramId = list_length(root->glob->paramExecTypes);
root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes,
InvalidOid);
return paramId;
}

View File

@ -0,0 +1,34 @@
/*-------------------------------------------------------------------------
*
* paramassign.h
* Functions for assigning PARAM_EXEC slots during planning.
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/optimizer/paramassign.h
*
*-------------------------------------------------------------------------
*/
#ifndef PARAMASSIGN_H
#define PARAMASSIGN_H
#include "nodes/relation.h"
extern Param *replace_outer_var(PlannerInfo *root, Var *var);
extern Param *replace_outer_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
extern Param *replace_outer_agg(PlannerInfo *root, Aggref *agg);
extern Param *replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp);
extern Param *replace_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *replace_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
extern void process_subquery_nestloop_params(PlannerInfo *root,
List *subplan_params);
extern List *identify_current_nestloop_params(PlannerInfo *root,
Relids leftrelids);
extern Param *generate_new_exec_param(PlannerInfo *root, Oid paramtype,
int32 paramtypmod, Oid paramcollation);
extern int assign_special_exec_param(PlannerInfo *root);
#endif /* PARAMASSIGN_H */

View File

@ -1,6 +1,7 @@
/*-------------------------------------------------------------------------
*
* subselect.h
* Planning routines for subselects.
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@ -35,9 +36,5 @@ extern Param *SS_make_initplan_output_param(PlannerInfo *root,
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Param *prm);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);
extern int SS_assign_special_param(PlannerInfo *root);
#endif /* SUBSELECT_H */