588 lines
18 KiB
C
588 lines
18 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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-2023, 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 varnosyn,
|
|
* varattnosyn, and location, so this does too.
|
|
*/
|
|
if (pvar->varno == var->varno &&
|
|
pvar->varattno == var->varattno &&
|
|
pvar->vartype == var->vartype &&
|
|
pvar->vartypmod == var->vartypmod &&
|
|
pvar->varcollid == var->varcollid)
|
|
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 = lfirst_node(PlannerParamItem, lc);
|
|
|
|
if (IsA(pitem->item, Var))
|
|
{
|
|
Var *var = (Var *) pitem->item;
|
|
NestLoopParam *nlp;
|
|
ListCell *lc2;
|
|
|
|
/* 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(lc2, root->curOuterParams)
|
|
{
|
|
nlp = (NestLoopParam *) lfirst(lc2);
|
|
if (nlp->paramno == pitem->paramId)
|
|
{
|
|
Assert(equal(var, nlp->paramval));
|
|
/* Present, so nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
if (lc2 == 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 *lc2;
|
|
|
|
/* If not from a nestloop outer rel, complain */
|
|
if (!bms_is_subset(find_placeholder_info(root, phv)->ph_eval_at,
|
|
root->curOuterRels))
|
|
elog(ERROR, "non-LATERAL parameter required by subquery");
|
|
|
|
/* Is this param already listed in root->curOuterParams? */
|
|
foreach(lc2, root->curOuterParams)
|
|
{
|
|
nlp = (NestLoopParam *) lfirst(lc2);
|
|
if (nlp->paramno == pitem->paramId)
|
|
{
|
|
Assert(equal(phv, nlp->paramval));
|
|
/* Present, so nothing to do */
|
|
break;
|
|
}
|
|
}
|
|
if (lc2 == 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;
|
|
|
|
result = NIL;
|
|
foreach(cell, root->curOuterParams)
|
|
{
|
|
NestLoopParam *nlp = (NestLoopParam *) lfirst(cell);
|
|
|
|
/*
|
|
* We are looking for Vars and PHVs that can be supplied by the
|
|
* lefthand rels.
|
|
*/
|
|
if (IsA(nlp->paramval, Var) &&
|
|
bms_is_member(nlp->paramval->varno, leftrelids))
|
|
{
|
|
root->curOuterParams = foreach_delete_current(root->curOuterParams,
|
|
cell);
|
|
result = lappend(result, nlp);
|
|
}
|
|
else if (IsA(nlp->paramval, PlaceHolderVar) &&
|
|
bms_is_subset(find_placeholder_info(root,
|
|
(PlaceHolderVar *) nlp->paramval)->ph_eval_at,
|
|
leftrelids))
|
|
{
|
|
root->curOuterParams = foreach_delete_current(root->curOuterParams,
|
|
cell);
|
|
result = lappend(result, nlp);
|
|
}
|
|
}
|
|
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;
|
|
}
|