diff options
Diffstat (limited to 'src/backend/executor/execMain.c')
-rw-r--r-- | src/backend/executor/execMain.c | 127 |
1 files changed, 117 insertions, 10 deletions
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 963aa390620..0493b7d5365 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -55,11 +55,13 @@ #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" #include "utils/lsyscache.h" #include "utils/partcache.h" +#include "utils/plancache.h" #include "utils/rls.h" #include "utils/snapmgr.h" @@ -114,11 +116,16 @@ static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree); * get control when ExecutorStart is called. Such a plugin would * normally call standard_ExecutorStart(). * + * Return value indicates if the plan has been initialized successfully so + * that queryDesc->planstate contains a valid PlanState tree. It may not + * if the plan got invalidated during InitPlan(). * ---------------------------------------------------------------- */ -void +bool ExecutorStart(QueryDesc *queryDesc, int eflags) { + bool plan_valid; + /* * In some cases (e.g. an EXECUTE statement or an execute message with the * extended query protocol) the query_id won't be reported, so do it now. @@ -130,12 +137,14 @@ ExecutorStart(QueryDesc *queryDesc, int eflags) pgstat_report_query_id(queryDesc->plannedstmt->queryId, false); if (ExecutorStart_hook) - (*ExecutorStart_hook) (queryDesc, eflags); + plan_valid = (*ExecutorStart_hook) (queryDesc, eflags); else - standard_ExecutorStart(queryDesc, eflags); + plan_valid = standard_ExecutorStart(queryDesc, eflags); + + return plan_valid; } -void +bool standard_ExecutorStart(QueryDesc *queryDesc, int eflags) { EState *estate; @@ -259,6 +268,64 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) InitPlan(queryDesc, eflags); MemoryContextSwitchTo(oldcontext); + + return ExecPlanStillValid(queryDesc->estate); +} + +/* + * ExecutorStartCachedPlan + * Start execution for a given query in the CachedPlanSource, replanning + * if the plan is invalidated due to deferred locks taken during the + * plan's initialization + * + * This function handles cases where the CachedPlan given in queryDesc->cplan + * might become invalid during the initialization of the plan given in + * queryDesc->plannedstmt, particularly when prunable relations in it are + * locked after performing initial pruning. If the locks invalidate the plan, + * the function calls UpdateCachedPlan() to replan all queries in the + * CachedPlan, and then retries initialization. + * + * The function repeats the process until ExecutorStart() successfully + * initializes the plan, that is without the CachedPlan becoming invalid. + */ +void +ExecutorStartCachedPlan(QueryDesc *queryDesc, int eflags, + CachedPlanSource *plansource, + int query_index) +{ + if (unlikely(queryDesc->cplan == NULL)) + elog(ERROR, "ExecutorStartCachedPlan(): missing CachedPlan"); + if (unlikely(plansource == NULL)) + elog(ERROR, "ExecutorStartCachedPlan(): missing CachedPlanSource"); + + /* + * Loop and retry with an updated plan until no further invalidation + * occurs. + */ + while (1) + { + if (!ExecutorStart(queryDesc, eflags)) + { + /* + * Clean up the current execution state before creating the new + * plan to retry ExecutorStart(). Mark execution as aborted to + * ensure that AFTER trigger state is properly reset. + */ + queryDesc->estate->es_aborted = true; + ExecutorEnd(queryDesc); + + /* Retry ExecutorStart() with an updated plan tree. */ + queryDesc->plannedstmt = UpdateCachedPlan(plansource, query_index, + queryDesc->queryEnv); + } + else + + /* + * Exit the loop if the plan is initialized successfully and no + * sinval messages were received that invalidated the CachedPlan. + */ + break; + } } /* ---------------------------------------------------------------- @@ -317,6 +384,7 @@ standard_ExecutorRun(QueryDesc *queryDesc, estate = queryDesc->estate; Assert(estate != NULL); + Assert(!estate->es_aborted); Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); /* caller must ensure the query's snapshot is active */ @@ -423,8 +491,11 @@ standard_ExecutorFinish(QueryDesc *queryDesc) Assert(estate != NULL); Assert(!(estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); - /* This should be run once and only once per Executor instance */ - Assert(!estate->es_finished); + /* + * This should be run once and only once per Executor instance and never + * if the execution was aborted. + */ + Assert(!estate->es_finished && !estate->es_aborted); /* Switch into per-query memory context */ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); @@ -487,11 +558,10 @@ standard_ExecutorEnd(QueryDesc *queryDesc) (PgStat_Counter) estate->es_parallel_workers_launched); /* - * 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 aborted. */ - Assert(estate->es_finished || + Assert(estate->es_finished || estate->es_aborted || (estate->es_top_eflags & EXEC_FLAG_EXPLAIN_ONLY)); /* @@ -506,6 +576,14 @@ standard_ExecutorEnd(QueryDesc *queryDesc) UnregisterSnapshot(estate->es_crosscheck_snapshot); /* + * Reset AFTER trigger module if the query execution was aborted. + */ + if (estate->es_aborted && + !(estate->es_top_eflags & + (EXEC_FLAG_SKIP_TRIGGERS | EXEC_FLAG_EXPLAIN_ONLY))) + AfterTriggerAbortQuery(); + + /* * Must switch out of context before destroying it */ MemoryContextSwitchTo(oldcontext); @@ -603,6 +681,21 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos, (rte->rtekind == RTE_SUBQUERY && rte->relkind == RELKIND_VIEW)); + /* + * Ensure that we have at least an AccessShareLock on relations + * whose permissions need to be checked. + * + * Skip this check in a parallel worker because locks won't be + * taken until ExecInitNode() performs plan initialization. + * + * XXX: ExecCheckPermissions() in a parallel worker may be + * redundant with the checks done in the leader process, so this + * should be reviewed to ensure it’s necessary. + */ + Assert(IsParallelWorker() || + CheckRelationOidLockedByMe(rte->relid, AccessShareLock, + true)); + (void) getRTEPermissionInfo(rteperminfos, rte); /* Many-to-one mapping not allowed */ Assert(!bms_is_member(rte->perminfoindex, indexset)); @@ -828,6 +921,12 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) * * Initializes the query plan: open files, allocate storage * and start up the rule manager + * + * If the plan originates from a CachedPlan (given in queryDesc->cplan), + * it can become invalid during runtime "initial" pruning when the + * remaining set of locks is taken. The function returns early in that + * case without initializing the plan, and the caller is expected to + * retry with a new valid plan. * ---------------------------------------------------------------- */ static void @@ -835,6 +934,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) { CmdType operation = queryDesc->operation; PlannedStmt *plannedstmt = queryDesc->plannedstmt; + CachedPlan *cachedplan = queryDesc->cplan; Plan *plan = plannedstmt->planTree; List *rangeTable = plannedstmt->rtable; EState *estate = queryDesc->estate; @@ -855,6 +955,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) bms_copy(plannedstmt->unprunableRelids)); estate->es_plannedstmt = plannedstmt; + estate->es_cachedplan = cachedplan; estate->es_part_prune_infos = plannedstmt->partPruneInfos; /* @@ -868,6 +969,9 @@ InitPlan(QueryDesc *queryDesc, int eflags) */ ExecDoInitialPruning(estate); + if (!ExecPlanStillValid(estate)) + return; + /* * Next, build the ExecRowMark array from the PlanRowMark(s), if any. */ @@ -2873,6 +2977,9 @@ EvalPlanQualStart(EPQState *epqstate, Plan *planTree) * the snapshot, rangetable, and external Param info. They need their own * copies of local state, including a tuple table, es_param_exec_vals, * result-rel info, etc. + * + * es_cachedplan is not copied because EPQ plan execution does not acquire + * any new locks that could invalidate the CachedPlan. */ rcestate->es_direction = ForwardScanDirection; rcestate->es_snapshot = parentestate->es_snapshot; |