From f9ff9aa3ddc05ee948d721a05ec552d5e959c498 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Wed, 6 Sep 2023 17:53:46 +0900 Subject: [PATCH v47 4/8] Adjustments to allow ExecutorStart() to sometimes fail Upon passing a plan tree from a CachedPlan to the executor, there's a possibility that ExecutorStart() might return an incompletely set up planstate tree. This can happen if the CachedPlan undergoes invalidation during the ExecInitNode() initialization process. In such cases, the execution should be reattempted using a fresh CachedPlan. Also, any partially initialized EState must be cleaned up by invoking both ExecutorEnd() and FreeExecutorState(). ExecutorStart() (and ExecutorStart_hook()) now return a Boolean telling the caller if the plan initialization failed. For the replan loop in that context, it makes more sense to have ExecutorStart() either in the same scope or closer to where GetCachedPlan() is invoked. So this commit modifies the following sites: * The ExecutorStart() call in ExplainOnePlan() is moved into a new function ExplainQueryDesc() along with CreateQueryDesc(). Callers of ExplainOnePlan() should now call the new function first. * The ExecutorStart() call in _SPI_pquery() is moved to its caller _SPI_execute_plan(). * The ExecutorStart() call in PortalRunMulti() is moved to PortalStart(). This requires a new List field in PortalData to store the QueryDescs created in PortalStart() and a new memory context for those. One unintended consequence is that CommandCounterIncrement() between queries in the PORTAL_MULTI_QUERY case is now done in the loop in PortalStart() and not in PortalRunMulti(). That still works because the Snapshot registered in QueryDesc/EState is updated to account for the CCI(). This commit also adds a new flag to EState called es_canceled that complements es_finished to denote the new scenario where ExecutorStart() returns with a partially setup planstate tree. Also, to reset the AFTER trigger state that would have been set up in the ExecutorStart(), this adds a new function AfterTriggerCancelQuery() which is called from ExecutorEnd() (not ExecutorFinish()) when es_canceled is true. Note that this commit by itself doesn't make any functional change, because the CachedPlan is not passed into the executor yet. --- contrib/auto_explain/auto_explain.c | 12 +- .../pg_stat_statements/pg_stat_statements.c | 12 +- src/backend/commands/copyto.c | 4 +- src/backend/commands/createas.c | 8 +- src/backend/commands/explain.c | 142 ++++--- src/backend/commands/extension.c | 3 +- src/backend/commands/matview.c | 8 +- src/backend/commands/portalcmds.c | 5 +- src/backend/commands/prepare.c | 31 +- src/backend/commands/trigger.c | 13 + src/backend/executor/execMain.c | 57 ++- src/backend/executor/execParallel.c | 3 +- src/backend/executor/execUtils.c | 1 + src/backend/executor/functions.c | 4 +- src/backend/executor/spi.c | 48 ++- src/backend/tcop/postgres.c | 18 +- src/backend/tcop/pquery.c | 345 +++++++++--------- src/backend/utils/mmgr/portalmem.c | 9 + src/include/commands/explain.h | 7 +- src/include/commands/trigger.h | 1 + src/include/executor/executor.h | 6 +- src/include/nodes/execnodes.h | 3 + src/include/tcop/pquery.h | 2 +- src/include/utils/portal.h | 2 + 24 files changed, 460 insertions(+), 284 deletions(-) diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index c3ac27ae99..a0630d7944 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -78,7 +78,7 @@ static ExecutorRun_hook_type prev_ExecutorRun = NULL; static ExecutorFinish_hook_type prev_ExecutorFinish = NULL; static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; -static void explain_ExecutorStart(QueryDesc *queryDesc, int eflags); +static bool explain_ExecutorStart(QueryDesc *queryDesc, int eflags); static void explain_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, bool execute_once); @@ -258,9 +258,11 @@ _PG_init(void) /* * ExecutorStart hook: start up logging if needed */ -static void +static bool explain_ExecutorStart(QueryDesc *queryDesc, int eflags) { + bool plan_valid; + /* * At the beginning of each top-level statement, decide whether we'll * sample this statement. If nested-statement explaining is enabled, @@ -296,9 +298,9 @@ explain_ExecutorStart(QueryDesc *queryDesc, int eflags) } if (prev_ExecutorStart) - prev_ExecutorStart(queryDesc, eflags); + plan_valid = prev_ExecutorStart(queryDesc, eflags); else - standard_ExecutorStart(queryDesc, eflags); + plan_valid = standard_ExecutorStart(queryDesc, eflags); if (auto_explain_enabled()) { @@ -316,6 +318,8 @@ explain_ExecutorStart(QueryDesc *queryDesc, int eflags) MemoryContextSwitchTo(oldcxt); } } + + return plan_valid; } /* diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 06b65aeef5..5354dff7d7 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -324,7 +324,7 @@ static PlannedStmt *pgss_planner(Query *parse, const char *query_string, int cursorOptions, ParamListInfo boundParams); -static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags); +static bool pgss_ExecutorStart(QueryDesc *queryDesc, int eflags); static void pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, bool execute_once); @@ -961,13 +961,15 @@ pgss_planner(Query *parse, /* * ExecutorStart hook: start up tracking if needed */ -static void +static bool pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) { + bool plan_valid; + if (prev_ExecutorStart) - prev_ExecutorStart(queryDesc, eflags); + plan_valid = prev_ExecutorStart(queryDesc, eflags); else - standard_ExecutorStart(queryDesc, eflags); + plan_valid = standard_ExecutorStart(queryDesc, eflags); /* * If query has queryId zero, don't track it. This prevents double @@ -990,6 +992,8 @@ pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) MemoryContextSwitchTo(oldcxt); } } + + return plan_valid; } /* diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index eaa3172793..a45489f8f5 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -567,8 +567,10 @@ BeginCopyTo(ParseState *pstate, * Call ExecutorStart to prepare the plan for execution. * * ExecutorStart computes a result tupdesc for us + * + * OK to ignore the return value; plan can't become invalid. */ - ExecutorStart(cstate->queryDesc, 0); + (void) ExecutorStart(cstate->queryDesc, 0); tupDesc = cstate->queryDesc->tupDesc; } diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index e91920ca14..167db4cf56 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -329,8 +329,12 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, GetActiveSnapshot(), InvalidSnapshot, dest, params, queryEnv, 0); - /* call ExecutorStart to prepare the plan for execution */ - ExecutorStart(queryDesc, GetIntoRelEFlags(into)); + /* + * call ExecutorStart to prepare the plan for execution + * + * OK to ignore the return value; plan can't become invalid. + */ + (void) ExecutorStart(queryDesc, GetIntoRelEFlags(into)); /* run the plan to completion */ ExecutorRun(queryDesc, ForwardScanDirection, 0, true); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 8570b14f62..fe9314bc96 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -393,6 +393,7 @@ ExplainOneQuery(Query *query, int cursorOptions, else { PlannedStmt *plan; + QueryDesc *queryDesc; instr_time planstart, planduration; BufferUsage bufusage_start, @@ -415,12 +416,87 @@ ExplainOneQuery(Query *query, int cursorOptions, BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start); } + queryDesc = ExplainQueryDesc(plan, queryString, into, es, + params, queryEnv); + Assert(queryDesc); + /* run it (if needed) and produce output */ - ExplainOnePlan(plan, into, es, queryString, params, queryEnv, + ExplainOnePlan(queryDesc, into, es, queryString, params, queryEnv, &planduration, (es->buffers ? &bufusage : NULL)); } } +/* + * ExplainQueryDesc + * Set up QueryDesc for EXPLAINing a given plan + */ +QueryDesc * +ExplainQueryDesc(PlannedStmt *stmt, + const char *queryString, IntoClause *into, ExplainState *es, + ParamListInfo params, QueryEnvironment *queryEnv) +{ + QueryDesc *queryDesc; + DestReceiver *dest; + int eflags; + int instrument_option = 0; + + /* + * Normally we discard the query's output, but if explaining CREATE TABLE + * AS, we'd better use the appropriate tuple receiver. + */ + if (into) + dest = CreateIntoRelDestReceiver(into); + else + dest = None_Receiver; + + if (es->analyze && es->timing) + instrument_option |= INSTRUMENT_TIMER; + else if (es->analyze) + instrument_option |= INSTRUMENT_ROWS; + + if (es->buffers) + instrument_option |= INSTRUMENT_BUFFERS; + if (es->wal) + instrument_option |= INSTRUMENT_WAL; + + /* + * Use a snapshot with an updated command ID to ensure this query sees + * results of any previously executed queries. + */ + PushCopiedSnapshot(GetActiveSnapshot()); + UpdateActiveSnapshotCommandId(); + + /* Create a QueryDesc for the query */ + queryDesc = CreateQueryDesc(stmt, queryString, + GetActiveSnapshot(), InvalidSnapshot, + dest, params, queryEnv, instrument_option); + + /* Select execution options */ + if (es->analyze) + eflags = 0; /* default run-to-completion flags */ + else + eflags = EXEC_FLAG_EXPLAIN_ONLY; + if (es->generic) + eflags |= EXEC_FLAG_EXPLAIN_GENERIC; + if (into) + eflags |= GetIntoRelEFlags(into); + + /* + * Call ExecutorStart to prepare the plan for execution. A cached plan + * may get invalidated during plan intialization. + */ + if (!ExecutorStart(queryDesc, eflags)) + { + /* Clean up. */ + ExecutorEnd(queryDesc); + FreeQueryDesc(queryDesc); + PopActiveSnapshot(); + return NULL; + } + + return queryDesc; +} + /* * ExplainOneUtility - * print out the execution plan for one utility statement @@ -524,29 +600,16 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, * to call it. */ void -ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, +ExplainOnePlan(QueryDesc *queryDesc, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, const BufferUsage *bufusage) { - DestReceiver *dest; - QueryDesc *queryDesc; instr_time starttime; double totaltime = 0; - int eflags; - int instrument_option = 0; - - Assert(plannedstmt->commandType != CMD_UTILITY); - if (es->analyze && es->timing) - instrument_option |= INSTRUMENT_TIMER; - else if (es->analyze) - instrument_option |= INSTRUMENT_ROWS; - - if (es->buffers) - instrument_option |= INSTRUMENT_BUFFERS; - if (es->wal) - instrument_option |= INSTRUMENT_WAL; + Assert(queryDesc->plannedstmt->commandType != CMD_UTILITY); /* * We always collect timing for the entire statement, even when node-level @@ -555,40 +618,6 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, */ INSTR_TIME_SET_CURRENT(starttime); - /* - * Use a snapshot with an updated command ID to ensure this query sees - * results of any previously executed queries. - */ - PushCopiedSnapshot(GetActiveSnapshot()); - UpdateActiveSnapshotCommandId(); - - /* - * Normally we discard the query's output, but if explaining CREATE TABLE - * AS, we'd better use the appropriate tuple receiver. - */ - if (into) - dest = CreateIntoRelDestReceiver(into); - else - dest = None_Receiver; - - /* Create a QueryDesc for the query */ - queryDesc = CreateQueryDesc(plannedstmt, queryString, - GetActiveSnapshot(), InvalidSnapshot, - dest, params, queryEnv, instrument_option); - - /* Select execution options */ - if (es->analyze) - eflags = 0; /* default run-to-completion flags */ - else - eflags = EXEC_FLAG_EXPLAIN_ONLY; - if (es->generic) - eflags |= EXEC_FLAG_EXPLAIN_GENERIC; - if (into) - eflags |= GetIntoRelEFlags(into); - - /* call ExecutorStart to prepare the plan for execution */ - ExecutorStart(queryDesc, eflags); - /* Execute the plan for statistics if asked for */ if (es->analyze) { @@ -4865,6 +4894,17 @@ ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es) } } +/* + * Discard output buffer for a fresh restart. + */ +void +ExplainResetOutput(ExplainState *es) +{ + Assert(es->str); + resetStringInfo(es->str); + ExplainBeginOutput(es); +} + /* * Emit the start-of-output boilerplate. * diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 535072d181..b702a65e81 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -801,7 +801,8 @@ execute_sql_string(const char *sql) GetActiveSnapshot(), NULL, dest, NULL, NULL, 0); - ExecutorStart(qdesc, 0); + /* OK to ignore the return value; plan can't become invalid. */ + (void) ExecutorStart(qdesc, 0); ExecutorRun(qdesc, ForwardScanDirection, 0, true); ExecutorFinish(qdesc); ExecutorEnd(qdesc); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index ac2e74fa3f..7124994a43 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -412,8 +412,12 @@ refresh_matview_datafill(DestReceiver *dest, Query *query, GetActiveSnapshot(), InvalidSnapshot, dest, NULL, NULL, 0); - /* call ExecutorStart to prepare the plan for execution */ - ExecutorStart(queryDesc, 0); + /* + * call ExecutorStart to prepare the plan for execution + * + * OK to ignore the return value; plan can't become invalid. + */ + (void) ExecutorStart(queryDesc, 0); /* run the plan */ ExecutorRun(queryDesc, ForwardScanDirection, 0, true); diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 73ed7aa2f0..5120f93414 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -142,9 +142,10 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa /* * Start execution, inserting parameters if any. + * + * OK to ignore the return value; plan can't become invalid here. */ - PortalStart(portal, params, 0, GetActiveSnapshot()); - + (void) PortalStart(portal, params, 0, GetActiveSnapshot()); Assert(portal->strategy == PORTAL_ONE_SELECT); /* diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 18f70319fc..bcdf56fe32 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -183,6 +183,7 @@ ExecuteQuery(ParseState *pstate, paramLI = EvaluateParams(pstate, entry, stmt->params, estate); } +replan: /* Create a new portal to run the query in */ portal = CreateNewPortal(); /* Don't display the portal in pg_cursors, it is for internal use only */ @@ -251,9 +252,15 @@ ExecuteQuery(ParseState *pstate, } /* - * Run the portal as appropriate. + * Run the portal as appropriate. If the portal has a cached plan and + * it's found to be invalidated during the initialization of its plan + * trees, the plan must be regenerated. */ - PortalStart(portal, paramLI, eflags, GetActiveSnapshot()); + if (!PortalStart(portal, paramLI, eflags, GetActiveSnapshot())) + { + PortalDrop(portal, false); + goto replan; + } (void) PortalRun(portal, count, false, true, dest, dest, qc); @@ -574,7 +581,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, { PreparedStatement *entry; const char *query_string; - CachedPlan *cplan; + CachedPlan *cplan = NULL; List *plan_list; ListCell *p; ParamListInfo paramLI = NULL; @@ -618,6 +625,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, } /* Replan if needed, and acquire a transient refcount */ +replan: cplan = GetCachedPlan(entry->plansource, paramLI, CurrentResourceOwner, queryEnv); @@ -639,8 +647,21 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, PlannedStmt *pstmt = lfirst_node(PlannedStmt, p); if (pstmt->commandType != CMD_UTILITY) - ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv, - &planduration, (es->buffers ? &bufusage : NULL)); + { + QueryDesc *queryDesc; + + queryDesc = ExplainQueryDesc(pstmt, queryString, + into, es, paramLI, queryEnv); + if (queryDesc == NULL) + { + ExplainResetOutput(es); + ReleaseCachedPlan(cplan, CurrentResourceOwner); + goto replan; + } + ExplainOnePlan(queryDesc, into, es, query_string, paramLI, + queryEnv, &planduration, + (es->buffers ? &bufusage : NULL)); + } else ExplainOneUtility(pstmt->utilityStmt, into, es, query_string, paramLI, queryEnv); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 52177759ab..dd139432b9 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -5009,6 +5009,19 @@ AfterTriggerBeginQuery(void) afterTriggers.query_depth++; } +/* ---------- + * AfterTriggerCancelQuery() + * + * Called from ExecutorEnd() if the query execution was canceled. + * ---------- + */ +void +AfterTriggerCancelQuery(void) +{ + /* Set to a value denoting that no query is active. */ + afterTriggers.query_depth = -1; +} + /* ---------- * AfterTriggerEndQuery() diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 66f1b7398d..383ebee008 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -79,7 +79,7 @@ ExecutorEnd_hook_type ExecutorEnd_hook = NULL; ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook = NULL; /* decls for local routines only used within this module */ -static void InitPlan(QueryDesc *queryDesc, int eflags); +static bool InitPlan(QueryDesc *queryDesc, int eflags); static void CheckValidRowMarkRel(Relation rel, RowMarkType markType); static void ExecPostprocessPlan(EState *estate); static void ExecEndPlan(PlanState *planstate, EState *estate); @@ -119,6 +119,13 @@ static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree); * * eflags contains flag bits as described in executor.h. * + * Plan initialization may fail if the input plan tree is found to have been + * invalidated, which can happen if it comes from a CachedPlan. + * + * Returns true if plan was successfully initialized and false otherwise. If + * the latter, the caller must call ExecutorEnd() on 'queryDesc' to clean up + * after failed plan initialization. + * * NB: the CurrentMemoryContext when this is called will become the parent * of the per-query context used for this Executor invocation. * @@ -128,7 +135,7 @@ static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree); * * ---------------------------------------------------------------- */ -void +bool ExecutorStart(QueryDesc *queryDesc, int eflags) { /* @@ -140,14 +147,15 @@ ExecutorStart(QueryDesc *queryDesc, int eflags) pgstat_report_query_id(queryDesc->plannedstmt->queryId, false); if (ExecutorStart_hook) - (*ExecutorStart_hook) (queryDesc, eflags); - else - standard_ExecutorStart(queryDesc, eflags); + return (*ExecutorStart_hook) (queryDesc, eflags); + + return standard_ExecutorStart(queryDesc, eflags); } -void +bool standard_ExecutorStart(QueryDesc *queryDesc, int eflags) { + bool plan_valid; EState *estate; MemoryContext oldcontext; @@ -263,9 +271,14 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) /* * Initialize the plan state tree */ - InitPlan(queryDesc, eflags); + plan_valid = InitPlan(queryDesc, eflags); + + /* Mark execution as canceled if plan won't be executed. */ + estate->es_canceled = !plan_valid; MemoryContextSwitchTo(oldcontext); + + return plan_valid; } /* ---------------------------------------------------------------- @@ -325,6 +338,7 @@ standard_ExecutorRun(QueryDesc *queryDesc, estate = queryDesc->estate; Assert(estate != NULL); + Assert(!estate->es_canceled); Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); /* @@ -429,7 +443,7 @@ standard_ExecutorFinish(QueryDesc *queryDesc) Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); /* This should be run once and only once per Executor instance */ - Assert(!estate->es_finished); + Assert(!estate->es_finished && !estate->es_canceled); /* Switch into per-query memory context */ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); @@ -488,11 +502,11 @@ standard_ExecutorEnd(QueryDesc *queryDesc) Assert(estate != NULL); /* - * Check that ExecutorFinish was called, unless in EXPLAIN-only mode. This - * Assert is needed because ExecutorFinish is new as of 9.1, and callers - * might forget to call it. + * Check that ExecutorFinish was called, unless in EXPLAIN-only mode or if + * execution was canceled. This Assert is needed because ExecutorFinish is + * new as of 9.1, and callers might forget to call it. */ - Assert(estate->es_finished || + Assert(estate->es_finished || estate->es_canceled || (estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); /* @@ -506,6 +520,14 @@ standard_ExecutorEnd(QueryDesc *queryDesc) UnregisterSnapshot(estate->es_snapshot); UnregisterSnapshot(estate->es_crosscheck_snapshot); + /* + * Cancel trigger execution too if the query execution was canceled. + */ + if (estate->es_canceled && + !(estate->es_top_eflags & + (EXEC_FLAG_SKIP_TRIGGERS | EXEC_FLAG_EXPLAIN_ONLY))) + AfterTriggerCancelQuery(); + /* * Must switch out of context before destroying it */ @@ -829,9 +851,12 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) * * Initializes the query plan: open files, allocate storage * and start up the rule manager + * + * Returns true if the plan tree is successfully initialized for execution, + * false otherwise. * ---------------------------------------------------------------- */ -static void +static bool InitPlan(QueryDesc *queryDesc, int eflags) { CmdType operation = queryDesc->operation; @@ -1014,9 +1039,15 @@ InitPlan(QueryDesc *queryDesc, int eflags) } } + queryDesc->tupDesc = tupType; + Assert(planstate != NULL); + queryDesc->planstate = planstate; + return true; + plan_init_suspended: queryDesc->tupDesc = tupType; queryDesc->planstate = planstate; + return false; } /* diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index cc2b8ccab7..f84a3a17d5 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -1430,7 +1430,8 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc) /* Start up the executor */ queryDesc->plannedstmt->jitFlags = fpes->jit_flags; - ExecutorStart(queryDesc, fpes->eflags); + /* OK to ignore the return value; plan can't become invalid. */ + (void) ExecutorStart(queryDesc, fpes->eflags); /* Special executor initialization steps for parallel workers */ queryDesc->planstate->state->es_query_dsa = area; diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index c3f7279b06..da8a1511ac 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -151,6 +151,7 @@ CreateExecutorState(void) estate->es_top_eflags = 0; estate->es_instrument = 0; estate->es_finished = false; + estate->es_canceled = false; estate->es_exprcontexts = NIL; diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index f55424eb5a..8cf0b3132d 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -862,7 +862,9 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) eflags = EXEC_FLAG_SKIP_TRIGGERS; else eflags = 0; /* default run-to-completion flags */ - ExecutorStart(es->qd, eflags); + + /* OK to ignore the return value; plan can't become invalid. */ + (void) ExecutorStart(es->qd, eflags); } es->status = F_EXEC_RUN; diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 33975687b3..6a96d7fc22 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -71,7 +71,7 @@ static int _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, Datum *Values, const char *Nulls); -static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount); +static int _SPI_pquery(QueryDesc *queryDesc, uint64 tcount); static void _SPI_error_callback(void *arg); @@ -1582,6 +1582,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, Snapshot snapshot; MemoryContext oldcontext; Portal portal; + bool plan_valid; SPICallbackArg spicallbackarg; ErrorContextCallback spierrcontext; @@ -1623,6 +1624,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, _SPI_current->processed = 0; _SPI_current->tuptable = NULL; +replan: /* Create the portal */ if (name == NULL || name[0] == '\0') { @@ -1766,15 +1768,23 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, } /* - * Start portal execution. + * Start portal execution. If the portal contains a cached plan, it must + * be recreated if the cached plan was found to have been invalidated when + * initializing one of the plan trees contained in it. */ - PortalStart(portal, paramLI, 0, snapshot); + plan_valid = PortalStart(portal, paramLI, 0, snapshot); Assert(portal->strategy != PORTAL_MULTI_QUERY); /* Pop the error context stack */ error_context_stack = spierrcontext.previous; + if (!plan_valid) + { + PortalDrop(portal, false); + goto replan; + } + /* Pop the SPI stack */ _SPI_end_call(true); @@ -2552,6 +2562,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, * Replan if needed, and increment plan refcount. If it's a saved * plan, the refcount must be backed by the plan_owner. */ +replan: cplan = GetCachedPlan(plansource, options->params, plan_owner, _SPI_current->queryEnv); @@ -2661,6 +2672,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, { QueryDesc *qdesc; Snapshot snap; + int eflags; if (ActiveSnapshotSet()) snap = GetActiveSnapshot(); @@ -2674,8 +2686,23 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, options->params, _SPI_current->queryEnv, 0); - res = _SPI_pquery(qdesc, fire_triggers, - canSetTag ? options->tcount : 0); + + /* Select execution options */ + if (fire_triggers) + eflags = 0; /* default run-to-completion flags */ + else + eflags = EXEC_FLAG_SKIP_TRIGGERS; + + if (!ExecutorStart(qdesc, eflags)) + { + ExecutorEnd(qdesc); + FreeQueryDesc(qdesc); + Assert(cplan); + ReleaseCachedPlan(cplan, plan_owner); + goto replan; + } + + res = _SPI_pquery(qdesc, canSetTag ? options->tcount : 0); FreeQueryDesc(qdesc); } else @@ -2850,10 +2877,9 @@ _SPI_convert_params(int nargs, Oid *argtypes, } static int -_SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount) +_SPI_pquery(QueryDesc *queryDesc, uint64 tcount) { int operation = queryDesc->operation; - int eflags; int res; switch (operation) @@ -2897,14 +2923,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount) ResetUsage(); #endif - /* Select execution options */ - if (fire_triggers) - eflags = 0; /* default run-to-completion flags */ - else - eflags = EXEC_FLAG_SKIP_TRIGGERS; - - ExecutorStart(queryDesc, eflags); - ExecutorRun(queryDesc, ForwardScanDirection, tcount, true); _SPI_current->processed = queryDesc->estate->es_processed; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index e4756f8be2..204002cff2 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1232,7 +1232,12 @@ exec_simple_query(const char *query_string) /* * Start the portal. No parameters here. */ - PortalStart(portal, NULL, 0, InvalidSnapshot); + { + bool plan_valid PG_USED_FOR_ASSERTS_ONLY; + + plan_valid = PortalStart(portal, NULL, 0, InvalidSnapshot); + Assert(plan_valid); + } /* * Select the appropriate output format: text unless we are doing a @@ -1737,6 +1742,7 @@ exec_bind_message(StringInfo input_message) "commands ignored until end of transaction block"), errdetail_abort())); +replan: /* * Create the portal. Allow silent replacement of an existing portal only * if the unnamed portal is specified. @@ -2028,9 +2034,15 @@ exec_bind_message(StringInfo input_message) PopActiveSnapshot(); /* - * And we're ready to start portal execution. + * Start portal execution. If the portal contains a cached plan, it must + * be recreated if the cached plan was found to have been invalidated when + * initializing one of the plan trees contained in it. */ - PortalStart(portal, params, 0, InvalidSnapshot); + if (!PortalStart(portal, params, 0, InvalidSnapshot)) + { + PortalDrop(portal, false); + goto replan; + } /* * Apply the result format requests to the portal. diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 5565f200c3..9a96b77f1e 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -19,6 +19,7 @@ #include "access/xact.h" #include "commands/prepare.h" +#include "executor/execdesc.h" #include "executor/tstoreReceiver.h" #include "miscadmin.h" #include "pg_trace.h" @@ -35,12 +36,6 @@ Portal ActivePortal = NULL; -static void ProcessQuery(PlannedStmt *plan, - const char *sourceText, - ParamListInfo params, - QueryEnvironment *queryEnv, - DestReceiver *dest, - QueryCompletion *qc); static void FillPortalStore(Portal portal, bool isTopLevel); static uint64 RunFromStore(Portal portal, ScanDirection direction, uint64 count, DestReceiver *dest); @@ -116,86 +111,6 @@ FreeQueryDesc(QueryDesc *qdesc) } -/* - * ProcessQuery - * Execute a single plannable query within a PORTAL_MULTI_QUERY, - * PORTAL_ONE_RETURNING, or PORTAL_ONE_MOD_WITH portal - * - * plan: the plan tree for the query - * sourceText: the source text of the query - * params: any parameters needed - * dest: where to send results - * qc: where to store the command completion status data. - * - * qc may be NULL if caller doesn't want a status string. - * - * Must be called in a memory context that will be reset or deleted on - * error; otherwise the executor's memory usage will be leaked. - */ -static void -ProcessQuery(PlannedStmt *plan, - const char *sourceText, - ParamListInfo params, - QueryEnvironment *queryEnv, - DestReceiver *dest, - QueryCompletion *qc) -{ - QueryDesc *queryDesc; - - /* - * Create the QueryDesc object - */ - queryDesc = CreateQueryDesc(plan, sourceText, - GetActiveSnapshot(), InvalidSnapshot, - dest, params, queryEnv, 0); - - /* - * Call ExecutorStart to prepare the plan for execution - */ - ExecutorStart(queryDesc, 0); - - /* - * Run the plan to completion. - */ - ExecutorRun(queryDesc, ForwardScanDirection, 0, true); - - /* - * Build command completion status data, if caller wants one. - */ - if (qc) - { - switch (queryDesc->operation) - { - case CMD_SELECT: - SetQueryCompletion(qc, CMDTAG_SELECT, queryDesc->estate->es_processed); - break; - case CMD_INSERT: - SetQueryCompletion(qc, CMDTAG_INSERT, queryDesc->estate->es_processed); - break; - case CMD_UPDATE: - SetQueryCompletion(qc, CMDTAG_UPDATE, queryDesc->estate->es_processed); - break; - case CMD_DELETE: - SetQueryCompletion(qc, CMDTAG_DELETE, queryDesc->estate->es_processed); - break; - case CMD_MERGE: - SetQueryCompletion(qc, CMDTAG_MERGE, queryDesc->estate->es_processed); - break; - default: - SetQueryCompletion(qc, CMDTAG_UNKNOWN, queryDesc->estate->es_processed); - break; - } - } - - /* - * Now, we close down all the scans and free allocated resources. - */ - ExecutorFinish(queryDesc); - ExecutorEnd(queryDesc); - - FreeQueryDesc(queryDesc); -} - /* * ChoosePortalStrategy * Select portal execution strategy given the intended statement list. @@ -426,19 +341,21 @@ FetchStatementTargetList(Node *stmt) * presently ignored for non-PORTAL_ONE_SELECT portals (it's only intended * to be used for cursors). * - * On return, portal is ready to accept PortalRun() calls, and the result - * tupdesc (if any) is known. + * True is returned if portal is ready to accept PortalRun() calls, and the + * result tupdesc (if any) is known. False if the plan tree is no longer + * valid, in which case, the caller must retry after generating a new + * CachedPlan. */ -void +bool PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snapshot) { Portal saveActivePortal; ResourceOwner saveResourceOwner; - MemoryContext savePortalContext; MemoryContext oldContext; QueryDesc *queryDesc; - int myeflags; + int myeflags = 0; + bool plan_valid = true; Assert(PortalIsValid(portal)); Assert(portal->status == PORTAL_DEFINED); @@ -448,15 +365,13 @@ PortalStart(Portal portal, ParamListInfo params, */ saveActivePortal = ActivePortal; saveResourceOwner = CurrentResourceOwner; - savePortalContext = PortalContext; PG_TRY(); { ActivePortal = portal; if (portal->resowner) CurrentResourceOwner = portal->resowner; - PortalContext = portal->portalContext; - oldContext = MemoryContextSwitchTo(PortalContext); + oldContext = MemoryContextSwitchTo(portal->queryContext); /* Must remember portal param list, if any */ portal->portalParams = params; @@ -472,6 +387,8 @@ PortalStart(Portal portal, ParamListInfo params, switch (portal->strategy) { case PORTAL_ONE_SELECT: + case PORTAL_ONE_RETURNING: + case PORTAL_ONE_MOD_WITH: /* Must set snapshot before starting executor. */ if (snapshot) @@ -489,8 +406,8 @@ PortalStart(Portal portal, ParamListInfo params, */ /* - * Create QueryDesc in portal's context; for the moment, set - * the destination to DestNone. + * Create QueryDesc in portal->queryContext; for the moment, + * set the destination to DestNone. */ queryDesc = CreateQueryDesc(linitial_node(PlannedStmt, portal->stmts), portal->sourceText, @@ -501,30 +418,51 @@ PortalStart(Portal portal, ParamListInfo params, portal->queryEnv, 0); + /* Remember for PortalRunMulti(). */ + if (portal->strategy == PORTAL_ONE_RETURNING || + portal->strategy == PORTAL_ONE_MOD_WITH) + portal->qdescs = list_make1(queryDesc); + /* * If it's a scrollable cursor, executor needs to support * REWIND and backwards scan, as well as whatever the caller * might've asked for. */ - if (portal->cursorOptions & CURSOR_OPT_SCROLL) + if (portal->strategy == PORTAL_ONE_SELECT && + (portal->cursorOptions & CURSOR_OPT_SCROLL)) myeflags = eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD; else myeflags = eflags; /* - * Call ExecutorStart to prepare the plan for execution + * Call ExecutorStart to prepare the plan for execution. A + * cached plan may get invalidated during plan intialization. */ - ExecutorStart(queryDesc, myeflags); + if (!ExecutorStart(queryDesc, myeflags)) + { + ExecutorEnd(queryDesc); + FreeQueryDesc(queryDesc); + PopActiveSnapshot(); + plan_valid = false; + goto plan_init_failed; + } /* - * This tells PortalCleanup to shut down the executor + * This tells PortalCleanup to shut down the executor, though + * not needed for queries handled by PortalRunMulti(). */ - portal->queryDesc = queryDesc; + if (portal->strategy == PORTAL_ONE_SELECT) + portal->queryDesc = queryDesc; /* - * Remember tuple descriptor (computed by ExecutorStart) + * Remember tuple descriptor (computed by ExecutorStart), + * though make it independent of QueryDesc for queries handled + * by PortalRunMulti(). */ - portal->tupDesc = queryDesc->tupDesc; + if (portal->strategy != PORTAL_ONE_SELECT) + portal->tupDesc = CreateTupleDescCopy(queryDesc->tupDesc); + else + portal->tupDesc = queryDesc->tupDesc; /* * Reset cursor position data to "start of query" @@ -536,29 +474,6 @@ PortalStart(Portal portal, ParamListInfo params, PopActiveSnapshot(); break; - case PORTAL_ONE_RETURNING: - case PORTAL_ONE_MOD_WITH: - - /* - * We don't start the executor until we are told to run the - * portal. We do need to set up the result tupdesc. - */ - { - PlannedStmt *pstmt; - - pstmt = PortalGetPrimaryStmt(portal); - portal->tupDesc = - ExecCleanTypeFromTL(pstmt->planTree->targetlist); - } - - /* - * Reset cursor position data to "start of query" - */ - portal->atStart = true; - portal->atEnd = false; /* allow fetches */ - portal->portalPos = 0; - break; - case PORTAL_UTIL_SELECT: /* @@ -581,7 +496,81 @@ PortalStart(Portal portal, ParamListInfo params, break; case PORTAL_MULTI_QUERY: - /* Need do nothing now */ + { + ListCell *lc; + bool first = true; + + myeflags = eflags; + foreach(lc, portal->stmts) + { + PlannedStmt *plan = lfirst_node(PlannedStmt, lc); + bool is_utility = (plan->utilityStmt != NULL); + + /* + * Push the snapshot to be used by the executor. + */ + if (!is_utility) + { + /* + * Must copy the snapshot for all statements + * except thec first as we'll need to update its + * command ID. + */ + if (!first) + PushCopiedSnapshot(GetTransactionSnapshot()); + else + PushActiveSnapshot(GetTransactionSnapshot()); + } + + /* + * From the 2nd statement onwards, update the command + * ID and the snapshot to match. + */ + if (!first) + { + CommandCounterIncrement(); + UpdateActiveSnapshotCommandId(); + } + + first = false; + + /* + * Create the QueryDesc. DestReceiver will be set in + * PortalRunMulti() before calling ExecutorRun(). + */ + queryDesc = CreateQueryDesc(plan, + portal->sourceText, + !is_utility ? + GetActiveSnapshot() : + InvalidSnapshot, + InvalidSnapshot, + NULL, + params, + portal->queryEnv, 0); + + /* Remember for PortalRunMulti() */ + portal->qdescs = lappend(portal->qdescs, queryDesc); + + if (is_utility) + continue; + + /* + * Call ExecutorStart to prepare the plan for + * execution. A cached plan may get invalidated + * during plan intialization. + */ + if (!ExecutorStart(queryDesc, myeflags)) + { + PopActiveSnapshot(); + ExecutorEnd(queryDesc); + FreeQueryDesc(queryDesc); + plan_valid = false; + goto plan_init_failed; + } + PopActiveSnapshot(); + } + } + portal->tupDesc = NULL; break; } @@ -594,19 +583,20 @@ PortalStart(Portal portal, ParamListInfo params, /* Restore global vars and propagate error */ ActivePortal = saveActivePortal; CurrentResourceOwner = saveResourceOwner; - PortalContext = savePortalContext; PG_RE_THROW(); } PG_END_TRY(); + portal->status = PORTAL_READY; + +plan_init_failed: MemoryContextSwitchTo(oldContext); ActivePortal = saveActivePortal; CurrentResourceOwner = saveResourceOwner; - PortalContext = savePortalContext; - portal->status = PORTAL_READY; + return plan_valid; } /* @@ -1193,7 +1183,7 @@ PortalRunMulti(Portal portal, QueryCompletion *qc) { bool active_snapshot_set = false; - ListCell *stmtlist_item; + ListCell *qdesc_item; /* * If the destination is DestRemoteExecute, change to DestNone. The @@ -1214,9 +1204,10 @@ PortalRunMulti(Portal portal, * Loop to handle the individual queries generated from a single parsetree * by analysis and rewrite. */ - foreach(stmtlist_item, portal->stmts) + foreach(qdesc_item, portal->qdescs) { - PlannedStmt *pstmt = lfirst_node(PlannedStmt, stmtlist_item); + QueryDesc *qdesc = (QueryDesc *) lfirst(qdesc_item); + PlannedStmt *pstmt = qdesc->plannedstmt; /* * If we got a cancel signal in prior command, quit @@ -1233,33 +1224,26 @@ PortalRunMulti(Portal portal, if (log_executor_stats) ResetUsage(); - /* - * Must always have a snapshot for plannable queries. First time - * through, take a new snapshot; for subsequent queries in the - * same portal, just update the snapshot's copy of the command - * counter. - */ + /* Push the snapshot for plannable queries. */ if (!active_snapshot_set) { - Snapshot snapshot = GetTransactionSnapshot(); + Snapshot snapshot = qdesc->snapshot; - /* If told to, register the snapshot and save in portal */ + /* + * If told to, register the snapshot and save in portal + * + * Note that the command ID of qdesc->snapshot for 2nd query + * onwards would have been updated in PortalStart() to account + * for CCI() done between queries, but it's OK that here we + * don't likewise update holdSnapshot's command ID. + */ if (setHoldSnapshot) { snapshot = RegisterSnapshot(snapshot); portal->holdSnapshot = snapshot; } - /* - * We can't have the holdSnapshot also be the active one, - * because UpdateActiveSnapshotCommandId would complain. So - * force an extra snapshot copy. Plain PushActiveSnapshot - * would have copied the transaction snapshot anyway, so this - * only adds a copy step when setHoldSnapshot is true. (It's - * okay for the command ID of the active snapshot to diverge - * from what holdSnapshot has.) - */ - PushCopiedSnapshot(snapshot); + PushActiveSnapshot(snapshot); /* * As for PORTAL_ONE_SELECT portals, it does not seem @@ -1268,26 +1252,39 @@ PortalRunMulti(Portal portal, active_snapshot_set = true; } - else - UpdateActiveSnapshotCommandId(); + /* + * Run the plan to completion. + */ + qdesc->dest = dest; + ExecutorRun(qdesc, ForwardScanDirection, 0, true); + + /* + * Build command completion status data if needed. + */ if (pstmt->canSetTag) { - /* statement can set tag string */ - ProcessQuery(pstmt, - portal->sourceText, - portal->portalParams, - portal->queryEnv, - dest, qc); - } - else - { - /* stmt added by rewrite cannot set tag */ - ProcessQuery(pstmt, - portal->sourceText, - portal->portalParams, - portal->queryEnv, - altdest, NULL); + switch (qdesc->operation) + { + case CMD_SELECT: + SetQueryCompletion(qc, CMDTAG_SELECT, qdesc->estate->es_processed); + break; + case CMD_INSERT: + SetQueryCompletion(qc, CMDTAG_INSERT, qdesc->estate->es_processed); + break; + case CMD_UPDATE: + SetQueryCompletion(qc, CMDTAG_UPDATE, qdesc->estate->es_processed); + break; + case CMD_DELETE: + SetQueryCompletion(qc, CMDTAG_DELETE, qdesc->estate->es_processed); + break; + case CMD_MERGE: + SetQueryCompletion(qc, CMDTAG_MERGE, qdesc->estate->es_processed); + break; + default: + SetQueryCompletion(qc, CMDTAG_UNKNOWN, qdesc->estate->es_processed); + break; + } } if (log_executor_stats) @@ -1342,12 +1339,12 @@ PortalRunMulti(Portal portal, if (portal->stmts == NIL) break; - /* - * Increment command counter between queries, but not after the last - * one. - */ - if (lnext(portal->stmts, stmtlist_item) != NULL) - CommandCounterIncrement(); + if (qdesc->estate) + { + ExecutorFinish(qdesc); + ExecutorEnd(qdesc); + } + FreeQueryDesc(qdesc); } /* Pop the snapshot if we pushed one. */ diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 06dfa85f04..0cad450dcd 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -201,6 +201,13 @@ CreatePortal(const char *name, bool allowDup, bool dupSilent) portal->portalContext = AllocSetContextCreate(TopPortalContext, "PortalContext", ALLOCSET_SMALL_SIZES); + /* + * initialize portal's query context to store QueryDescs created during + * PortalStart() and then used in PortalRun(). + */ + portal->queryContext = AllocSetContextCreate(TopPortalContext, + "PortalQueryContext", + ALLOCSET_SMALL_SIZES); /* create a resource owner for the portal */ portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner, @@ -224,6 +231,7 @@ CreatePortal(const char *name, bool allowDup, bool dupSilent) /* for named portals reuse portal->name copy */ MemoryContextSetIdentifier(portal->portalContext, portal->name[0] ? portal->name : ""); + MemoryContextSetIdentifier(portal->queryContext, portal->name[0] ? portal->name : ""); return portal; } @@ -594,6 +602,7 @@ PortalDrop(Portal portal, bool isTopCommit) /* release subsidiary storage */ MemoryContextDelete(portal->portalContext); + MemoryContextDelete(portal->queryContext); /* release portal struct (it's in TopPortalContext) */ pfree(portal); diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 3d3e632a0c..37554727ee 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -88,7 +88,11 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv); -extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, +extern QueryDesc *ExplainQueryDesc(PlannedStmt *stmt, + const char *queryString, IntoClause *into, ExplainState *es, + ParamListInfo params, QueryEnvironment *queryEnv); +extern void ExplainOnePlan(QueryDesc *queryDesc, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params, QueryEnvironment *queryEnv, const instr_time *planduration, @@ -104,6 +108,7 @@ extern void ExplainQueryParameters(ExplainState *es, ParamListInfo params, int m extern void ExplainBeginOutput(ExplainState *es); extern void ExplainEndOutput(ExplainState *es); +extern void ExplainResetOutput(ExplainState *es); extern void ExplainSeparatePlans(ExplainState *es); extern void ExplainPropertyList(const char *qlabel, List *data, diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 430e3ca7dd..d4f7c29301 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -257,6 +257,7 @@ extern void ExecASTruncateTriggers(EState *estate, extern void AfterTriggerBeginXact(void); extern void AfterTriggerBeginQuery(void); +extern void AfterTriggerCancelQuery(void); extern void AfterTriggerEndQuery(EState *estate); extern void AfterTriggerFireDeferred(void); extern void AfterTriggerEndXact(bool isCommit); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 72cbf120c5..10c5cda169 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -73,7 +73,7 @@ /* Hook for plugins to get control in ExecutorStart() */ -typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags); +typedef bool (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags); extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook; /* Hook for plugins to get control in ExecutorRun() */ @@ -198,8 +198,8 @@ ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull) /* * prototypes from functions in execMain.c */ -extern void ExecutorStart(QueryDesc *queryDesc, int eflags); -extern void standard_ExecutorStart(QueryDesc *queryDesc, int eflags); +extern bool ExecutorStart(QueryDesc *queryDesc, int eflags); +extern bool standard_ExecutorStart(QueryDesc *queryDesc, int eflags); extern void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, bool execute_once); extern void standard_ExecutorRun(QueryDesc *queryDesc, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index b2a576b76d..0922be6678 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -670,6 +670,9 @@ typedef struct EState int es_top_eflags; /* eflags passed to ExecutorStart */ int es_instrument; /* OR of InstrumentOption flags */ bool es_finished; /* true when ExecutorFinish is done */ + bool es_canceled; /* true when execution was canceled + * upon encountering that plan was invalided + * during ExecInitNode() */ List *es_exprcontexts; /* List of ExprContexts within EState */ diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index a5e65b98aa..577b81a9ee 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -29,7 +29,7 @@ extern List *FetchPortalTargetList(Portal portal); extern List *FetchStatementTargetList(Node *stmt); -extern void PortalStart(Portal portal, ParamListInfo params, +extern bool PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snapshot); extern void PortalSetResultFormat(Portal portal, int nFormats, diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index aa08b1e0fc..af059e30f8 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -138,6 +138,8 @@ typedef struct PortalData QueryCompletion qc; /* command completion data for executed query */ List *stmts; /* list of PlannedStmts */ CachedPlan *cplan; /* CachedPlan, if stmts are from one */ + List *qdescs; /* list of QueryDescs */ + MemoryContext queryContext; /* memory for QueryDescs and children */ ParamListInfo portalParams; /* params to pass to query */ QueryEnvironment *queryEnv; /* environment for query */ -- 2.35.3