Hi, On 2023-03-02 13:00:31 -0800, Andres Freund wrote: > I'm not opposed to EXPR_PARAM_SET, to be clear. I'll send an updated > version later. I was just thinking about the correctness in the current > world.
Attached. I named the set EEOP_PARAM_SET EEOP_PARAM_EXEC_SET or such, because I was wondering if there cases it could also be useful in conjunction with PARAM_EXTERN, and because nothing really depends on the kind of param. Greetings, Andres
>From f63915ab00d55b5a67d8b6d05863fe69ea4252b5 Mon Sep 17 00:00:00 2001 From: Andres Freund <and...@anarazel.de> Date: Sat, 25 Feb 2023 13:39:19 -0800 Subject: [PATCH v2] WIP: Evaluate arguments of correlated SubPlans in the referencing ExprState --- src/include/executor/execExpr.h | 6 +- src/include/nodes/execnodes.h | 1 - src/backend/executor/execExpr.c | 93 +++++++++++++++++---------- src/backend/executor/execExprInterp.c | 22 +++++++ src/backend/executor/execProcnode.c | 5 ++ src/backend/executor/nodeSubplan.c | 30 ++++----- src/backend/jit/llvm/llvmjit_expr.c | 6 ++ src/backend/jit/llvm/llvmjit_types.c | 1 + 8 files changed, 112 insertions(+), 52 deletions(-) diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 06c3adc0a19..ca2b7306cd0 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -158,6 +158,8 @@ typedef enum ExprEvalOp EEOP_PARAM_EXEC, EEOP_PARAM_EXTERN, EEOP_PARAM_CALLBACK, + /* set PARAM_EXEC value */ + EEOP_PARAM_SET, /* return CaseTestExpr value */ EEOP_CASE_TESTVAL, @@ -374,7 +376,7 @@ typedef struct ExprEvalStep ExprEvalRowtypeCache rowcache; } nulltest_row; - /* for EEOP_PARAM_EXEC/EXTERN */ + /* for EEOP_PARAM_EXEC/EXTERN and EEOP_PARAM_SET */ struct { int paramid; /* numeric ID for parameter */ @@ -738,6 +740,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op); extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op); extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index e7eb1e666ff..16e95e4cb48 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -947,7 +947,6 @@ typedef struct SubPlanState struct PlanState *planstate; /* subselect plan's state tree */ struct PlanState *parent; /* parent plan node's state tree */ ExprState *testexpr; /* state of combining expression */ - List *args; /* states of argument expression(s) */ HeapTuple curTuple; /* copy of most recent tuple from subplan */ Datum curArray; /* most recent array from ARRAY() subplan */ /* these are used when hashing the subselect's output: */ diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index c61f23c6c18..002f2a0091f 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -87,6 +87,9 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, FunctionCallInfo fcinfo, AggStatePerTrans pertrans, int transno, int setno, int setoff, bool ishash, bool nullcheck); +static void ExecInitSubPlanExpr(SubPlan *subplan, + ExprState *state, + Datum *resv, bool *resnull); /* @@ -1388,7 +1391,6 @@ ExecInitExprRec(Expr *node, ExprState *state, case T_SubPlan: { SubPlan *subplan = (SubPlan *) node; - SubPlanState *sstate; /* * Real execution of a MULTIEXPR SubPlan has already been @@ -1405,19 +1407,7 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } - if (!state->parent) - elog(ERROR, "SubPlan found with no parent plan"); - - sstate = ExecInitSubPlan(subplan, state->parent); - - /* add SubPlanState nodes to state->parent->subPlan */ - state->parent->subPlan = lappend(state->parent->subPlan, - sstate); - - scratch.opcode = EEOP_SUBPLAN; - scratch.d.subplan.sstate = sstate; - - ExprEvalPushStep(state, &scratch); + ExecInitSubPlanExpr(subplan, state, resv, resnull); break; } @@ -2618,29 +2608,12 @@ ExecPushExprSetupSteps(ExprState *state, ExprSetupInfo *info) foreach(lc, info->multiexpr_subplans) { SubPlan *subplan = (SubPlan *) lfirst(lc); - SubPlanState *sstate; Assert(subplan->subLinkType == MULTIEXPR_SUBLINK); - /* This should match what ExecInitExprRec does for other SubPlans: */ - - if (!state->parent) - elog(ERROR, "SubPlan found with no parent plan"); - - sstate = ExecInitSubPlan(subplan, state->parent); - - /* add SubPlanState nodes to state->parent->subPlan */ - state->parent->subPlan = lappend(state->parent->subPlan, - sstate); - - scratch.opcode = EEOP_SUBPLAN; - scratch.d.subplan.sstate = sstate; - /* The result can be ignored, but we better put it somewhere */ - scratch.resvalue = &state->resvalue; - scratch.resnull = &state->resnull; - - ExprEvalPushStep(state, &scratch); + ExecInitSubPlanExpr(subplan, state, + &state->resvalue, &state->resnull); } } @@ -4040,3 +4013,57 @@ ExecBuildParamSetEqual(TupleDesc desc, return state; } + +static void +ExecInitSubPlanExpr(SubPlan *subplan, + ExprState *state, + Datum *resv, bool *resnull) +{ + ExprEvalStep scratch = {0}; + SubPlanState *sstate; + ListCell *pvar; + ListCell *l; + + if (!state->parent) + elog(ERROR, "SubPlan found with no parent plan"); + + /* + * Generate steps to evaluate input arguments for the subplan. + * + * We evaluate the argument expressions into ExprState's resvalue/resnull, + * and then use PARAM_SET to update the parameter. We do that, instead of + * evaluating directly into the param, to avoid depending on the pointer + * value remaining stable / being included in the generated expression. No + * danger of conflicts with other uses of resvalue/resnull as storing and + * using the value always is in subsequent steps. + * + * Any calculation we have to do can be done in the parent econtext, since + * the Param values don't need to have per-query lifetime. + */ + forboth(l, subplan->parParam, pvar, subplan->args) + { + int paramid = lfirst_int(l); + + ExecInitExprRec(lfirst(pvar), state, + &state->resvalue, &state->resnull); + + scratch.opcode = EEOP_PARAM_SET; + scratch.d.param.paramid = paramid; + /* type isn't needed, but an old value could be confusing */ + scratch.d.param.paramtype = InvalidOid; + ExprEvalPushStep(state, &scratch); + } + + sstate = ExecInitSubPlan(subplan, state->parent); + + /* add SubPlanState nodes to state->parent->subPlan */ + state->parent->subPlan = lappend(state->parent->subPlan, + sstate); + + scratch.opcode = EEOP_SUBPLAN; + scratch.resvalue = resv; + scratch.resnull = resnull; + scratch.d.subplan.sstate = sstate; + + ExprEvalPushStep(state, &scratch); +} diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 19351fe34bf..3cab8a5cdae 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -446,6 +446,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_PARAM_EXEC, &&CASE_EEOP_PARAM_EXTERN, &&CASE_EEOP_PARAM_CALLBACK, + &&CASE_EEOP_PARAM_SET, &&CASE_EEOP_CASE_TESTVAL, &&CASE_EEOP_MAKE_READONLY, &&CASE_EEOP_IOCOERCE, @@ -1081,6 +1082,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_PARAM_SET) + { + /* out of line, unlikely to matter performancewise */ + ExecEvalParamSet(state, op, econtext); + EEO_NEXT(); + } + EEO_CASE(EEOP_CASE_TESTVAL) { /* @@ -2477,6 +2485,20 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext) errmsg("no value found for parameter %d", paramId))); } +/* + * Set value of a param (currently always PARAM_EXEC) from + * state->res{value,null}. + */ +void +ExecEvalParamSet(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + ParamExecData *prm; + + prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]); + prm->value = state->resvalue; + prm->isnull = state->resnull; +} + /* * Raise error if a CURRENT OF expression is evaluated. * diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 4d288bc8d41..f62bb28140f 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -393,6 +393,10 @@ ExecInitNode(Plan *node, EState *estate, int eflags) /* * Initialize any initPlans present in this node. The planner put them in * a separate list for us. + * + * The defining characteristic of initplans is that they don't have + * arguments, so we don't need to evaluate them (in contrast to + * ExecInitSubPlanExpr()). */ subps = NIL; foreach(l, node->initPlan) @@ -401,6 +405,7 @@ ExecInitNode(Plan *node, EState *estate, int eflags) SubPlanState *sstate; Assert(IsA(subplan, SubPlan)); + Assert(subplan->args == NIL); sstate = ExecInitSubPlan(subplan, result); subps = lappend(subps, sstate); } diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index c136f75ac24..3458ac007cd 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -107,7 +107,7 @@ ExecHashSubPlan(SubPlanState *node, TupleTableSlot *slot; /* Shouldn't have any direct correlation Vars */ - if (subplan->parParam != NIL || node->args != NIL) + if (subplan->parParam != NIL || subplan->args != NIL) elog(ERROR, "hashed subplan with direct correlation not supported"); /* @@ -231,7 +231,6 @@ ExecScanSubPlan(SubPlanState *node, TupleTableSlot *slot; Datum result; bool found = false; /* true if got at least one subplan tuple */ - ListCell *pvar; ListCell *l; ArrayBuildStateAny *astate = NULL; @@ -248,26 +247,20 @@ ExecScanSubPlan(SubPlanState *node, oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); /* - * Set Params of this plan from parent plan correlation values. (Any - * calculation we have to do is done in the parent econtext, since the - * Param values don't need to have per-query lifetime.) + * We rely on the caller to evaluate plan correlation values, if + * necessary. However we still need to record the fact that the values + * (might have) changed, otherwise the ExecReScan() below won't know that + * nodes need to be rescanned. */ - Assert(list_length(subplan->parParam) == list_length(node->args)); - - forboth(l, subplan->parParam, pvar, node->args) + Assert(list_length(subplan->parParam) == list_length(subplan->args)); + foreach(l, subplan->parParam) { int paramid = lfirst_int(l); - ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]); - prm->value = ExecEvalExprSwitchContext((ExprState *) lfirst(pvar), - econtext, - &(prm->isnull)); planstate->chgParam = bms_add_member(planstate->chgParam, paramid); } - /* - * Now that we've set up its parameters, we can reset the subplan. - */ + /* with that done, we can reset the subplan */ ExecReScan(planstate); /* @@ -817,6 +810,10 @@ slotNoNulls(TupleTableSlot *slot) * as well as regular SubPlans. Note that we don't link the SubPlan into * the parent's subPlan list, because that shouldn't happen for InitPlans. * Instead, ExecInitExpr() does that one part. + * + * We also rely on ExecInitExpr(), more precisely ExecInitSubPlanExpr(), to + * evaluate input parameters, as that allows them to be evaluated as part of + * the expression referencing the SubPlan. * ---------------------------------------------------------------- */ SubPlanState * @@ -844,7 +841,6 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) /* Initialize subexpressions */ sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent); - sstate->args = ExecInitExprList(subplan->args, parent); /* * initialize my state @@ -1107,7 +1103,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) elog(ERROR, "ANY/ALL subselect unsupported as initplan"); if (subLinkType == CTE_SUBLINK) elog(ERROR, "CTE subplans should not be executed via ExecSetParamPlan"); - if (subplan->parParam || node->args) + if (subplan->parParam || subplan->args) elog(ERROR, "correlated subplans should not be executed via ExecSetParamPlan"); /* diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 1c722c79552..812f8758743 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -1094,6 +1094,12 @@ llvm_compile_expr(ExprState *state) break; } + case EEOP_PARAM_SET: + build_EvalXFunc(b, mod, "ExecEvalParamSet", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + case EEOP_SBSREF_SUBSCRIPTS: { int jumpdone = op->d.sbsref_subscript.jumpdone; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 876fb640294..0b1df3afe00 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -123,6 +123,7 @@ void *referenced_functions[] = ExecEvalNextValueExpr, ExecEvalParamExec, ExecEvalParamExtern, + ExecEvalParamSet, ExecEvalRow, ExecEvalRowNotNull, ExecEvalRowNull, -- 2.37.1.188.g71a8fab31b