From dc2f02f6b26717b774f9438cddf692139e285c5f Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 22 Feb 2023 11:03:02 -0800 Subject: [PATCH v1] heavily-WIP: partition specific subplan plans --- src/include/executor/execExpr.h | 1 + src/include/nodes/execnodes.h | 17 +++++++ src/include/nodes/params.h | 2 +- src/backend/executor/execExpr.c | 15 ++++++ src/backend/executor/execExprInterp.c | 33 +++++++++++-- src/backend/executor/execParallel.c | 2 +- src/backend/executor/execPartition.c | 12 +++++ src/backend/executor/execUtils.c | 1 + src/backend/executor/nodeCtescan.c | 2 +- src/backend/executor/nodeRecursiveunion.c | 2 +- src/backend/executor/nodeSubplan.c | 57 +++++++++++++++++------ src/backend/executor/nodeWorktablescan.c | 2 +- 12 files changed, 122 insertions(+), 24 deletions(-) diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 86e1ac1e65c..6d2f66bbb93 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -379,6 +379,7 @@ typedef struct ExprEvalStep { int paramid; /* numeric ID for parameter */ Oid paramtype; /* OID of parameter's datatype */ + int paramthing; /* offset into EState->es_param_exec_plans[paramid][] */ } param; /* for EEOP_PARAM_CALLBACK */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 20f4c8b35f3..252c8ba7820 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -650,6 +650,21 @@ typedef struct EState ParamListInfo es_param_list_info; /* values of external params */ ParamExecData *es_param_exec_vals; /* values of internal params */ + /* + * two level list: es_param_exec_plans[paramid][target relation] + */ + List *es_param_exec_plans; + + /* + * Subplans may end up being partition specific. For that we need to have + * some sort of identifier to index into + * ->es_param_exec_plans[paramid]. That index needs to be available when + * building the expression. We don't have a good identifier for all + * partitions, so we assign these sequentially. + */ + int es_param_exec_target_thing_current; + int es_param_exec_target_thing_next; + QueryEnvironment *es_queryEnv; /* query environment */ /* Other working state: */ @@ -972,6 +987,8 @@ typedef struct SubPlanState FmgrInfo *lhs_hash_funcs; /* hash functions for lefthand datatype(s) */ FmgrInfo *cur_eq_funcs; /* equality functions for LHS vs. table */ ExprState *cur_eq_comp; /* equality comparator for LHS vs. table */ + /* identifies offset in Estate->es_param_exec_plans[paramid][offset] */ + int target_thing; } SubPlanState; /* diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h index ad23113a61c..f78bdf14268 100644 --- a/src/include/nodes/params.h +++ b/src/include/nodes/params.h @@ -145,7 +145,7 @@ typedef struct ParamListInfoData typedef struct ParamExecData { - void *execPlan; /* should be "SubPlanState *" */ + bool needsExecPlan; /* plan is in EState->es_param_exec_plans */ Datum value; bool isnull; } ParamExecData; diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 812ead95bc6..74a6b774bd0 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -993,6 +993,21 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.opcode = EEOP_PARAM_EXEC; scratch.d.param.paramid = param->paramid; scratch.d.param.paramtype = param->paramtype; + + /* + * If we're currently building an expression for a + * partition, a subplan might be specific to the + * current target partition. Unfortunately we can't + * tell yet, because we've not necessarily encountered + * the subplan yet. Need to decide at expression + * evaluation time. + */ + if (!state->parent) + scratch.d.param.paramthing = 0; + else + scratch.d.param.paramthing = + state->parent->state->es_param_exec_target_thing_current; + ExprEvalPushStep(state, &scratch); break; case PARAM_EXTERN: diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 19351fe34bf..7a6d6bf4579 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -2420,14 +2420,39 @@ void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { ParamExecData *prm; + int paramid = op->d.param.paramid; - prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]); - if (unlikely(prm->execPlan != NULL)) + prm = &(econtext->ecxt_param_exec_vals[paramid]); + + if (unlikely(prm->needsExecPlan)) { + List *param_list; + EState *estate = econtext->ecxt_estate; + SubPlanState *param_plan; + + Assert(list_length(estate->es_param_exec_plans) >= paramid + 1); + param_list = list_nth(estate->es_param_exec_plans, paramid); + + /* + * The plan could either be specific to the partitioned table, or + * not. Unfortunately it looks to be hard to determine that in + * execExpr.c. So we just try both - there shouldn't ever be + * conflicts. + */ + if (list_length(param_list) >= op->d.param.paramthing + 1) + { + param_plan = castNode(SubPlanState, list_nth(param_list, op->d.param.paramthing)); + } + else + { + Assert(list_length(param_list) == 1); + param_plan = linitial(param_list); + } + /* Parameter not evaluated yet, so go do it */ - ExecSetParamPlan(prm->execPlan, econtext); + ExecSetParamPlan(param_plan, econtext); /* ExecSetParamPlan should have processed this param... */ - Assert(prm->execPlan == NULL); + Assert(!prm->needsExecPlan); } *op->resvalue = prm->value; *op->resnull = prm->isnull; diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index aa3f2834537..2e868425e3f 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -429,7 +429,7 @@ RestoreParamExecParams(char *start_address, EState *estate) /* Read datum/isnull. */ prm->value = datumRestore(&start_address, &prm->isnull); - prm->execPlan = NULL; + prm->needsExecPlan = false; } } diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 651ad24fc10..e93494c7e18 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -510,9 +510,19 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, MemoryContext oldcxt; AttrMap *part_attmap = NULL; bool found_whole_row; + size_t save_target_thing_current = estate->es_param_exec_target_thing_current; oldcxt = MemoryContextSwitchTo(proute->memcxt); + /* + * We can't use partidx or such as an identifier for partition specific + * state, due to multi-level partitioning. So we just assign the + * identifier sequentially. One advantage of that scheme is that we end up + * with far smaller indexes when partition pruning is effective. + */ + Assert(estate->es_param_exec_target_thing_next != 0); + estate->es_param_exec_target_thing_current = estate->es_param_exec_target_thing_next++; + partrel = table_open(partOid, RowExclusiveLock); leaf_part_rri = makeNode(ResultRelInfo); @@ -959,6 +969,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, } MemoryContextSwitchTo(oldcxt); + estate->es_param_exec_target_thing_current = save_target_thing_current; + return leaf_part_rri; } diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index c33a3c0bec7..fef21a1953b 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -138,6 +138,7 @@ CreateExecutorState(void) estate->es_param_list_info = NULL; estate->es_param_exec_vals = NULL; + estate->es_param_exec_target_thing_next = 1; estate->es_queryEnv = NULL; diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c index cc4c4243e2f..b6b59410791 100644 --- a/src/backend/executor/nodeCtescan.c +++ b/src/backend/executor/nodeCtescan.c @@ -224,7 +224,7 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags) * CTEs, particularly the shared tuplestore. */ prmdata = &(estate->es_param_exec_vals[node->cteParam]); - Assert(prmdata->execPlan == NULL); + Assert(!prmdata->needsExecPlan); Assert(!prmdata->isnull); scanstate->leader = castNode(CteScanState, DatumGetPointer(prmdata->value)); if (scanstate->leader == NULL) diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c index e7810039341..bafbdf4e2d9 100644 --- a/src/backend/executor/nodeRecursiveunion.c +++ b/src/backend/executor/nodeRecursiveunion.c @@ -215,7 +215,7 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags) * via the Param slot reserved for it. */ prmdata = &(estate->es_param_exec_vals[node->wtParam]); - Assert(prmdata->execPlan == NULL); + Assert(!prmdata->needsExecPlan); prmdata->value = PointerGetDatum(rustate); prmdata->isnull = false; diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 64af36a1873..16c0998a65d 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -270,7 +270,7 @@ ExecScanSubPlan(SubPlanState *node, int paramid = lfirst_int(l); ParamExecData *prm = &(estate->es_param_exec_vals[paramid]); - prm->execPlan = node; + prm->needsExecPlan = true; } *isNull = true; return (Datum) 0; @@ -413,7 +413,7 @@ ExecScanSubPlan(SubPlanState *node, ParamExecData *prmdata; prmdata = &(econtext->ecxt_param_exec_vals[paramid]); - Assert(prmdata->execPlan == NULL); + Assert(!prmdata->needsExecPlan); prmdata->value = slot_getattr(slot, col, &(prmdata->isnull)); col++; } @@ -598,7 +598,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) ParamExecData *prmdata; prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]); - Assert(prmdata->execPlan == NULL); + Assert(!prmdata->needsExecPlan); prmdata->value = slot_getattr(slot, col, &(prmdata->isnull)); col++; @@ -846,6 +846,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) sstate->tab_collations = NULL; sstate->lhs_hash_funcs = NULL; sstate->cur_eq_funcs = NULL; + sstate->target_thing = estate->es_param_exec_target_thing_current; /* * If this is an initplan or MULTIEXPR subplan, it has output parameters @@ -867,8 +868,21 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) { int paramid = lfirst_int(lst); ParamExecData *prm = &(estate->es_param_exec_vals[paramid]); + ListCell *param_cell; - prm->execPlan = sstate; + prm->needsExecPlan = true; + + while (list_length(estate->es_param_exec_plans) <= paramid) + estate->es_param_exec_plans = lappend(estate->es_param_exec_plans, NULL); + + param_cell = list_nth_cell(estate->es_param_exec_plans, paramid); + + while (list_length(lfirst(param_cell)) <= sstate->target_thing) + lfirst(param_cell) = lappend(lfirst(param_cell), NULL); + + if (list_nth(lfirst(param_cell), sstate->target_thing) != NULL) + elog(WARNING, "overwriting previous subplan exec plan: %u", sstate->target_thing); + lfirst(list_nth_cell(lfirst(param_cell), sstate->target_thing)) = sstate; } } @@ -1143,7 +1157,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) int paramid = linitial_int(subplan->setParam); ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]); - prm->execPlan = NULL; + prm->needsExecPlan = false; prm->value = BoolGetDatum(true); prm->isnull = false; found = true; @@ -1193,7 +1207,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) int paramid = lfirst_int(l); ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]); - prm->execPlan = NULL; + prm->needsExecPlan = false; prm->value = heap_getattr(node->curTuple, i, tdesc, &(prm->isnull)); i++; @@ -1216,7 +1230,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) node->curArray = makeArrayResultAny(astate, econtext->ecxt_per_query_memory, true); - prm->execPlan = NULL; + prm->needsExecPlan = false; prm->value = node->curArray; prm->isnull = false; } @@ -1228,7 +1242,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) int paramid = linitial_int(subplan->setParam); ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]); - prm->execPlan = NULL; + prm->needsExecPlan = false; prm->value = BoolGetDatum(false); prm->isnull = false; } @@ -1240,7 +1254,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) int paramid = lfirst_int(l); ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]); - prm->execPlan = NULL; + prm->needsExecPlan = false; prm->value = (Datum) 0; prm->isnull = true; } @@ -1268,18 +1282,31 @@ void ExecSetParamPlanMulti(const Bitmapset *params, ExprContext *econtext) { int paramid; + EState *estate = econtext->ecxt_estate; paramid = -1; while ((paramid = bms_next_member(params, paramid)) >= 0) { ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]); - if (prm->execPlan != NULL) + if (prm->needsExecPlan) { - /* Parameter not evaluated yet, so go do it */ - ExecSetParamPlan(prm->execPlan, econtext); - /* ExecSetParamPlan should have processed this param... */ - Assert(prm->execPlan == NULL); + ListCell *lc; + List *param_list; + + Assert(list_length(estate->es_param_exec_plans) >= paramid); + param_list = list_nth(estate->es_param_exec_plans, paramid); + + foreach(lc, param_list) + { + if (lfirst(lc) == NULL) + continue; + + /* Parameter not evaluated yet, so go do it */ + ExecSetParamPlan(lfirst(lc), econtext); + /* ExecSetParamPlan should have processed this param... */ + Assert(!prm->needsExecPlan); + } } } } @@ -1321,7 +1348,7 @@ ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent) ParamExecData *prm = &(estate->es_param_exec_vals[paramid]); if (subplan->subLinkType != CTE_SUBLINK) - prm->execPlan = node; + prm->needsExecPlan = node; parent->chgParam = bms_add_member(parent->chgParam, paramid); } diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c index 0c13448236a..4202ab74955 100644 --- a/src/backend/executor/nodeWorktablescan.c +++ b/src/backend/executor/nodeWorktablescan.c @@ -95,7 +95,7 @@ ExecWorkTableScan(PlanState *pstate) ParamExecData *param; param = &(estate->es_param_exec_vals[plan->wtParam]); - Assert(param->execPlan == NULL); + Assert(!param->needsExecPlan); Assert(!param->isnull); node->rustate = castNode(RecursiveUnionState, DatumGetPointer(param->value)); Assert(node->rustate); -- 2.38.0