From 36eb6e04907d4ab44ad424e11d54f9589309d0d2 Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Wed, 6 Sep 2023 17:53:46 +0900
Subject: [PATCH v48 3/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                 |   5 +-
 src/backend/commands/createas.c               |   9 +-
 src/backend/commands/explain.c                | 145 +++++---
 src/backend/commands/extension.c              |   6 +-
 src/backend/commands/matview.c                |   9 +-
 src/backend/commands/portalcmds.c             |   6 +-
 src/backend/commands/prepare.c                |  31 +-
 src/backend/commands/trigger.c                |  13 +
 src/backend/executor/execMain.c               |  44 ++-
 src/backend/executor/execParallel.c           |   6 +-
 src/backend/executor/execUtils.c              |   1 +
 src/backend/executor/functions.c              |   7 +-
 src/backend/executor/spi.c                    |  48 ++-
 src/backend/tcop/postgres.c                   |  18 +-
 src/backend/tcop/pquery.c                     | 346 +++++++++---------
 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, 466 insertions(+), 282 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 a46f2db352..58cb62e872 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -330,7 +330,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);
@@ -967,13 +967,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
@@ -996,6 +998,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 0e3547c35b..f7730c8702 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -568,8 +568,11 @@ 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,
+		 * because there's no CachedPlan.
 		 */
-		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 18b07c0200..4a950c03ff 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -329,8 +329,13 @@ 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,
+		 * because there's no CachedPlan.
+		 */
+		(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 281c47b2ee..8d1fe5738b 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,90 @@ ExplainOneQuery(Query *query, int cursorOptions,
 			BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);
 		}
 
+		queryDesc = ExplainQueryDesc(plan, NULL, 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
+ *
+ * This returns NULL if cplan is found to have been invalidated after
+ * calling ExecutorStart().
+ */
+QueryDesc *
+ExplainQueryDesc(PlannedStmt *stmt, CachedPlan *cplan,
+				 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, cplan, 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 +603,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 +621,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, NULL, 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)
 	{
@@ -4873,6 +4905,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 b287a2e84c..127d2a3b0a 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -802,7 +802,11 @@ 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,
+				 * because there's no CachedPlan.
+				 */
+				(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 22b8b820c3..7083fb2350 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -412,8 +412,13 @@ 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,
+	 * because there's no CachedPlan.
+	 */
+	(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..a1ee5c0acd 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -142,9 +142,11 @@ 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,
+	 * because there's no CachedPlan.
 	 */
-	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..f8d0b0ee25 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, cplan, 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 de7bf7ca67..5755336abd 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -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
 	 */
-	(void) 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
 	 */
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 457ee46faf..13d2820a41 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -1437,7 +1437,11 @@ 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,
+	 * because there's no CachedPlan.
+	 */
+	(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 16704c0c2f..f0f5740c26 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 7e452ed743..606da72535 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -863,7 +863,12 @@ 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,
+		 * because there's no CachedPlan.
+		 */
+		(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 f2cca807ef..814ff1390f 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();
@@ -2675,8 +2687,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
@@ -2851,10 +2878,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)
@@ -2898,14 +2924,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 21b9763183..4f923bbcae 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1230,7 +1230,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
@@ -1735,6 +1740,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.
@@ -2026,9 +2032,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 4ef349df8b..fcf9925ed4 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);
@@ -118,86 +113,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, NULL, 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.
@@ -428,19 +343,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);
@@ -450,15 +367,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;
@@ -474,6 +389,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)
@@ -491,8 +408,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),
 											NULL,
@@ -504,30 +421,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"
@@ -539,29 +477,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:
 
 				/*
@@ -584,7 +499,82 @@ 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,
+													NULL,
+													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;
 		}
@@ -597,19 +587,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;
 }
 
 /*
@@ -1196,7 +1187,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
@@ -1217,9 +1208,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
@@ -1236,33 +1228,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
@@ -1271,26 +1256,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)
@@ -1345,12 +1343,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 : "<unnamed>");
+	MemoryContextSetIdentifier(portal->queryContext, portal->name[0] ? portal->name : "<unnamed>");
 
 	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..392abb5150 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, struct CachedPlan *cplan,
+				 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 846eb32a1d..bb5734edb5 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

