/* we have static list of params, so no hooks needed */
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
+ paramLI->paramCompile = NULL;
+ paramLI->paramCompileArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = num_params;
- paramLI->paramMask = NULL;
i = 0;
foreach(l, exprstates)
if (paramInfo &&
paramId > 0 && paramId <= paramInfo->numParams)
{
- ParamExternData *prm = ¶mInfo->params[paramId - 1];
+ ParamExternData *prm;
+ ParamExternData prmdata;
/* give hook a chance in case parameter is dynamic */
- if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
- paramInfo->paramFetch(paramInfo, paramId);
+ if (paramInfo->paramFetch != NULL)
+ prm = paramInfo->paramFetch(paramInfo, paramId, false, &prmdata);
+ else
+ prm = ¶mInfo->params[paramId - 1];
if (OidIsValid(prm->ptype) && !prm->isnull)
{
} LastAttnumInfo;
static void ExecReadyExpr(ExprState *state);
-static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
+static void ExecInitExprRec(Expr *node, ExprState *state,
Datum *resv, bool *resnull);
-static void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
- Oid funcid, Oid inputcollid, PlanState *parent,
+ Oid funcid, Oid inputcollid,
ExprState *state);
static void ExecInitExprSlots(ExprState *state, Node *node);
static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
- PlanState *parent);
+ ExprState *state);
static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
- PlanState *parent, ExprState *state,
+ ExprState *state,
Datum *resv, bool *resnull);
static bool isAssignmentIndirectionExpr(Expr *expr);
static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
- PlanState *parent, ExprState *state,
+ ExprState *state,
Datum *resv, bool *resnull);
/* Initialize ExprState with empty step list */
state = makeNode(ExprState);
state->expr = node;
+ state->parent = parent;
+ state->ext_params = NULL;
/* Insert EEOP_*_FETCHSOME steps as needed */
ExecInitExprSlots(state, (Node *) node);
/* Compile the expression proper */
- ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
+/*
+ * ExecInitExprWithParams: prepare a standalone expression tree for execution
+ *
+ * This is the same as ExecInitExpr, except that there is no parent PlanState,
+ * and instead we may have a ParamListInfo describing PARAM_EXTERN Params.
+ */
+ExprState *
+ExecInitExprWithParams(Expr *node, ParamListInfo ext_params)
+{
+ ExprState *state;
+ ExprEvalStep scratch;
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+ state->parent = NULL;
+ state->ext_params = ext_params;
+
+ /* Insert EEOP_*_FETCHSOME steps as needed */
+ ExecInitExprSlots(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
/* Finally, append a DONE step */
scratch.opcode = EEOP_DONE;
state = makeNode(ExprState);
state->expr = (Expr *) qual;
+ state->parent = parent;
+ state->ext_params = NULL;
+
/* mark expression as to be used with ExecQual() */
state->flags = EEO_FLAG_IS_QUAL;
Expr *node = (Expr *) lfirst(lc);
/* first evaluate expression */
- ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
+ ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
/* then emit EEOP_QUAL to detect if it's false (or null) */
scratch.d.qualexpr.jumpdone = -1;
projInfo->pi_state.tag.type = T_ExprState;
state = &projInfo->pi_state;
state->expr = (Expr *) targetList;
+ state->parent = parent;
+ state->ext_params = NULL;
+
state->resultslot = slot;
/* Insert EEOP_*_FETCHSOME steps as needed */
* matter) can change between executions. We instead evaluate
* into the ExprState's resvalue/resnull and then move.
*/
- ExecInitExprRec(tle->expr, parent, state,
+ ExecInitExprRec(tle->expr, state,
&state->resvalue, &state->resnull);
/*
* possibly recursing into sub-expressions of node.
*
* node - expression to evaluate
- * parent - parent executor node (or NULL if a standalone expression)
* state - ExprState to whose ->steps to append the necessary operations
* resv / resnull - where to store the result of the node into
*/
static void
-ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
+ExecInitExprRec(Expr *node, ExprState *state,
Datum *resv, bool *resnull)
{
ExprEvalStep scratch;
if (variable->varattno == InvalidAttrNumber)
{
/* whole-row Var */
- ExecInitWholeRowVar(&scratch, variable, parent);
+ ExecInitWholeRowVar(&scratch, variable, state);
}
else if (variable->varattno <= 0)
{
case T_Param:
{
Param *param = (Param *) node;
+ ParamListInfo params;
switch (param->paramkind)
{
scratch.opcode = EEOP_PARAM_EXEC;
scratch.d.param.paramid = param->paramid;
scratch.d.param.paramtype = param->paramtype;
+ ExprEvalPushStep(state, &scratch);
break;
case PARAM_EXTERN:
- scratch.opcode = EEOP_PARAM_EXTERN;
- scratch.d.param.paramid = param->paramid;
- scratch.d.param.paramtype = param->paramtype;
+
+ /*
+ * If we have a relevant ParamCompileHook, use it;
+ * otherwise compile a standard EEOP_PARAM_EXTERN
+ * step. ext_params, if supplied, takes precedence
+ * over info from the parent node's EState (if any).
+ */
+ if (state->ext_params)
+ params = state->ext_params;
+ else if (state->parent &&
+ state->parent->state)
+ params = state->parent->state->es_param_list_info;
+ else
+ params = NULL;
+ if (params && params->paramCompile)
+ {
+ params->paramCompile(params, param, state,
+ resv, resnull);
+ }
+ else
+ {
+ scratch.opcode = EEOP_PARAM_EXTERN;
+ scratch.d.param.paramid = param->paramid;
+ scratch.d.param.paramtype = param->paramtype;
+ ExprEvalPushStep(state, &scratch);
+ }
break;
default:
elog(ERROR, "unrecognized paramkind: %d",
(int) param->paramkind);
break;
}
-
- ExprEvalPushStep(state, &scratch);
break;
}
scratch.d.aggref.astate = astate;
astate->aggref = aggref;
- if (parent && IsA(parent, AggState))
+ if (state->parent && IsA(state->parent, AggState))
{
- AggState *aggstate = (AggState *) parent;
+ AggState *aggstate = (AggState *) state->parent;
aggstate->aggs = lcons(astate, aggstate->aggs);
aggstate->numaggs++;
GroupingFunc *grp_node = (GroupingFunc *) node;
Agg *agg;
- if (!parent || !IsA(parent, AggState) ||
- !IsA(parent->plan, Agg))
+ if (!state->parent || !IsA(state->parent, AggState) ||
+ !IsA(state->parent->plan, Agg))
elog(ERROR, "GroupingFunc found in non-Agg plan node");
scratch.opcode = EEOP_GROUPING_FUNC;
- scratch.d.grouping_func.parent = (AggState *) parent;
+ scratch.d.grouping_func.parent = (AggState *) state->parent;
- agg = (Agg *) (parent->plan);
+ agg = (Agg *) (state->parent->plan);
if (agg->groupingSets)
scratch.d.grouping_func.clauses = grp_node->cols;
wfstate->wfunc = wfunc;
- if (parent && IsA(parent, WindowAggState))
+ if (state->parent && IsA(state->parent, WindowAggState))
{
- WindowAggState *winstate = (WindowAggState *) parent;
+ WindowAggState *winstate = (WindowAggState *) state->parent;
int nfuncs;
winstate->funcs = lcons(wfstate, winstate->funcs);
winstate->numaggs++;
/* for now initialize agg using old style expressions */
- wfstate->args = ExecInitExprList(wfunc->args, parent);
+ wfstate->args = ExecInitExprList(wfunc->args,
+ state->parent);
wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
- parent);
+ state->parent);
/*
* Complain if the windowfunc's arguments contain any
{
ArrayRef *aref = (ArrayRef *) node;
- ExecInitArrayRef(&scratch, aref, parent, state, resv, resnull);
+ ExecInitArrayRef(&scratch, aref, state, resv, resnull);
break;
}
ExecInitFunc(&scratch, node,
func->args, func->funcid, func->inputcollid,
- parent, state);
+ state);
ExprEvalPushStep(state, &scratch);
break;
}
ExecInitFunc(&scratch, node,
op->args, op->opfuncid, op->inputcollid,
- parent, state);
+ state);
ExprEvalPushStep(state, &scratch);
break;
}
ExecInitFunc(&scratch, node,
op->args, op->opfuncid, op->inputcollid,
- parent, state);
+ state);
/*
* Change opcode of call instruction to EEOP_DISTINCT.
ExecInitFunc(&scratch, node,
op->args, op->opfuncid, op->inputcollid,
- parent, state);
+ state);
/*
* Change opcode of call instruction to EEOP_NULLIF.
opexpr->inputcollid, NULL, NULL);
/* Evaluate scalar directly into left function argument */
- ExecInitExprRec(scalararg, parent, state,
+ ExecInitExprRec(scalararg, state,
&fcinfo->arg[0], &fcinfo->argnull[0]);
/*
* be overwritten by EEOP_SCALARARRAYOP, and will not be
* passed to any other expression.
*/
- ExecInitExprRec(arrayarg, parent, state, resv, resnull);
+ ExecInitExprRec(arrayarg, state, resv, resnull);
/* And perform the operation */
scratch.opcode = EEOP_SCALARARRAYOP;
Expr *arg = (Expr *) lfirst(lc);
/* Evaluate argument into our output variable */
- ExecInitExprRec(arg, parent, state, resv, resnull);
+ ExecInitExprRec(arg, state, resv, resnull);
/* Perform the appropriate step type */
switch (boolexpr->boolop)
SubPlan *subplan = (SubPlan *) node;
SubPlanState *sstate;
- if (!parent)
+ if (!state->parent)
elog(ERROR, "SubPlan found with no parent plan");
- sstate = ExecInitSubPlan(subplan, parent);
+ sstate = ExecInitSubPlan(subplan, state->parent);
- /* add SubPlanState nodes to parent->subPlan */
- parent->subPlan = lappend(parent->subPlan, sstate);
+ /* add SubPlanState nodes to state->parent->subPlan */
+ state->parent->subPlan = lappend(state->parent->subPlan,
+ sstate);
scratch.opcode = EEOP_SUBPLAN;
scratch.d.subplan.sstate = sstate;
AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
AlternativeSubPlanState *asstate;
- if (!parent)
+ if (!state->parent)
elog(ERROR, "AlternativeSubPlan found with no parent plan");
- asstate = ExecInitAlternativeSubPlan(asplan, parent);
+ asstate = ExecInitAlternativeSubPlan(asplan, state->parent);
scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
scratch.d.alternative_subplan.asstate = asstate;
FieldSelect *fselect = (FieldSelect *) node;
/* evaluate row/record argument into result area */
- ExecInitExprRec(fselect->arg, parent, state, resv, resnull);
+ ExecInitExprRec(fselect->arg, state, resv, resnull);
/* and extract field */
scratch.opcode = EEOP_FIELDSELECT;
*descp = NULL;
/* emit code to evaluate the composite input value */
- ExecInitExprRec(fstore->arg, parent, state, resv, resnull);
+ ExecInitExprRec(fstore->arg, state, resv, resnull);
/* next, deform the input tuple into our workspace */
scratch.opcode = EEOP_FIELDSTORE_DEFORM;
state->innermost_caseval = &values[fieldnum - 1];
state->innermost_casenull = &nulls[fieldnum - 1];
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&values[fieldnum - 1],
&nulls[fieldnum - 1]);
/* relabel doesn't need to do anything at runtime */
RelabelType *relabel = (RelabelType *) node;
- ExecInitExprRec(relabel->arg, parent, state, resv, resnull);
+ ExecInitExprRec(relabel->arg, state, resv, resnull);
break;
}
FunctionCallInfo fcinfo_in;
/* evaluate argument into step's result area */
- ExecInitExprRec(iocoerce->arg, parent, state, resv, resnull);
+ ExecInitExprRec(iocoerce->arg, state, resv, resnull);
/*
* Prepare both output and input function calls, to be
ExprState *elemstate;
/* evaluate argument into step's result area */
- ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
+ ExecInitExprRec(acoerce->arg, state, resv, resnull);
resultelemtype = get_element_type(acoerce->resulttype);
if (!OidIsValid(resultelemtype))
*/
elemstate = makeNode(ExprState);
elemstate->expr = acoerce->elemexpr;
+ elemstate->parent = state->parent;
+ elemstate->ext_params = state->ext_params;
+
elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
- ExecInitExprRec(acoerce->elemexpr, parent, elemstate,
+ ExecInitExprRec(acoerce->elemexpr, elemstate,
&elemstate->resvalue, &elemstate->resnull);
if (elemstate->steps_len == 1 &&
ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
/* evaluate argument into step's result area */
- ExecInitExprRec(convert->arg, parent, state, resv, resnull);
+ ExecInitExprRec(convert->arg, state, resv, resnull);
/* and push conversion step */
scratch.opcode = EEOP_CONVERT_ROWTYPE;
caseval = palloc(sizeof(Datum));
casenull = palloc(sizeof(bool));
- ExecInitExprRec(caseExpr->arg, parent, state,
+ ExecInitExprRec(caseExpr->arg, state,
caseval, casenull);
/*
state->innermost_casenull = casenull;
/* evaluate condition into CASE's result variables */
- ExecInitExprRec(when->expr, parent, state, resv, resnull);
+ ExecInitExprRec(when->expr, state, resv, resnull);
state->innermost_caseval = save_innermost_caseval;
state->innermost_casenull = save_innermost_casenull;
* If WHEN result is true, evaluate THEN result, storing
* it into the CASE's result variables.
*/
- ExecInitExprRec(when->result, parent, state, resv, resnull);
+ ExecInitExprRec(when->result, state, resv, resnull);
/* Emit JUMP step to jump to end of CASE's code */
scratch.opcode = EEOP_JUMP;
Assert(caseExpr->defresult);
/* evaluate ELSE expr into CASE's result variables */
- ExecInitExprRec(caseExpr->defresult, parent, state,
+ ExecInitExprRec(caseExpr->defresult, state,
resv, resnull);
/* adjust jump targets */
{
Expr *e = (Expr *) lfirst(lc);
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&scratch.d.arrayexpr.elemvalues[elemoff],
&scratch.d.arrayexpr.elemnulls[elemoff]);
elemoff++;
}
/* Evaluate column expr into appropriate workspace slot */
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&scratch.d.row.elemvalues[i],
&scratch.d.row.elemnulls[i]);
i++;
*/
/* evaluate left and right args directly into fcinfo */
- ExecInitExprRec(left_expr, parent, state,
+ ExecInitExprRec(left_expr, state,
&fcinfo->arg[0], &fcinfo->argnull[0]);
- ExecInitExprRec(right_expr, parent, state,
+ ExecInitExprRec(right_expr, state,
&fcinfo->arg[1], &fcinfo->argnull[1]);
scratch.opcode = EEOP_ROWCOMPARE_STEP;
Expr *e = (Expr *) lfirst(lc);
/* evaluate argument, directly into result datum */
- ExecInitExprRec(e, parent, state, resv, resnull);
+ ExecInitExprRec(e, state, resv, resnull);
/* if it's not null, skip to end of COALESCE expr */
scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
{
Expr *e = (Expr *) lfirst(lc);
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&scratch.d.minmax.values[off],
&scratch.d.minmax.nulls[off]);
off++;
{
Expr *e = (Expr *) lfirst(arg);
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&scratch.d.xmlexpr.named_argvalue[off],
&scratch.d.xmlexpr.named_argnull[off]);
off++;
{
Expr *e = (Expr *) lfirst(arg);
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&scratch.d.xmlexpr.argvalue[off],
&scratch.d.xmlexpr.argnull[off]);
off++;
scratch.d.nulltest_row.argdesc = NULL;
/* first evaluate argument into result variable */
- ExecInitExprRec(ntest->arg, parent, state,
+ ExecInitExprRec(ntest->arg, state,
resv, resnull);
/* then push the test of that argument */
* and will get overwritten by the below EEOP_BOOLTEST_IS_*
* step.
*/
- ExecInitExprRec(btest->arg, parent, state, resv, resnull);
+ ExecInitExprRec(btest->arg, state, resv, resnull);
switch (btest->booltesttype)
{
{
CoerceToDomain *ctest = (CoerceToDomain *) node;
- ExecInitCoerceToDomain(&scratch, ctest, parent, state,
+ ExecInitCoerceToDomain(&scratch, ctest, state,
resv, resnull);
break;
}
* Note that this potentially re-allocates es->steps, therefore no pointer
* into that array may be used while the expression is still being built.
*/
-static void
+void
ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
{
if (es->steps_alloc == 0)
*/
static void
ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
- Oid inputcollid, PlanState *parent, ExprState *state)
+ Oid inputcollid, ExprState *state)
{
int nargs = list_length(args);
AclResult aclresult;
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set"),
- parent ? executor_errposition(parent->state,
- exprLocation((Node *) node)) : 0));
+ state->parent ?
+ executor_errposition(state->parent->state,
+ exprLocation((Node *) node)) : 0));
/* Build code to evaluate arguments directly into the fcinfo struct */
argno = 0;
}
else
{
- ExecInitExprRec(arg, parent, state,
+ ExecInitExprRec(arg, state,
&fcinfo->arg[argno], &fcinfo->argnull[argno]);
}
argno++;
* The caller still has to push the step.
*/
static void
-ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent)
+ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state)
{
+ PlanState *parent = state->parent;
+
/* fill in all but the target */
scratch->opcode = EEOP_WHOLEROW;
scratch->d.wholerow.var = variable;
* Prepare evaluation of an ArrayRef expression.
*/
static void
-ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
+ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
ExprState *state, Datum *resv, bool *resnull)
{
bool isAssignment = (aref->refassgnexpr != NULL);
* be overwritten by the final EEOP_ARRAYREF_FETCH/ASSIGN step, which is
* pushed last.
*/
- ExecInitExprRec(aref->refexpr, parent, state, resv, resnull);
+ ExecInitExprRec(aref->refexpr, state, resv, resnull);
/*
* If refexpr yields NULL, and it's a fetch, then result is NULL. We can
arefstate->upperprovided[i] = true;
/* Each subscript is evaluated into subscriptvalue/subscriptnull */
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&arefstate->subscriptvalue, &arefstate->subscriptnull);
/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
arefstate->lowerprovided[i] = true;
/* Each subscript is evaluated into subscriptvalue/subscriptnull */
- ExecInitExprRec(e, parent, state,
+ ExecInitExprRec(e, state,
&arefstate->subscriptvalue, &arefstate->subscriptnull);
/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
state->innermost_casenull = &arefstate->prevnull;
/* evaluate replacement value into replacevalue/replacenull */
- ExecInitExprRec(aref->refassgnexpr, parent, state,
+ ExecInitExprRec(aref->refassgnexpr, state,
&arefstate->replacevalue, &arefstate->replacenull);
state->innermost_caseval = save_innermost_caseval;
*/
static void
ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
- PlanState *parent, ExprState *state,
- Datum *resv, bool *resnull)
+ ExprState *state, Datum *resv, bool *resnull)
{
ExprEvalStep scratch2;
DomainConstraintRef *constraint_ref;
* if there's constraint failures there'll be errors, otherwise it's what
* needs to be returned.
*/
- ExecInitExprRec(ctest->arg, parent, state, resv, resnull);
+ ExecInitExprRec(ctest->arg, state, resv, resnull);
/*
* Note: if the argument is of varlena type, it could be a R/W expanded
state->innermost_domainnull = domainnull;
/* evaluate check expression value */
- ExecInitExprRec(con->check_expr, parent, state,
+ ExecInitExprRec(con->check_expr, state,
scratch->d.domaincheck.checkvalue,
scratch->d.domaincheck.checknull);
&&CASE_EEOP_BOOLTEST_IS_NOT_FALSE,
&&CASE_EEOP_PARAM_EXEC,
&&CASE_EEOP_PARAM_EXTERN,
+ &&CASE_EEOP_PARAM_CALLBACK,
&&CASE_EEOP_CASE_TESTVAL,
&&CASE_EEOP_MAKE_READONLY,
&&CASE_EEOP_IOCOERCE,
EEO_NEXT();
}
+ EEO_CASE(EEOP_PARAM_CALLBACK)
+ {
+ /* allow an extension module to supply a PARAM_EXTERN value */
+ op->d.cparam.paramfunc(state, op, econtext);
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_CASE_TESTVAL)
{
/*
if (likely(paramInfo &&
paramId > 0 && paramId <= paramInfo->numParams))
{
- ParamExternData *prm = ¶mInfo->params[paramId - 1];
+ ParamExternData *prm;
+ ParamExternData prmdata;
/* give hook a chance in case parameter is dynamic */
- if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
- paramInfo->paramFetch(paramInfo, paramId);
+ if (paramInfo->paramFetch != NULL)
+ prm = paramInfo->paramFetch(paramInfo, paramId, false, &prmdata);
+ else
+ prm = ¶mInfo->params[paramId - 1];
if (likely(OidIsValid(prm->ptype)))
{
/* we have static list of params, so no hooks needed */
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
+ paramLI->paramCompile = NULL;
+ paramLI->paramCompileArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = nargs;
- paramLI->paramMask = NULL;
fcache->paramLI = paramLI;
}
else
/* we have static list of params, so no hooks needed */
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
+ paramLI->paramCompile = NULL;
+ paramLI->paramCompileArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = nargs;
- paramLI->paramMask = NULL;
for (i = 0; i < nargs; i++)
{
retval = (ParamListInfo) palloc(size);
retval->paramFetch = NULL;
retval->paramFetchArg = NULL;
+ retval->paramCompile = NULL;
+ retval->paramCompileArg = NULL;
retval->parserSetup = NULL;
retval->parserSetupArg = NULL;
retval->numParams = from->numParams;
- retval->paramMask = NULL;
for (i = 0; i < from->numParams; i++)
{
- ParamExternData *oprm = &from->params[i];
+ ParamExternData *oprm;
ParamExternData *nprm = &retval->params[i];
+ ParamExternData prmdata;
int16 typLen;
bool typByVal;
- /* Ignore parameters we don't need, to save cycles and space. */
- if (from->paramMask != NULL &&
- !bms_is_member(i, from->paramMask))
- {
- nprm->value = (Datum) 0;
- nprm->isnull = true;
- nprm->pflags = 0;
- nprm->ptype = InvalidOid;
- continue;
- }
-
/* give hook a chance in case parameter is dynamic */
- if (!OidIsValid(oprm->ptype) && from->paramFetch != NULL)
- from->paramFetch(from, i + 1);
+ if (from->paramFetch != NULL)
+ oprm = from->paramFetch(from, i + 1, false, &prmdata);
+ else
+ oprm = &from->params[i];
/* flat-copy the parameter info */
*nprm = *oprm;
for (i = 0; i < paramLI->numParams; i++)
{
- ParamExternData *prm = ¶mLI->params[i];
+ ParamExternData *prm;
+ ParamExternData prmdata;
Oid typeOid;
int16 typLen;
bool typByVal;
- /* Ignore parameters we don't need, to save cycles and space. */
- if (paramLI->paramMask != NULL &&
- !bms_is_member(i, paramLI->paramMask))
- typeOid = InvalidOid;
+ /* give hook a chance in case parameter is dynamic */
+ if (paramLI->paramFetch != NULL)
+ prm = paramLI->paramFetch(paramLI, i + 1, false, &prmdata);
else
- {
- /* give hook a chance in case parameter is dynamic */
- if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL)
- paramLI->paramFetch(paramLI, i + 1);
- typeOid = prm->ptype;
- }
+ prm = ¶mLI->params[i];
+
+ typeOid = prm->ptype;
sz = add_size(sz, sizeof(Oid)); /* space for type OID */
sz = add_size(sz, sizeof(uint16)); /* space for pflags */
/* Write each parameter in turn. */
for (i = 0; i < nparams; i++)
{
- ParamExternData *prm = ¶mLI->params[i];
+ ParamExternData *prm;
+ ParamExternData prmdata;
Oid typeOid;
int16 typLen;
bool typByVal;
- /* Ignore parameters we don't need, to save cycles and space. */
- if (paramLI->paramMask != NULL &&
- !bms_is_member(i, paramLI->paramMask))
- typeOid = InvalidOid;
+ /* give hook a chance in case parameter is dynamic */
+ if (paramLI->paramFetch != NULL)
+ prm = paramLI->paramFetch(paramLI, i + 1, false, &prmdata);
else
- {
- /* give hook a chance in case parameter is dynamic */
- if (!OidIsValid(prm->ptype) && paramLI->paramFetch != NULL)
- paramLI->paramFetch(paramLI, i + 1);
- typeOid = prm->ptype;
- }
+ prm = ¶mLI->params[i];
+
+ typeOid = prm->ptype;
/* Write type OID. */
memcpy(*start_address, &typeOid, sizeof(Oid));
paramLI = (ParamListInfo) palloc(size);
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
+ paramLI->paramCompile = NULL;
+ paramLI->paramCompileArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = nparams;
- paramLI->paramMask = NULL;
for (i = 0; i < nparams; i++)
{
case T_Param:
{
Param *param = (Param *) node;
+ ParamListInfo paramLI = context->boundParams;
/* Look to see if we've been given a value for this Param */
if (param->paramkind == PARAM_EXTERN &&
- context->boundParams != NULL &&
+ paramLI != NULL &&
param->paramid > 0 &&
- param->paramid <= context->boundParams->numParams)
+ param->paramid <= paramLI->numParams)
{
- ParamExternData *prm = &context->boundParams->params[param->paramid - 1];
+ ParamExternData *prm;
+ ParamExternData prmdata;
+
+ /*
+ * Give hook a chance in case parameter is dynamic. Tell
+ * it that this fetch is speculative, so it should avoid
+ * erroring out if parameter is unavailable.
+ */
+ if (paramLI->paramFetch != NULL)
+ prm = paramLI->paramFetch(paramLI, param->paramid,
+ true, &prmdata);
+ else
+ prm = ¶mLI->params[param->paramid - 1];
if (OidIsValid(prm->ptype))
{
/* we have static list of params, so no hooks needed */
params->paramFetch = NULL;
params->paramFetchArg = NULL;
+ params->paramCompile = NULL;
+ params->paramCompileArg = NULL;
params->parserSetup = NULL;
params->parserSetupArg = NULL;
params->numParams = numParams;
- params->paramMask = NULL;
for (paramno = 0; paramno < numParams; paramno++)
{
MemoryContext oldcontext;
int paramno;
+ /* This code doesn't support dynamic param lists */
+ Assert(params->paramFetch == NULL);
+
/* Make sure any trash is generated in MessageContext */
oldcontext = MemoryContextSwitchTo(MessageContext);
#include "nodes/execnodes.h"
-/* forward reference to avoid circularity */
+/* forward references to avoid circularity */
+struct ExprEvalStep;
struct ArrayRefState;
/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
/* jump-threading is in use */
#define EEO_FLAG_DIRECT_THREADED (1 << 2)
+/* Typical API for out-of-line evaluation subroutines */
+typedef void (*ExecEvalSubroutine) (ExprState *state,
+ struct ExprEvalStep *op,
+ ExprContext *econtext);
+
/*
* Discriminator for ExprEvalSteps.
*
/* evaluate PARAM_EXEC/EXTERN parameters */
EEOP_PARAM_EXEC,
EEOP_PARAM_EXTERN,
+ EEOP_PARAM_CALLBACK,
/* return CaseTestExpr value */
EEOP_CASE_TESTVAL,
Oid paramtype; /* OID of parameter's datatype */
} param;
+ /* for EEOP_PARAM_CALLBACK */
+ struct
+ {
+ ExecEvalSubroutine paramfunc; /* add-on evaluation subroutine */
+ void *paramarg; /* private data for same */
+ int paramid; /* numeric ID for parameter */
+ Oid paramtype; /* OID of parameter's datatype */
+ } cparam;
+
/* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
struct
{
} ArrayRefState;
-extern void ExecReadyInterpretedExpr(ExprState *state);
+/* functions in execExpr.c */
+extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
+/* functions in execExprInterp.c */
+extern void ExecReadyInterpretedExpr(ExprState *state);
extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
/*
* prototypes from functions in execExpr.c
*/
extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params);
extern ExprState *ExecInitQual(List *qual, PlanState *parent);
extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
extern List *ExecInitExprList(List *nodes, PlanState *parent);
#include "storage/condition_variable.h"
+struct PlanState; /* forward references in this file */
+struct ParallelHashJoinState;
+struct ExprState;
+struct ExprContext;
+struct ExprEvalStep; /* avoid including execExpr.h everywhere */
+
+
/* ----------------
* ExprState node
*
* It contains instructions (in ->steps) to evaluate the expression.
* ----------------
*/
-struct ExprState; /* forward references in this file */
-struct ExprContext;
-struct ExprEvalStep; /* avoid including execExpr.h everywhere */
-
-struct ParallelHashJoinState;
-
typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
struct ExprContext *econtext,
bool *isNull);
Expr *expr;
/*
- * XXX: following only needed during "compilation", could be thrown away.
+ * XXX: following fields only needed during "compilation" (ExecInitExpr);
+ * could be thrown away afterwards.
*/
int steps_len; /* number of steps currently */
int steps_alloc; /* allocated length of steps array */
+ struct PlanState *parent; /* parent PlanState node, if any */
+ ParamListInfo ext_params; /* for compiling PARAM_EXTERN nodes */
+
Datum *innermost_caseval;
bool *innermost_casenull;
* ----------------------------------------------------------------
*/
-struct PlanState;
-
/* ----------------
* ExecProcNodeMtd
*
/* Forward declarations, to avoid including other headers */
struct Bitmapset;
+struct ExprState;
+struct Param;
struct ParseState;
-/* ----------------
+/*
* ParamListInfo
*
- * ParamListInfo arrays are used to pass parameters into the executor
- * for parameterized plans. Each entry in the array defines the value
- * to be substituted for a PARAM_EXTERN parameter. The "paramid"
- * of a PARAM_EXTERN Param can range from 1 to numParams.
+ * ParamListInfo structures are used to pass parameters into the executor
+ * for parameterized plans. We support two basic approaches to supplying
+ * parameter values, the "static" way and the "dynamic" way.
+ *
+ * In the static approach, per-parameter data is stored in an array of
+ * ParamExternData structs appended to the ParamListInfo struct.
+ * Each entry in the array defines the value to be substituted for a
+ * PARAM_EXTERN parameter. The "paramid" of a PARAM_EXTERN Param
+ * can range from 1 to numParams.
*
* Although parameter numbers are normally consecutive, we allow
* ptype == InvalidOid to signal an unused array entry.
* as a constant (i.e., generate a plan that works only for this value
* of the parameter).
*
- * There are two hook functions that can be associated with a ParamListInfo
- * array to support dynamic parameter handling. First, if paramFetch
- * isn't null and the executor requires a value for an invalid parameter
- * (one with ptype == InvalidOid), the paramFetch hook is called to give
- * it a chance to fill in the parameter value. Second, a parserSetup
- * hook can be supplied to re-instantiate the original parsing hooks if
- * a query needs to be re-parsed/planned (as a substitute for supposing
- * that the current ptype values represent a fixed set of parameter types).
-
+ * In the dynamic approach, all access to parameter values is done through
+ * hook functions found in the ParamListInfo struct. In this case,
+ * the ParamExternData array is typically unused and not allocated;
+ * but the legal range of paramid is still 1 to numParams.
+ *
* Although the data structure is really an array, not a list, we keep
* the old typedef name to avoid unnecessary code changes.
- * ----------------
+ *
+ * There are 3 hook functions that can be associated with a ParamListInfo
+ * structure:
+ *
+ * If paramFetch isn't null, it is called to fetch the ParamExternData
+ * for a particular param ID, rather than accessing the relevant element
+ * of the ParamExternData array. This supports the case where the array
+ * isn't there at all, as well as cases where the data in the array
+ * might be obsolete or lazily evaluated. paramFetch must return the
+ * address of a ParamExternData struct describing the specified param ID;
+ * the convention above about ptype == InvalidOid signaling an invalid
+ * param ID still applies. The returned struct can either be placed in
+ * the "workspace" supplied by the caller, or it can be in storage
+ * controlled by the paramFetch hook if that's more convenient.
+ * (In either case, the struct is not expected to be long-lived.)
+ * If "speculative" is true, the paramFetch hook should not risk errors
+ * in trying to fetch the parameter value, and should report an invalid
+ * parameter instead.
+ *
+ * If paramCompile isn't null, then it controls what execExpr.c compiles
+ * for PARAM_EXTERN Param nodes --- typically, this hook would emit a
+ * EEOP_PARAM_CALLBACK step. This allows unnecessary work to be
+ * optimized away in compiled expressions.
+ *
+ * If parserSetup isn't null, then it is called to re-instantiate the
+ * original parsing hooks when a query needs to be re-parsed/planned.
+ * This is especially useful if the types of parameters might change
+ * from time to time, since it can replace the need to supply a fixed
+ * list of parameter types to the parser.
+ *
+ * Notice that the paramFetch and paramCompile hooks are actually passed
+ * the ParamListInfo struct's address; they can therefore access all
+ * three of the "arg" fields, and the distinction between paramFetchArg
+ * and paramCompileArg is rather arbitrary.
*/
#define PARAM_FLAG_CONST 0x0001 /* parameter is constant */
typedef struct ParamListInfoData *ParamListInfo;
-typedef void (*ParamFetchHook) (ParamListInfo params, int paramid);
+typedef ParamExternData *(*ParamFetchHook) (ParamListInfo params,
+ int paramid, bool speculative,
+ ParamExternData *workspace);
+
+typedef void (*ParamCompileHook) (ParamListInfo params, struct Param *param,
+ struct ExprState *state,
+ Datum *resv, bool *resnull);
typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
{
ParamFetchHook paramFetch; /* parameter fetch hook */
void *paramFetchArg;
+ ParamCompileHook paramCompile; /* parameter compile hook */
+ void *paramCompileArg;
ParserSetupHook parserSetup; /* parser setup hook */
void *parserSetupArg;
- int numParams; /* number of ParamExternDatas following */
- struct Bitmapset *paramMask; /* if non-NULL, can ignore omitted params */
+ int numParams; /* nominal/maximum # of Params represented */
+
+ /*
+ * params[] may be of length zero if paramFetch is supplied; otherwise it
+ * must be of length numParams.
+ */
ParamExternData params[FLEXIBLE_ARRAY_MEMBER];
} ParamListInfoData;
/* ----------
* plpgsql_finish_datums Copy completed datum info into function struct.
- *
- * This is also responsible for building resettable_datums, a bitmapset
- * of the dnos of all ROW, REC, and RECFIELD datums in the function.
* ----------
*/
static void
plpgsql_finish_datums(PLpgSQL_function *function)
{
- Bitmapset *resettable_datums = NULL;
int i;
function->ndatums = plpgsql_nDatums;
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
for (i = 0; i < plpgsql_nDatums; i++)
- {
function->datums[i] = plpgsql_Datums[i];
- switch (function->datums[i]->dtype)
- {
- case PLPGSQL_DTYPE_ROW:
- case PLPGSQL_DTYPE_REC:
- case PLPGSQL_DTYPE_RECFIELD:
- resettable_datums = bms_add_member(resettable_datums, i);
- break;
-
- default:
- break;
- }
- }
- function->resettable_datums = resettable_datums;
}
#include "access/tupconvert.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "miscadmin.h"
Portal portal, bool prefetch_ok);
static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr);
-static ParamListInfo setup_unshared_param_list(PLpgSQL_execstate *estate,
- PLpgSQL_expr *expr);
-static void plpgsql_param_fetch(ParamListInfo params, int paramid);
+static ParamExternData *plpgsql_param_fetch(ParamListInfo params,
+ int paramid, bool speculative,
+ ParamExternData *prm);
+static void plpgsql_param_compile(ParamListInfo params, Param *param,
+ ExprState *state,
+ Datum *resv, bool *resnull);
+static void plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+static void plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+static void plpgsql_param_eval_non_var(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
static void exec_move_row(PLpgSQL_execstate *estate,
PLpgSQL_variable *target,
HeapTuple tup, TupleDesc tupdesc);
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
- * Set up short-lived ParamListInfo
+ * Set up ParamListInfo for this query
*/
- paramLI = setup_unshared_param_list(estate, query);
+ paramLI = setup_param_list(estate, query);
/*
* Open the cursor (the paramlist will get copied into the portal)
estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
/* caller is expected to fill the datums array */
- /* initialize ParamListInfo with one entry per datum, all invalid */
+ /* initialize our ParamListInfo with appropriate hook functions */
estate->paramLI = (ParamListInfo)
- palloc0(offsetof(ParamListInfoData, params) +
- estate->ndatums * sizeof(ParamExternData));
+ palloc(offsetof(ParamListInfoData, params));
estate->paramLI->paramFetch = plpgsql_param_fetch;
estate->paramLI->paramFetchArg = (void *) estate;
+ estate->paramLI->paramCompile = plpgsql_param_compile;
+ estate->paramLI->paramCompileArg = NULL; /* not needed */
estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
estate->paramLI->parserSetupArg = NULL; /* filled during use */
estate->paramLI->numParams = estate->ndatums;
- estate->paramLI->paramMask = NULL;
- estate->params_dirty = false;
/* set up for use of appropriate simple-expression EState and cast hash */
if (simple_eval_estate)
}
/*
- * Set up short-lived ParamListInfo
+ * Set up ParamListInfo for this query
*/
- paramLI = setup_unshared_param_list(estate, query);
+ paramLI = setup_param_list(estate, query);
/*
- * Open the cursor
+ * Open the cursor (the paramlist will get copied into the portal)
*/
portal = SPI_cursor_open_with_paramlist(curname, query->plan,
paramLI,
portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
/*
- * If a portal was requested, put the query into the portal
+ * Set up ParamListInfo to pass to executor
+ */
+ paramLI = setup_param_list(estate, expr);
+
+ /*
+ * If a portal was requested, put the query and paramlist into the portal
*/
if (portalP != NULL)
{
- /*
- * Set up short-lived ParamListInfo
- */
- paramLI = setup_unshared_param_list(estate, expr);
-
*portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
paramLI,
estate->readonly_func);
return SPI_OK_CURSOR;
}
- /*
- * Set up ParamListInfo to pass to executor
- */
- paramLI = setup_param_list(estate, expr);
-
/*
* Execute the query
*/
ExprContext *econtext = estate->eval_econtext;
LocalTransactionId curlxid = MyProc->lxid;
CachedPlan *cplan;
- ParamListInfo paramLI;
void *save_setup_arg;
MemoryContext oldcontext;
*rettype = expr->expr_simple_type;
*rettypmod = expr->expr_simple_typmod;
+ /*
+ * Set up ParamListInfo to pass to executor. For safety, save and restore
+ * estate->paramLI->parserSetupArg around our use of the param list.
+ */
+ save_setup_arg = estate->paramLI->parserSetupArg;
+
+ econtext->ecxt_param_list_info = setup_param_list(estate, expr);
+
/*
* Prepare the expression for execution, if it's not been done already in
* the current transaction. (This will be forced to happen if we called
if (expr->expr_simple_lxid != curlxid)
{
oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
- expr->expr_simple_state = ExecInitExpr(expr->expr_simple_expr, NULL);
+ expr->expr_simple_state =
+ ExecInitExprWithParams(expr->expr_simple_expr,
+ econtext->ecxt_param_list_info);
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
PushActiveSnapshot(GetTransactionSnapshot());
}
- /*
- * Set up ParamListInfo to pass to executor. We need an unshared list if
- * it's going to include any R/W expanded-object pointer. For safety,
- * save and restore estate->paramLI->parserSetupArg around our use of the
- * param list.
- */
- save_setup_arg = estate->paramLI->parserSetupArg;
-
- if (expr->rwparam >= 0)
- paramLI = setup_unshared_param_list(estate, expr);
- else
- paramLI = setup_param_list(estate, expr);
-
- econtext->ecxt_param_list_info = paramLI;
-
/*
* Mark expression as busy for the duration of the ExecEvalExpr call.
*/
/*
* Create a ParamListInfo to pass to SPI
*
- * We share a single ParamListInfo array across all SPI calls made from this
- * estate, except calls creating cursors, which use setup_unshared_param_list
- * (see its comments for reasons why), and calls that pass a R/W expanded
- * object pointer. A shared array is generally OK since any given slot in
- * the array would need to contain the same current datum value no matter
- * which query or expression we're evaluating; but of course that doesn't
- * hold when a specific variable is being passed as a R/W pointer, because
- * other expressions in the same function probably don't want to do that.
- *
- * Note that paramLI->parserSetupArg points to the specific PLpgSQL_expr
- * being evaluated. This is not an issue for statement-level callers, but
- * lower-level callers must save and restore estate->paramLI->parserSetupArg
- * just in case there's an active evaluation at an outer call level.
+ * We use a single ParamListInfo struct for all SPI calls made from this
+ * estate; it contains no per-param data, just hook functions, so it's
+ * effectively read-only for SPI.
*
- * The general plan for passing parameters to SPI is that plain VAR datums
- * always have valid images in the shared param list. This is ensured by
- * assign_simple_var(), which also marks those params as PARAM_FLAG_CONST,
- * allowing the planner to use those values in custom plans. However, non-VAR
- * datums cannot conveniently be managed that way. For one thing, they could
- * throw errors (for example "no such record field") and we do not want that
- * to happen in a part of the expression that might never be evaluated at
- * runtime. For another thing, exec_eval_datum() may return short-lived
- * values stored in the estate's eval_mcontext, which will not necessarily
- * survive to the next SPI operation. And for a third thing, ROW
- * and RECFIELD datums' values depend on other datums, and we don't have a
- * cheap way to track that. Therefore, param slots for non-VAR datum types
- * are always reset here and then filled on-demand by plpgsql_param_fetch().
- * We can save a few cycles by not bothering with the reset loop unless at
- * least one such param has actually been filled by plpgsql_param_fetch().
+ * An exception from pure read-only-ness is that the parserSetupArg points
+ * to the specific PLpgSQL_expr being evaluated. This is not an issue for
+ * statement-level callers, but lower-level callers must save and restore
+ * estate->paramLI->parserSetupArg just in case there's an active evaluation
+ * at an outer call level. (A plausible alternative design would be to
+ * create a ParamListInfo struct for each PLpgSQL_expr, but for the moment
+ * that seems like a waste of memory.)
*/
static ParamListInfo
setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
*/
Assert(expr->plan != NULL);
- /*
- * Expressions with R/W parameters can't use the shared param list.
- */
- Assert(expr->rwparam == -1);
-
/*
* We only need a ParamListInfo if the expression has parameters. In
* principle we should test with bms_is_empty(), but we use a not-null
/* Use the common ParamListInfo */
paramLI = estate->paramLI;
- /*
- * If any resettable parameters have been passed to the executor since
- * last time, we need to reset those param slots to "invalid", for the
- * reasons mentioned in the comment above.
- */
- if (estate->params_dirty)
- {
- Bitmapset *resettable_datums = estate->func->resettable_datums;
- int dno = -1;
-
- while ((dno = bms_next_member(resettable_datums, dno)) >= 0)
- {
- ParamExternData *prm = ¶mLI->params[dno];
-
- prm->ptype = InvalidOid;
- }
- estate->params_dirty = false;
- }
-
/*
* Set up link to active expr where the hook functions can find it.
* Callers must save and restore parserSetupArg if there is any chance
*/
paramLI->parserSetupArg = (void *) expr;
- /*
- * Allow parameters that aren't needed by this expression to be
- * ignored.
- */
- paramLI->paramMask = expr->paramnos;
-
/*
* Also make sure this is set before parser hooks need it. There is
* no need to save and restore, since the value is always correct once
}
/*
- * Create an unshared, short-lived ParamListInfo to pass to SPI
- *
- * When creating a cursor, we do not use the shared ParamListInfo array
- * but create a short-lived one that will contain only params actually
- * referenced by the query. The reason for this is that copyParamList() will
- * be used to copy the parameters into cursor-lifespan storage, and we don't
- * want it to copy anything that's not used by the specific cursor; that
- * could result in uselessly copying some large values.
- *
- * We also use this for expressions that are passing a R/W object pointer
- * to some trusted function. We don't want the R/W pointer to get into the
- * shared param list, where it could get passed to some less-trusted function.
+ * plpgsql_param_fetch paramFetch callback for dynamic parameter fetch
*
- * The result, if not NULL, is in the estate's eval_mcontext.
+ * We always use the caller's workspace to construct the returned struct.
*
- * XXX. Could we use ParamListInfo's new paramMask to avoid creating unshared
- * parameter lists?
- */
-static ParamListInfo
-setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
-{
- ParamListInfo paramLI;
-
- /*
- * We must have created the SPIPlan already (hence, query text has been
- * parsed/analyzed at least once); else we cannot rely on expr->paramnos.
- */
- Assert(expr->plan != NULL);
-
- /*
- * We only need a ParamListInfo if the expression has parameters. In
- * principle we should test with bms_is_empty(), but we use a not-null
- * test because it's faster. In current usage bits are never removed from
- * expr->paramnos, only added, so this test is correct anyway.
- */
- if (expr->paramnos)
- {
- int dno;
-
- /* initialize ParamListInfo with one entry per datum, all invalid */
- paramLI = (ParamListInfo)
- eval_mcontext_alloc0(estate,
- offsetof(ParamListInfoData, params) +
- estate->ndatums * sizeof(ParamExternData));
- paramLI->paramFetch = plpgsql_param_fetch;
- paramLI->paramFetchArg = (void *) estate;
- paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
- paramLI->parserSetupArg = (void *) expr;
- paramLI->numParams = estate->ndatums;
- paramLI->paramMask = NULL;
-
- /*
- * Instantiate values for "safe" parameters of the expression. We
- * could skip this and leave them to be filled by plpgsql_param_fetch;
- * but then the values would not be available for query planning,
- * since the planner doesn't call the paramFetch hook.
- */
- dno = -1;
- while ((dno = bms_next_member(expr->paramnos, dno)) >= 0)
- {
- PLpgSQL_datum *datum = estate->datums[dno];
-
- if (datum->dtype == PLPGSQL_DTYPE_VAR)
- {
- PLpgSQL_var *var = (PLpgSQL_var *) datum;
- ParamExternData *prm = ¶mLI->params[dno];
-
- if (dno == expr->rwparam)
- prm->value = var->value;
- else
- prm->value = MakeExpandedObjectReadOnly(var->value,
- var->isnull,
- var->datatype->typlen);
- prm->isnull = var->isnull;
- prm->pflags = PARAM_FLAG_CONST;
- prm->ptype = var->datatype->typoid;
- }
- }
-
- /*
- * Also make sure this is set before parser hooks need it. There is
- * no need to save and restore, since the value is always correct once
- * set. (Should be set already, but let's be sure.)
- */
- expr->func = estate->func;
- }
- else
- {
- /*
- * Expression requires no parameters. Be sure we represent this case
- * as a NULL ParamListInfo, so that plancache.c knows there is no
- * point in a custom plan.
- */
- paramLI = NULL;
- }
- return paramLI;
-}
-
-/*
- * plpgsql_param_fetch paramFetch callback for dynamic parameter fetch
+ * Note: this is no longer used during query execution. It is used during
+ * planning (with speculative == true) and when the ParamListInfo we supply
+ * to the executor is copied into a cursor portal or transferred to a
+ * parallel child process.
*/
-static void
-plpgsql_param_fetch(ParamListInfo params, int paramid)
+static ParamExternData *
+plpgsql_param_fetch(ParamListInfo params,
+ int paramid, bool speculative,
+ ParamExternData *prm)
{
int dno;
PLpgSQL_execstate *estate;
PLpgSQL_expr *expr;
PLpgSQL_datum *datum;
- ParamExternData *prm;
+ bool ok = true;
int32 prmtypmod;
/* paramid's are 1-based, but dnos are 0-based */
/*
* Since copyParamList() or SerializeParamList() will try to materialize
- * every single parameter slot, it's important to do nothing when asked
- * for a datum that's not supposed to be used by this SQL expression.
- * Otherwise we risk failures in exec_eval_datum(), or copying a lot more
- * data than necessary.
+ * every single parameter slot, it's important to return a dummy param
+ * when asked for a datum that's not supposed to be used by this SQL
+ * expression. Otherwise we risk failures in exec_eval_datum(), or
+ * copying a lot more data than necessary.
*/
if (!bms_is_member(dno, expr->paramnos))
- return;
+ ok = false;
- if (params == estate->paramLI)
+ /*
+ * If the access is speculative, we prefer to return no data rather than
+ * to fail in exec_eval_datum(). Check the likely failure cases.
+ */
+ else if (speculative)
{
- /*
- * We need to mark the shared params array dirty if we're about to
- * evaluate a resettable datum.
- */
switch (datum->dtype)
{
+ case PLPGSQL_DTYPE_VAR:
+ /* always safe */
+ break;
+
case PLPGSQL_DTYPE_ROW:
+ /* should be safe in all interesting cases */
+ break;
+
case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+
+ if (!HeapTupleIsValid(rec->tup))
+ ok = false;
+ break;
+ }
+
case PLPGSQL_DTYPE_RECFIELD:
- estate->params_dirty = true;
- break;
+ {
+ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+ PLpgSQL_rec *rec;
+ int fno;
+
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+ if (!HeapTupleIsValid(rec->tup))
+ ok = false;
+ else
+ {
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE)
+ ok = false;
+ }
+ break;
+ }
default:
+ ok = false;
break;
}
}
- /* OK, evaluate the value and store into the appropriate paramlist slot */
- prm = ¶ms->params[dno];
+ /* Return "no such parameter" if not ok */
+ if (!ok)
+ {
+ prm->value = (Datum) 0;
+ prm->isnull = true;
+ prm->pflags = 0;
+ prm->ptype = InvalidOid;
+ return prm;
+ }
+
+ /* OK, evaluate the value and store into the return struct */
exec_eval_datum(estate, datum,
&prm->ptype, &prmtypmod,
&prm->value, &prm->isnull);
prm->value = MakeExpandedObjectReadOnly(prm->value,
prm->isnull,
((PLpgSQL_var *) datum)->datatype->typlen);
+
+ return prm;
+}
+
+/*
+ * plpgsql_param_compile paramCompile callback for plpgsql parameters
+ */
+static void
+plpgsql_param_compile(ParamListInfo params, Param *param,
+ ExprState *state,
+ Datum *resv, bool *resnull)
+{
+ PLpgSQL_execstate *estate;
+ PLpgSQL_expr *expr;
+ int dno;
+ PLpgSQL_datum *datum;
+ ExprEvalStep scratch;
+
+ /* fetch back the hook data */
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ expr = (PLpgSQL_expr *) params->parserSetupArg;
+
+ /* paramid's are 1-based, but dnos are 0-based */
+ dno = param->paramid - 1;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ datum = estate->datums[dno];
+
+ scratch.opcode = EEOP_PARAM_CALLBACK;
+ scratch.resvalue = resv;
+ scratch.resnull = resnull;
+
+ /* Select appropriate eval function */
+ if (datum->dtype == PLPGSQL_DTYPE_VAR)
+ {
+ if (dno != expr->rwparam &&
+ ((PLpgSQL_var *) datum)->datatype->typlen == -1)
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_var_ro;
+ else
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_var;
+ }
+ else
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_non_var;
+
+ /*
+ * Note: it's tempting to use paramarg to store the estate pointer and
+ * thereby save an indirection or two in the eval functions. But that
+ * doesn't work because the compiled expression might be used with
+ * different estates for the same PL/pgSQL function.
+ */
+ scratch.d.cparam.paramarg = NULL;
+ scratch.d.cparam.paramid = param->paramid;
+ scratch.d.cparam.paramtype = param->paramtype;
+ ExprEvalPushStep(state, &scratch);
+}
+
+/*
+ * plpgsql_param_eval_var evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This is specialized to the case of DTYPE_VAR variables for which
+ * we do not need to invoke MakeExpandedObjectReadOnly.
+ */
+static void
+plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_var *var;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ var = (PLpgSQL_var *) estate->datums[dno];
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+
+ /* inlined version of exec_eval_datum() */
+ *op->resvalue = var->value;
+ *op->resnull = var->isnull;
+
+ /* safety check -- an assertion should be sufficient */
+ Assert(var->datatype->typoid == op->d.cparam.paramtype);
+}
+
+/*
+ * plpgsql_param_eval_var_ro evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This is specialized to the case of DTYPE_VAR variables for which
+ * we need to invoke MakeExpandedObjectReadOnly.
+ */
+static void
+plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_var *var;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ var = (PLpgSQL_var *) estate->datums[dno];
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+
+ /*
+ * Inlined version of exec_eval_datum() ... and while we're at it, force
+ * expanded datums to read-only.
+ */
+ *op->resvalue = MakeExpandedObjectReadOnly(var->value,
+ var->isnull,
+ -1);
+ *op->resnull = var->isnull;
+
+ /* safety check -- an assertion should be sufficient */
+ Assert(var->datatype->typoid == op->d.cparam.paramtype);
+}
+
+/*
+ * plpgsql_param_eval_non_var evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This handles all variable types except DTYPE_VAR.
+ */
+static void
+plpgsql_param_eval_non_var(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_datum *datum;
+ Oid datumtype;
+ int32 datumtypmod;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ datum = estate->datums[dno];
+ Assert(datum->dtype != PLPGSQL_DTYPE_VAR);
+
+ exec_eval_datum(estate, datum,
+ &datumtype, &datumtypmod,
+ op->resvalue, op->resnull);
+
+ /* safety check -- needed for, eg, record fields */
+ if (unlikely(datumtype != op->d.cparam.paramtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
+ op->d.cparam.paramid,
+ format_type_be(datumtype),
+ format_type_be(op->d.cparam.paramtype))));
+
+ /*
+ * Currently, if the dtype isn't VAR, the value couldn't be a read/write
+ * expanded datum.
+ */
}
* assign_simple_var --- assign a new value to any VAR datum.
*
* This should be the only mechanism for assignment to simple variables,
- * lest we forget to update the paramLI image.
+ * lest we do the release of the old value incorrectly.
*/
static void
assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
Datum newvalue, bool isnull, bool freeable)
{
- ParamExternData *prm;
-
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
/* Free the old value if needed */
if (var->freeval)
var->value = newvalue;
var->isnull = isnull;
var->freeval = freeable;
- /* And update the image in the common parameter list */
- prm = &estate->paramLI->params[var->dno];
- prm->value = MakeExpandedObjectReadOnly(newvalue,
- isnull,
- var->datatype->typlen);
- prm->isnull = isnull;
- /* these might be set already, but let's be sure */
- prm->pflags = PARAM_FLAG_CONST;
- prm->ptype = var->datatype->typoid;
}
/*
/* the datums representing the function's local variables */
int ndatums;
PLpgSQL_datum **datums;
- Bitmapset *resettable_datums; /* dnos of non-simple vars */
/* function body parsetree */
PLpgSQL_stmt_block *action;
int ndatums;
PLpgSQL_datum **datums;
- /* we pass datums[i] to the executor, when needed, in paramLI->params[i] */
+ /*
+ * paramLI is what we use to pass local variable values to the executor.
+ * It does not have a ParamExternData array; we just dynamically
+ * instantiate parameter data as needed. By convention, PARAM_EXTERN
+ * Params have paramid equal to the dno of the referenced local variable.
+ */
ParamListInfo paramLI;
- bool params_dirty; /* T if any resettable datum has been passed */
/* EState to use for "simple" expression evaluation */
EState *simple_eval_estate;