From 32267b58bdf9db56a716abde9fcc3e4e8fac6fee Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Wed, 25 Mar 2026 16:07:18 +0900 Subject: [PATCH v9 2/5] Introduce ExecutorPrep and refactor executor startup Factor permission checks, range table initialization, and initial partition pruning out of InitPlan() into a new ExecutorPrep() helper. ExecutorStart() calls it to build the EState, keeping behavior unchanged. If QueryDesc->estate is already set when ExecutorStart() is called, the existing EState is reused and ExecutorPrep() is skipped. This allows a later commit to supply a pre-built EState from outside the executor. Add scaffolding for carrying an optional prep EState through CreateQueryDesc, PortalDefineQuery, and SPI. All callers currently pass NULL/NIL; the next commit populates these to enable pruning-aware locking in cached plans. In assert builds, verify that the expected relation locks are held when entering ExecutorStart(). --- src/backend/commands/copyto.c | 2 +- src/backend/commands/createas.c | 2 +- src/backend/commands/explain.c | 8 +- src/backend/commands/extension.c | 2 +- src/backend/commands/matview.c | 2 +- src/backend/commands/portalcmds.c | 1 + src/backend/commands/prepare.c | 9 +- src/backend/executor/README | 11 +- src/backend/executor/execMain.c | 157 +++++++++++++++++++++++----- src/backend/executor/execParallel.c | 3 +- src/backend/executor/functions.c | 3 +- src/backend/executor/spi.c | 9 +- src/backend/tcop/postgres.c | 2 + src/backend/tcop/pquery.c | 24 ++++- src/backend/utils/mmgr/portalmem.c | 2 + src/include/commands/explain.h | 3 +- src/include/executor/execdesc.h | 5 +- src/include/executor/executor.h | 26 +++++ src/include/utils/portal.h | 2 + 19 files changed, 223 insertions(+), 50 deletions(-) diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index faf62d959b4..b9bd5ba7078 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -1011,7 +1011,7 @@ BeginCopyTo(ParseState *pstate, cstate->queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext, GetActiveSnapshot(), InvalidSnapshot, - dest, NULL, NULL, 0); + dest, NULL, NULL, 0, NULL); /* * Call ExecutorStart to prepare the plan for execution. diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 270e9bf3110..b4a9808955a 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -336,7 +336,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, /* Create a QueryDesc, redirecting output to our tuple receiver */ queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext, GetActiveSnapshot(), InvalidSnapshot, - dest, params, queryEnv, 0); + dest, params, queryEnv, 0, NULL); /* call ExecutorStart to prepare the plan for execution */ ExecutorStart(queryDesc, GetIntoRelEFlags(into)); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index e4b70166b0e..24c0c235fd3 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -372,7 +372,7 @@ standard_ExplainOneQuery(Query *query, int cursorOptions, } /* run it (if needed) and produce output */ - ExplainOnePlan(plan, into, es, queryString, params, queryEnv, + ExplainOnePlan(plan, NULL, into, es, queryString, params, queryEnv, &planduration, (es->buffers ? &bufusage : NULL), es->memory ? &mem_counters : NULL); } @@ -494,7 +494,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, * to call it. */ void -ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, +ExplainOnePlan(PlannedStmt *plannedstmt, EState *prep_estate, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, const BufferUsage *bufusage, @@ -552,7 +553,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, /* Create a QueryDesc for the query */ queryDesc = CreateQueryDesc(plannedstmt, queryString, GetActiveSnapshot(), InvalidSnapshot, - dest, params, queryEnv, instrument_option); + dest, params, queryEnv, instrument_option, + prep_estate); /* Select execution options */ if (es->analyze) diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index b98801d08f2..939e7a632f0 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -1174,7 +1174,7 @@ execute_sql_string(const char *sql, const char *filename) qdesc = CreateQueryDesc(stmt, sql, GetActiveSnapshot(), NULL, - dest, NULL, NULL, 0); + dest, NULL, NULL, 0, NULL); ExecutorStart(qdesc, 0); ExecutorRun(qdesc, ForwardScanDirection, 0); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 81a55a33ef2..2cdfdcf984b 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -439,7 +439,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query, /* Create a QueryDesc, redirecting output to our tuple receiver */ queryDesc = CreateQueryDesc(plan, queryString, GetActiveSnapshot(), InvalidSnapshot, - dest, NULL, NULL, 0); + dest, NULL, NULL, 0, NULL); /* call ExecutorStart to prepare the plan for execution */ ExecutorStart(queryDesc, 0); diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 01efac3319e..1e880a6d7c9 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -118,6 +118,7 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa queryString, CMDTAG_SELECT, /* cursor's query is always a SELECT */ list_make1(plan), + NIL, NULL); /*---------- diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 876aad2100a..c7bab14b633 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -207,6 +207,7 @@ ExecuteQuery(ParseState *pstate, query_string, entry->plansource->commandTag, plan_list, + NIL, cplan); /* @@ -577,7 +578,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, const char *query_string; CachedPlan *cplan; List *plan_list; + List *prep_estates; ListCell *p; + ListCell *prep_lc; ParamListInfo paramLI = NULL; EState *estate = NULL; instr_time planstart; @@ -652,14 +655,18 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, } plan_list = cplan->stmt_list; + prep_estates = NIL; /* Explain each query */ + prep_lc = list_head(prep_estates); foreach(p, plan_list) { PlannedStmt *pstmt = lfirst_node(PlannedStmt, p); + EState *prep_estate = next_prep_estate(prep_estates, &prep_lc); if (pstmt->commandType != CMD_UTILITY) - ExplainOnePlan(pstmt, into, es, query_string, paramLI, pstate->p_queryEnv, + ExplainOnePlan(pstmt, prep_estate, + into, es, query_string, paramLI, pstate->p_queryEnv, &planduration, (es->buffers ? &bufusage : NULL), es->memory ? &mem_counters : NULL); else diff --git a/src/backend/executor/README b/src/backend/executor/README index 54f4782f31b..d749ceb6687 100644 --- a/src/backend/executor/README +++ b/src/backend/executor/README @@ -291,11 +291,18 @@ Query Processing Control Flow This is a sketch of control flow for full query processing: + ExecutorPrep + May be run before ExecutorStart (e.g., for plan validation), or + implicitly from ExecutorStart if not done earlier. Creates EState, + performs range table initialization, permission checks, and initial + partition pruning. Returns the EState that ExecutorStart() should + reuse. + CreateQueryDesc ExecutorStart - CreateExecutorState - creates per-query context + ExecutorPrep (if not already done, indicated by NULL QueryDesc.estate) + creates EState and per-query context switch to per-query context to run ExecInitNode AfterTriggerBeginQuery ExecInitNode --- recursively scans plan tree diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 58b84955c2b..282c9871de0 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -57,6 +57,7 @@ #include "parser/parse_relation.h" #include "pgstat.h" #include "rewrite/rewriteHandler.h" +#include "storage/lmgr.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/backend_status.h" @@ -147,7 +148,6 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) /* sanity checks: queryDesc must not be started already */ Assert(queryDesc != NULL); - Assert(queryDesc->estate == NULL); /* caller must ensure the query's snapshot is active */ Assert(GetActiveSnapshot() == queryDesc->snapshot); @@ -173,9 +173,70 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) /* * Build EState, switch into per-query memory context for startup. - */ - estate = CreateExecutorState(); - queryDesc->estate = estate; + * + * If ExecutorPrep() ran earlier (e.g., to do initial pruning during plan + * validity checking), reuse its EState to avoid redoing range table setup + * and pruning. Otherwise, create a fresh EState as usual. + * + * In assert builds, verify that the expected locks are held. When + * no prep EState was provided, AcquireExecutorLocks() should have + * locked every relation in the plan. When one was provided, + * pruning-aware locking should have locked at least the unpruned + * relations. Both checks are skipped in parallel workers, which + * acquire relation locks lazily in ExecGetRangeTableRelation(). + */ + if (queryDesc->estate == NULL) + { +#ifdef USE_ASSERT_CHECKING + if (!IsParallelWorker()) + { + ListCell *lc; + + foreach(lc, queryDesc->plannedstmt->rtable) + { + RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + + if (rte->rtekind == RTE_RELATION || + (rte->rtekind == RTE_SUBQUERY && rte->relid != InvalidOid)) + Assert(CheckRelationOidLockedByMe(rte->relid, + rte->rellockmode, + true)); + } + } +#endif + queryDesc->estate = ExecutorPrep(queryDesc->plannedstmt, + queryDesc->params, + CurrentResourceOwner, + eflags); + } +#ifdef USE_ASSERT_CHECKING + else + { + /* + * A prep EState was provided, meaning pruning-aware locking + * should have locked at least the unpruned relations. + */ + if (!IsParallelWorker()) + { + int rtindex = -1; + + while ((rtindex = bms_next_member(queryDesc->estate->es_unpruned_relids, + rtindex)) >= 0) + { + RangeTblEntry *rte = exec_rt_fetch(rtindex, queryDesc->estate); + + Assert(rte->rtekind == RTE_RELATION || + (rte->rtekind == RTE_SUBQUERY && + rte->relid != InvalidOid)); + Assert(CheckRelationOidLockedByMe(rte->relid, + rte->rellockmode, true)); + } + } + } +#endif + + estate = queryDesc->estate; + Assert(estate); oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); @@ -265,6 +326,67 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) MemoryContextSwitchTo(oldcontext); } +/* + * ExecutorPrep: prepare executor state for a PlannedStmt outside ExecutorStart. + * + * Performs range table initialization, permission checks, and initial + * partition pruning if partPruneInfos are present. + * + * Returns an EState that the caller must either pass to ExecutorStart() + * for reuse or free via FreeExecutorState() if execution will not proceed. + */ +EState * +ExecutorPrep(PlannedStmt *pstmt, ParamListInfo params, ResourceOwner owner, + int eflags) +{ + ResourceOwner oldowner; + EState *estate; + + if (pstmt->commandType == CMD_UTILITY) + return NULL; + + /* Caller must have established an active snapshot. */ + Assert(ActiveSnapshotSet()); + + estate = CreateExecutorState(); + estate->es_plannedstmt = pstmt; + estate->es_part_prune_infos = pstmt->partPruneInfos; + estate->es_param_list_info = params; + estate->es_top_eflags = eflags; + + /* + * Do permissions checks. + */ + ExecCheckPermissions(pstmt->rtable, pstmt->permInfos, true); + + /* + * Initialize range table. + */ + ExecInitRangeTable(estate, pstmt->rtable, pstmt->permInfos, + bms_copy(pstmt->unprunableRelids)); + + /* + * Ensure locks taken during initial pruning are tracked under the given + * ResourceOwner (e.g., one associated with CachedPlan validation). + */ + oldowner = CurrentResourceOwner; + CurrentResourceOwner = owner; + + /* + * Set up PartitionPruneState structures and perform initial partition + * pruning to compute the subset of child subplans that will be + * executed. The results, which are bitmapsets of selected child + * indexes, are saved in es_part_prune_results, parallel to + * es_part_prune_infos. RT indexes of surviving partitions are + * added to es_unpruned_relids. + */ + ExecDoInitialPruning(estate); + + CurrentResourceOwner = oldowner; + + return estate; +} + /* ---------------------------------------------------------------- * ExecutorRun * @@ -840,37 +962,14 @@ InitPlan(QueryDesc *queryDesc, int eflags) CmdType operation = queryDesc->operation; PlannedStmt *plannedstmt = queryDesc->plannedstmt; Plan *plan = plannedstmt->planTree; - List *rangeTable = plannedstmt->rtable; EState *estate = queryDesc->estate; PlanState *planstate; TupleDesc tupType; ListCell *l; int i; - /* - * Do permissions checks - */ - ExecCheckPermissions(rangeTable, plannedstmt->permInfos, true); - - /* - * initialize the node's execution state - */ - ExecInitRangeTable(estate, rangeTable, plannedstmt->permInfos, - bms_copy(plannedstmt->unprunableRelids)); - - estate->es_plannedstmt = plannedstmt; - estate->es_part_prune_infos = plannedstmt->partPruneInfos; - - /* - * Perform runtime "initial" pruning to identify which child subplans, - * corresponding to the children of plan nodes that contain - * PartitionPruneInfo such as Append, will not be executed. The results, - * which are bitmapsets of indexes of the child subplans that will be - * executed, are saved in es_part_prune_results. These results correspond - * to each PartitionPruneInfo entry, and the es_part_prune_results list is - * parallel to es_part_prune_infos. - */ - ExecDoInitialPruning(estate); + /* ExecutorPrep() must have been done. */ + Assert(queryDesc->estate); /* * Next, build the ExecRowMark array from the PlanRowMark(s), if any. diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index ac84af294c9..024780d3516 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -1300,7 +1300,8 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver, return CreateQueryDesc(pstmt, queryString, GetActiveSnapshot(), InvalidSnapshot, - receiver, paramLI, NULL, instrument_options); + receiver, paramLI, NULL, instrument_options, + NULL); } /* diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 88109348817..952a784c924 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1369,7 +1369,8 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) dest, fcache->paramLI, es->qd ? es->qd->queryEnv : NULL, - 0); + 0, + NULL); /* Utility commands don't need Executor. */ if (es->qd->operation != CMD_UTILITY) diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 52f3b11301c..380bbc44e97 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -1686,6 +1686,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, query_string, plansource->commandTag, stmt_list, + NIL, cplan); /* @@ -2500,6 +2501,8 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); List *stmt_list; ListCell *lc2; + List *prep_estates; + ListCell *prep_lc; spicallbackarg.query = plansource->query_string; @@ -2578,6 +2581,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, plan_owner, _SPI_current->queryEnv); stmt_list = cplan->stmt_list; + prep_estates = NIL; /* * If we weren't given a specific snapshot to use, and the statement @@ -2615,9 +2619,11 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, } } + prep_lc = list_head(prep_estates); foreach(lc2, stmt_list) { PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2); + EState *prep_estate = next_prep_estate(prep_estates, &prep_lc); bool canSetTag = stmt->canSetTag; DestReceiver *dest; @@ -2695,7 +2701,8 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, dest, options->params, _SPI_current->queryEnv, - 0); + 0, + prep_estate); res = _SPI_pquery(qdesc, fire_triggers, canSetTag ? options->tcount : 0); FreeQueryDesc(qdesc); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index b3563113219..355a490cde9 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1231,6 +1231,7 @@ exec_simple_query(const char *query_string) query_string, commandTag, plantree_list, + NIL, NULL); /* @@ -2030,6 +2031,7 @@ exec_bind_message(StringInfo input_message) query_string, psrc->commandTag, cplan->stmt_list, + NIL, cplan); /* Portal is defined, set the plan ID based on its contents. */ diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index d8fc75d0bb9..b18266487bb 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -37,6 +37,7 @@ Portal ActivePortal = NULL; static void ProcessQuery(PlannedStmt *plan, + EState *prep_estate, const char *sourceText, ParamListInfo params, QueryEnvironment *queryEnv, @@ -72,7 +73,8 @@ CreateQueryDesc(PlannedStmt *plannedstmt, DestReceiver *dest, ParamListInfo params, QueryEnvironment *queryEnv, - int instrument_options) + int instrument_options, + EState *prep_estate) { QueryDesc *qd = palloc_object(QueryDesc); @@ -93,6 +95,9 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->planstate = NULL; qd->totaltime = NULL; + /* Use the EState created by ExecutorPrep() if already done. */ + qd->estate = prep_estate; + /* not yet executed */ qd->already_executed = false; @@ -123,6 +128,7 @@ FreeQueryDesc(QueryDesc *qdesc) * PORTAL_ONE_RETURNING, or PORTAL_ONE_MOD_WITH portal * * plan: the plan tree for the query + * prep_estate: EState created in ExecutorPrep() for the query, if any * sourceText: the source text of the query * params: any parameters needed * dest: where to send results @@ -135,6 +141,7 @@ FreeQueryDesc(QueryDesc *qdesc) */ static void ProcessQuery(PlannedStmt *plan, + EState *prep_estate, const char *sourceText, ParamListInfo params, QueryEnvironment *queryEnv, @@ -148,7 +155,8 @@ ProcessQuery(PlannedStmt *plan, */ queryDesc = CreateQueryDesc(plan, sourceText, GetActiveSnapshot(), InvalidSnapshot, - dest, params, queryEnv, 0); + dest, params, queryEnv, 0, + prep_estate); /* * Call ExecutorStart to prepare the plan for execution @@ -495,7 +503,10 @@ PortalStart(Portal portal, ParamListInfo params, None_Receiver, params, portal->queryEnv, - 0); + 0, + portal->prep_estates ? + (EState *) linitial(portal->prep_estates) : + NULL); /* * If it's a scrollable cursor, executor needs to support @@ -1185,6 +1196,7 @@ PortalRunMulti(Portal portal, { bool active_snapshot_set = false; ListCell *stmtlist_item; + ListCell *prep_lc; /* * If the destination is DestRemoteExecute, change to DestNone. The @@ -1205,9 +1217,11 @@ PortalRunMulti(Portal portal, * Loop to handle the individual queries generated from a single parsetree * by analysis and rewrite. */ + prep_lc = list_head(portal->prep_estates); foreach(stmtlist_item, portal->stmts) { PlannedStmt *pstmt = lfirst_node(PlannedStmt, stmtlist_item); + EState *prep_estate = next_prep_estate(portal->prep_estates, &prep_lc); /* * If we got a cancel signal in prior command, quit @@ -1265,7 +1279,7 @@ PortalRunMulti(Portal portal, if (pstmt->canSetTag) { /* statement can set tag string */ - ProcessQuery(pstmt, + ProcessQuery(pstmt, prep_estate, portal->sourceText, portal->portalParams, portal->queryEnv, @@ -1274,7 +1288,7 @@ PortalRunMulti(Portal portal, else { /* stmt added by rewrite cannot set tag */ - ProcessQuery(pstmt, + ProcessQuery(pstmt, prep_estate, portal->sourceText, portal->portalParams, portal->queryEnv, diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 493f9b0ee19..443b583637c 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -286,6 +286,7 @@ PortalDefineQuery(Portal portal, const char *sourceText, CommandTag commandTag, List *stmts, + List *prep_estates, CachedPlan *cplan) { Assert(PortalIsValid(portal)); @@ -299,6 +300,7 @@ PortalDefineQuery(Portal portal, portal->commandTag = commandTag; SetQueryCompletion(&portal->qc, commandTag, 0); portal->stmts = stmts; + portal->prep_estates = prep_estates; portal->cplan = cplan; portal->status = PORTAL_DEFINED; } diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 472e141bba3..71ebe38bc86 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -64,7 +64,8 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, ParseState *pstate, ParamListInfo params); -extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, +extern void ExplainOnePlan(PlannedStmt *plannedstmt, EState *prep_estate, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h index d3a57242844..3a2169c9613 100644 --- a/src/include/executor/execdesc.h +++ b/src/include/executor/execdesc.h @@ -43,7 +43,7 @@ typedef struct QueryDesc QueryEnvironment *queryEnv; /* query environment passed in */ int instrument_options; /* OR of InstrumentOption flags */ - /* These fields are set by ExecutorStart */ + /* These fields are set by ExecutorStart or ExecutorPrep */ TupleDesc tupDesc; /* descriptor for result tuples */ EState *estate; /* executor's query-wide state */ PlanState *planstate; /* tree of per-plan-node state */ @@ -63,7 +63,8 @@ extern QueryDesc *CreateQueryDesc(PlannedStmt *plannedstmt, DestReceiver *dest, ParamListInfo params, QueryEnvironment *queryEnv, - int instrument_options); + int instrument_options, + EState *prep_estate); extern void FreeQueryDesc(QueryDesc *qdesc); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 07f4b1f7490..4505ceaca3c 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -21,6 +21,7 @@ #include "nodes/lockoptions.h" #include "nodes/parsenodes.h" #include "utils/memutils.h" +#include "utils/resowner.h" /* @@ -235,6 +236,31 @@ ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull) */ extern void ExecutorStart(QueryDesc *queryDesc, int eflags); extern void standard_ExecutorStart(QueryDesc *queryDesc, int eflags); + +extern EState *ExecutorPrep(PlannedStmt *pstmt, + ParamListInfo params, + ResourceOwner owner, + int eflags); + +/* + * Walk a prep_estates list in step with a parallel stmt_list iteration. + * Returns the next EState (or NULL) and advances *lc. + * + * Safe when prep_estates is NIL; just returns NULL for every call. + */ +static inline EState * +next_prep_estate(List *prep_estates, ListCell **lc) +{ + EState *result = NULL; + + if (*lc != NULL) + { + result = (EState *) lfirst(*lc); + *lc = lnext(prep_estates, *lc); + } + return result; +} + extern void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count); extern void standard_ExecutorRun(QueryDesc *queryDesc, diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index a7bedb12c18..f69b4b9b479 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -137,6 +137,7 @@ typedef struct PortalData CommandTag commandTag; /* command tag for original query */ QueryCompletion qc; /* command completion data for executed query */ List *stmts; /* list of PlannedStmts */ + List *prep_estates; /* list of EStates where needed */ CachedPlan *cplan; /* CachedPlan, if stmts are from one */ ParamListInfo portalParams; /* params to pass to query */ @@ -240,6 +241,7 @@ extern void PortalDefineQuery(Portal portal, const char *sourceText, CommandTag commandTag, List *stmts, + List *prep_estates, CachedPlan *cplan); extern PlannedStmt *PortalGetPrimaryStmt(Portal portal); extern void PortalCreateHoldStore(Portal portal); -- 2.47.3