From e9eb720d23c56f8b950b96128bc52b690711dd65 Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Mon, 9 Dec 2024 12:34:04 +0900
Subject: [PATCH v59 5/5] Remove the need to check if plan is valid from
 ExecutorStart hooks

For testing using delay_execution, a new hook
ExecutotStartCachedPlan_hook is added.  This hook allows the
delay_execution module to block the execution of the cached plan to
allow a concurrent session to modify the objects referenced in the
plan, which is then detected when the locks are taken on prunable
relations in ExecutorStart().
---
 contrib/auto_explain/auto_explain.c           |   4 -
 .../pg_stat_statements/pg_stat_statements.c   |   4 -
 src/backend/executor/execMain.c               | 257 ++++++++++--------
 src/backend/executor/execPartition.c          |   7 +-
 src/include/executor/execPartition.h          |   3 +-
 src/include/executor/executor.h               |  34 +--
 src/include/nodes/execnodes.h                 |   1 -
 .../modules/delay_execution/delay_execution.c |  20 +-
 .../expected/cached-plan-inval.out            |  26 +-
 9 files changed, 179 insertions(+), 177 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 8b5eaf3ef3..623a674f99 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -298,10 +298,6 @@ explain_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	else
 		standard_ExecutorStart(queryDesc, eflags);
 
-	/* The plan may have become invalid during standard_ExecutorStart() */
-	if (!ExecPlanStillValid(queryDesc->estate))
-		return;
-
 	if (auto_explain_enabled())
 	{
 		/*
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index b11691ae26..49c657b3e0 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -994,10 +994,6 @@ pgss_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	else
 		standard_ExecutorStart(queryDesc, eflags);
 
-	/* The plan may have become invalid during standard_ExecutorStart() */
-	if (!ExecPlanStillValid(queryDesc->estate))
-		return;
-
 	/*
 	 * If query has queryId zero, don't track it.  This prevents double
 	 * counting of optimizable statements that are directly contained in
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2dfa7eff54..b617a9ca8d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -68,6 +68,7 @@
 
 /* Hooks for plugins to get control in ExecutorStart/Run/Finish/End */
 ExecutorStart_hook_type ExecutorStart_hook = NULL;
+ExecutorStartCachedPlan_hook_type ExecutorStartCachedPlan_hook = NULL;
 ExecutorRun_hook_type ExecutorRun_hook = NULL;
 ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
 ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
@@ -123,6 +124,16 @@ static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
 void
 ExecutorStart(QueryDesc *queryDesc, int eflags)
 {
+	EState	   *estate;
+	MemoryContext oldcontext;
+	PlannedStmt *plannedstmt = queryDesc->plannedstmt;
+	List	   *rangeTable = plannedstmt->rtable;
+	CachedPlan *cachedplan = queryDesc->cplan;
+
+	/* sanity checks: queryDesc must not be started already */
+	Assert(queryDesc != NULL);
+	Assert(queryDesc->estate == NULL);
+
 	/*
 	 * 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.
@@ -133,6 +144,117 @@ ExecutorStart(QueryDesc *queryDesc, int eflags)
 	 */
 	pgstat_report_query_id(queryDesc->plannedstmt->queryId, false);
 
+	/*
+	 * Build EState, switch into per-query memory context for startup.
+	 */
+	estate = CreateExecutorState();
+	queryDesc->estate = estate;
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	/*
+	 * Fill in external parameters, if any, from queryDesc; and allocate
+	 * workspace for internal parameters
+	 */
+	estate->es_param_list_info = queryDesc->params;
+
+	if (queryDesc->plannedstmt->paramExecTypes != NIL)
+	{
+		int			nParamExec;
+
+		nParamExec = list_length(queryDesc->plannedstmt->paramExecTypes);
+		estate->es_param_exec_vals = (ParamExecData *)
+			palloc0(nParamExec * sizeof(ParamExecData));
+	}
+
+	/* We now require all callers to provide sourceText */
+	Assert(queryDesc->sourceText != NULL);
+	estate->es_sourceText = queryDesc->sourceText;
+
+	/*
+	 * Fill in the query environment, if any, from queryDesc.
+	 */
+	estate->es_queryEnv = queryDesc->queryEnv;
+
+	/*
+	 * If non-read-only query, set the command ID to mark output tuples with
+	 */
+	switch (queryDesc->operation)
+	{
+		case CMD_SELECT:
+
+			/*
+			 * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark
+			 * tuples
+			 */
+			if (queryDesc->plannedstmt->rowMarks != NIL ||
+				queryDesc->plannedstmt->hasModifyingCTE)
+				estate->es_output_cid = GetCurrentCommandId(true);
+
+			/*
+			 * A SELECT without modifying CTEs can't possibly queue triggers,
+			 * so force skip-triggers mode. This is just a marginal efficiency
+			 * hack, since AfterTriggerBeginQuery/AfterTriggerEndQuery aren't
+			 * all that expensive, but we might as well do it.
+			 */
+			if (!queryDesc->plannedstmt->hasModifyingCTE)
+				eflags |= EXEC_FLAG_SKIP_TRIGGERS;
+			break;
+
+		case CMD_INSERT:
+		case CMD_DELETE:
+		case CMD_UPDATE:
+		case CMD_MERGE:
+			estate->es_output_cid = GetCurrentCommandId(true);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized operation code: %d",
+				 (int) queryDesc->operation);
+			break;
+	}
+
+	/*
+	 * Copy other important information into the EState
+	 */
+	estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot);
+	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
+	estate->es_top_eflags = eflags;
+	estate->es_instrument = queryDesc->instrument_options;
+	estate->es_jit_flags = queryDesc->plannedstmt->jitFlags;
+
+	estate->es_part_prune_infos = plannedstmt->partPruneInfos;
+
+	/*
+	 * Do permissions checks
+	 */
+	ExecCheckPermissions(rangeTable, plannedstmt->permInfos, true);
+
+	/*
+	 * initialize the node's execution state
+	 */
+	ExecInitRangeTable(estate, rangeTable, plannedstmt->permInfos,
+					   bms_copy(plannedstmt->unprunableRelids));
+
+	/*
+	 * 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.
+	 *
+	 * This will also add the RT indexes of surviving leaf partitions to
+	 * es_unpruned_relids.
+	 */
+	ExecDoInitialPruning(estate, cachedplan);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (cachedplan && !CachedPlanValid(cachedplan))
+		return;
+
 	if (ExecutorStart_hook)
 		(*ExecutorStart_hook) (queryDesc, eflags);
 	else
@@ -159,6 +281,20 @@ void
 ExecutorStartCachedPlan(QueryDesc *queryDesc, int eflags,
 						CachedPlanSource *plansource,
 						int query_index)
+{
+	if (ExecutorStartCachedPlan_hook)
+		(*ExecutorStartCachedPlan_hook) (queryDesc, eflags, plansource,
+										 query_index);
+	else
+		standard_ExecutorStartCachedPlan(queryDesc, eflags, plansource,
+										 query_index);
+
+}
+
+void
+standard_ExecutorStartCachedPlan(QueryDesc *queryDesc, int eflags,
+								 CachedPlanSource *plansource,
+								 int query_index)
 {
 	if (unlikely(queryDesc->cplan == NULL))
 		elog(ERROR, "ExecutorStartCachedPlan(): missing CachedPlan");
@@ -198,12 +334,12 @@ ExecutorStartCachedPlan(QueryDesc *queryDesc, int eflags,
 void
 standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 {
-	EState	   *estate;
 	MemoryContext oldcontext;
+	EState	   *estate;
 
 	/* sanity checks: queryDesc must not be started already */
 	Assert(queryDesc != NULL);
-	Assert(queryDesc->estate == NULL);
+	Assert(queryDesc->estate != NULL);
 
 	/* caller must ensure the query's snapshot is active */
 	Assert(GetActiveSnapshot() == queryDesc->snapshot);
@@ -227,85 +363,6 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
 		ExecCheckXactReadOnly(queryDesc->plannedstmt);
 
-	/*
-	 * Build EState, switch into per-query memory context for startup.
-	 */
-	estate = CreateExecutorState();
-	queryDesc->estate = estate;
-
-	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
-
-	/*
-	 * Fill in external parameters, if any, from queryDesc; and allocate
-	 * workspace for internal parameters
-	 */
-	estate->es_param_list_info = queryDesc->params;
-
-	if (queryDesc->plannedstmt->paramExecTypes != NIL)
-	{
-		int			nParamExec;
-
-		nParamExec = list_length(queryDesc->plannedstmt->paramExecTypes);
-		estate->es_param_exec_vals = (ParamExecData *)
-			palloc0(nParamExec * sizeof(ParamExecData));
-	}
-
-	/* We now require all callers to provide sourceText */
-	Assert(queryDesc->sourceText != NULL);
-	estate->es_sourceText = queryDesc->sourceText;
-
-	/*
-	 * Fill in the query environment, if any, from queryDesc.
-	 */
-	estate->es_queryEnv = queryDesc->queryEnv;
-
-	/*
-	 * If non-read-only query, set the command ID to mark output tuples with
-	 */
-	switch (queryDesc->operation)
-	{
-		case CMD_SELECT:
-
-			/*
-			 * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark
-			 * tuples
-			 */
-			if (queryDesc->plannedstmt->rowMarks != NIL ||
-				queryDesc->plannedstmt->hasModifyingCTE)
-				estate->es_output_cid = GetCurrentCommandId(true);
-
-			/*
-			 * A SELECT without modifying CTEs can't possibly queue triggers,
-			 * so force skip-triggers mode. This is just a marginal efficiency
-			 * hack, since AfterTriggerBeginQuery/AfterTriggerEndQuery aren't
-			 * all that expensive, but we might as well do it.
-			 */
-			if (!queryDesc->plannedstmt->hasModifyingCTE)
-				eflags |= EXEC_FLAG_SKIP_TRIGGERS;
-			break;
-
-		case CMD_INSERT:
-		case CMD_DELETE:
-		case CMD_UPDATE:
-		case CMD_MERGE:
-			estate->es_output_cid = GetCurrentCommandId(true);
-			break;
-
-		default:
-			elog(ERROR, "unrecognized operation code: %d",
-				 (int) queryDesc->operation);
-			break;
-	}
-
-	/*
-	 * Copy other important information into the EState
-	 */
-	estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot);
-	estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot);
-	estate->es_top_eflags = eflags;
-	estate->es_instrument = queryDesc->instrument_options;
-	estate->es_jit_flags = queryDesc->plannedstmt->jitFlags;
-
 	/*
 	 * Set up an AFTER-trigger statement context, unless told not to, or
 	 * unless it's EXPLAIN-only mode (when ExecutorFinish won't be called).
@@ -313,6 +370,9 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	if (!(eflags & (EXEC_FLAG_SKIP_TRIGGERS | EXEC_FLAG_EXPLAIN_ONLY)))
 		AfterTriggerBeginQuery();
 
+	estate = queryDesc->estate;
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
 	/*
 	 * Initialize the plan state tree
 	 */
@@ -928,46 +988,14 @@ 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;
 	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_cachedplan = cachedplan;
-	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.
-	 *
-	 * This will also add the RT indexes of surviving leaf partitions to
-	 * es_unpruned_relids.
-	 */
-	ExecDoInitialPruning(estate);
-
-	if (!ExecPlanStillValid(estate))
-		return;
 
 	/*
 	 * Next, build the ExecRowMark array from the PlanRowMark(s), if any.
@@ -2961,9 +2989,6 @@ 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;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index ac6bc4326b..72f5973754 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -1813,7 +1813,7 @@ adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap)
  * use the same index to retrieve the pruning results.
  */
 void
-ExecDoInitialPruning(EState *estate)
+ExecDoInitialPruning(EState *estate, CachedPlan *cplan)
 {
 	ListCell   *lc;
 	List	   *locked_relids = NIL;
@@ -1842,7 +1842,7 @@ ExecDoInitialPruning(EState *estate)
 		else
 			validsubplan_rtis = all_leafpart_rtis;
 
-		if (ExecShouldLockRelations(estate))
+		if (cplan && CachedPlanRequiresLocking(cplan))
 		{
 			int		rtindex = -1;
 
@@ -1857,6 +1857,7 @@ ExecDoInitialPruning(EState *estate)
 				locked_relids = lappend_int(locked_relids, rtindex);
 			}
 		}
+
 		estate->es_unpruned_relids = bms_add_members(estate->es_unpruned_relids,
 													 validsubplan_rtis);
 		estate->es_part_prune_results = lappend(estate->es_part_prune_results,
@@ -1867,7 +1868,7 @@ ExecDoInitialPruning(EState *estate)
 	 * Release the useless locks if the plan won't be executed.  This is the
 	 * same as what CheckCachedPlan() in plancache.c does.
 	 */
-	if (!ExecPlanStillValid(estate))
+	if (cplan && !CachedPlanValid(cplan))
 	{
 		foreach(lc, locked_relids)
 		{
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index a0843481f7..95d886884c 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -17,6 +17,7 @@
 #include "nodes/parsenodes.h"
 #include "nodes/plannodes.h"
 #include "partitioning/partprune.h"
+#include "utils/plancache.h"
 
 /* See execPartition.c for the definitions. */
 typedef struct PartitionDispatchData *PartitionDispatch;
@@ -136,7 +137,7 @@ typedef struct PartitionPruneState
 	PartitionPruningData *partprunedata[FLEXIBLE_ARRAY_MEMBER];
 } PartitionPruneState;
 
-void ExecDoInitialPruning(EState *estate);
+void ExecDoInitialPruning(EState *estate, CachedPlan *cplan);
 extern PartitionPruneState *ExecInitPartitionExecPruning(PlanState *planstate,
 														 int n_total_subplans,
 														 int part_prune_index,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 02584dd154..b07942a457 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -76,6 +76,12 @@
 typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags);
 extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook;
 
+/* Hook for plugins to get control in ExecutorStartCachedPlan() */
+typedef void (*ExecutorStartCachedPlan_hook_type) (QueryDesc *queryDesc, int eflags,
+												   CachedPlanSource *plansource,
+												   int query_index);
+extern PGDLLIMPORT ExecutorStartCachedPlan_hook_type ExecutorStartCachedPlan_hook;
+
 /* Hook for plugins to get control in ExecutorRun() */
 typedef void (*ExecutorRun_hook_type) (QueryDesc *queryDesc,
 									   ScanDirection direction,
@@ -203,6 +209,9 @@ extern void ExecutorStartCachedPlan(QueryDesc *queryDesc, int eflags,
 									CachedPlanSource *plansource,
 									int query_index);
 extern void standard_ExecutorStart(QueryDesc *queryDesc, int eflags);
+extern void standard_ExecutorStartCachedPlan(QueryDesc *queryDesc, int eflags,
+											 CachedPlanSource *plansource,
+											 int query_index);
 extern void ExecutorRun(QueryDesc *queryDesc,
 						ScanDirection direction, uint64 count, bool execute_once);
 extern void standard_ExecutorRun(QueryDesc *queryDesc,
@@ -265,31 +274,6 @@ extern void ExecEndNode(PlanState *node);
 extern void ExecShutdownNode(PlanState *node);
 extern void ExecSetTupleBound(int64 tuples_needed, PlanState *child_node);
 
-/*
- * Is the CachedPlan in es_cachedplan still valid?
- *
- * Called from InitPlan() because invalidation messages that affect the plan
- * might be received after locks have been taken on runtime-prunable relations.
- * The caller should take appropriate action if the plan has become invalid.
- */
-static inline bool
-ExecPlanStillValid(EState *estate)
-{
-	return estate->es_cachedplan == NULL ? true :
-		CachedPlanValid(estate->es_cachedplan);
-}
-
-/*
- * Locks are needed only if running a cached plan that might contain unlocked
- * relations, such as a reused generic plan.
- */
-static inline bool
-ExecShouldLockRelations(EState *estate)
-{
-	return estate->es_cachedplan == NULL ? false :
-		CachedPlanRequiresLocking(estate->es_cachedplan);
-}
-
 /* ----------------------------------------------------------------
  *		ExecProcNode
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9643a9d626..45a9e3e5a5 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -640,7 +640,6 @@ typedef struct EState
 										 * ExecRowMarks, or NULL if none */
 	List	   *es_rteperminfos;	/* List of RTEPermissionInfo */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
-	CachedPlan *es_cachedplan;		/* CachedPlan providing the plan tree */
 	List	   *es_part_prune_infos;	/* List of PartitionPruneInfo */
 	List	   *es_part_prune_states;	/* List of PartitionPruneState */
 	List	   *es_part_prune_results;	/* List of Bitmapset */
diff --git a/src/test/modules/delay_execution/delay_execution.c b/src/test/modules/delay_execution/delay_execution.c
index 44aa828fdf..a3bfaed372 100644
--- a/src/test/modules/delay_execution/delay_execution.c
+++ b/src/test/modules/delay_execution/delay_execution.c
@@ -41,7 +41,7 @@ static int	executor_start_lock_id = 0;
 
 /* Save previous hook users to be a good citizen */
 static planner_hook_type prev_planner_hook = NULL;
-static ExecutorStart_hook_type prev_ExecutorStart_hook = NULL;
+static ExecutorStartCachedPlan_hook_type prev_ExecutorStartCachedPlan_hook = NULL;
 
 
 /* planner_hook function to provide the desired delay */
@@ -79,7 +79,9 @@ delay_execution_planner(Query *parse, const char *query_string,
 
 /* ExecutorStart_hook function to provide the desired delay */
 static void
-delay_execution_ExecutorStart(QueryDesc *queryDesc, int eflags)
+delay_execution_ExecutorStartCachedPlan(QueryDesc *queryDesc, int eflags,
+										CachedPlanSource *plansource,
+										int query_index)
 {
 	/* If enabled, delay by taking and releasing the specified lock */
 	if (executor_start_lock_id != 0)
@@ -97,13 +99,15 @@ delay_execution_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	}
 
 	/* Now start the executor, possibly via a previous hook user */
-	if (prev_ExecutorStart_hook)
-		prev_ExecutorStart_hook(queryDesc, eflags);
+	if (prev_ExecutorStartCachedPlan_hook)
+		prev_ExecutorStartCachedPlan_hook(queryDesc, eflags, plansource,
+										  query_index);
 	else
-		standard_ExecutorStart(queryDesc, eflags);
+		standard_ExecutorStartCachedPlan(queryDesc, eflags, plansource,
+										 query_index);
 
 	if (executor_start_lock_id != 0)
-		elog(NOTICE, "Finished ExecutorStart(): CachedPlan is %s",
+		elog(NOTICE, "Finished ExecutorStartCachedPlan(): CachedPlan is %s",
 			 CachedPlanValid(queryDesc->cplan) ? "valid" : "not valid");
 }
 
@@ -139,6 +143,6 @@ _PG_init(void)
 	/* Install our hooks. */
 	prev_planner_hook = planner_hook;
 	planner_hook = delay_execution_planner;
-	prev_ExecutorStart_hook = ExecutorStart_hook;
-	ExecutorStart_hook = delay_execution_ExecutorStart;
+	prev_ExecutorStartCachedPlan_hook = ExecutorStartCachedPlan_hook;
+	ExecutorStartCachedPlan_hook = delay_execution_ExecutorStartCachedPlan;
 }
diff --git a/src/test/modules/delay_execution/expected/cached-plan-inval.out b/src/test/modules/delay_execution/expected/cached-plan-inval.out
index 5bfb2b33b3..165f865b7a 100644
--- a/src/test/modules/delay_execution/expected/cached-plan-inval.out
+++ b/src/test/modules/delay_execution/expected/cached-plan-inval.out
@@ -32,8 +32,7 @@ t
 (1 row)
 
 step s1exec: <... completed>
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is not valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
 QUERY PLAN                           
 -------------------------------------
 LockRows                             
@@ -48,7 +47,7 @@ starting permutation: s1prep2 s2lock s1exec2 s2dropi s2unlock
 step s1prep2: SET plan_cache_mode = force_generic_plan;
 		  PREPARE q2 AS SELECT * FROM foov WHERE a = one() or a = two();
 		  EXPLAIN (COSTS OFF) EXECUTE q2;
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
 QUERY PLAN                                        
 --------------------------------------------------
 Append                                            
@@ -81,8 +80,7 @@ t
 (1 row)
 
 step s1exec2: <... completed>
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is not valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
 QUERY PLAN                                  
 --------------------------------------------
 Append                                      
@@ -98,9 +96,9 @@ starting permutation: s1prep3 s2lock s1exec3 s2dropi s2unlock
 step s1prep3: SET plan_cache_mode = force_generic_plan;
 		  PREPARE q3 AS UPDATE foov SET a = a WHERE a = one() or a = two();
 		  EXPLAIN (COSTS OFF) EXECUTE q3;
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
 QUERY PLAN                                                    
 --------------------------------------------------------------
 Nested Loop                                                   
@@ -178,10 +176,9 @@ t
 (1 row)
 
 step s1exec3: <... completed>
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is not valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
 QUERY PLAN                                                   
 -------------------------------------------------------------
 Nested Loop                                                  
@@ -233,7 +230,7 @@ step s1prep4: SET plan_cache_mode = force_generic_plan;
 		  SET enable_seqscan TO off;
 		  PREPARE q4 AS SELECT * FROM generate_series(1, 1) WHERE EXISTS (SELECT * FROM foov WHERE a = $1 FOR UPDATE);
 		  EXPLAIN (COSTS OFF) EXECUTE q4 (1);
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
 QUERY PLAN                                                     
 ---------------------------------------------------------------
 Result                                                         
@@ -264,8 +261,7 @@ t
 (1 row)
 
 step s1exec4: <... completed>
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is not valid
-s1: NOTICE:  Finished ExecutorStart(): CachedPlan is valid
+s1: NOTICE:  Finished ExecutorStartCachedPlan(): CachedPlan is valid
 QUERY PLAN                                   
 ---------------------------------------------
 Result                                       
-- 
2.43.0

