From 1b27fcbdfed5426ff19721d3e19664adb84f9c7d Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Thu, 3 Aug 2023 12:34:31 +0900
Subject: [PATCH v45 2/6] Refactoring to move ExecutorStart() calls to be near
 GetCachedPlan()

An upcoming patch will make ExecutorStart() detect the invalidation
of a CachedPlan when initializing the plan tree contained in it.  A
caller must retry with a new CachedPlan when ExecutorStart() detects
an invalidation.  Having the ExecutorStart() in the same or nearby
as GetCachedPlan() makes it more convenient to implement the replan
loop.

The following sites have thus been modified:

* The ExecutorStart() call in ExplainOnePlan() is moved, along with
  CreateQueryDesc(), into a new function ExplainQueryDesc(), which its
  callers now call before calling it.

* 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 the associated
  memory context field.  One unintended consequence is that the
  CommandCounterIncrement() between queries in PORTAL_MULTI_QUERY
  cases is now done in the loop in PortalStart() and not in
  PortalRunMulti().  That still seems to work because the Snapshot
  registered in QueryDesc/EState is updated to account for the
  CCI().
---
 src/backend/commands/explain.c     | 121 ++++++-----
 src/backend/commands/prepare.c     |  12 +-
 src/backend/executor/spi.c         |  27 +--
 src/backend/tcop/pquery.c          | 311 +++++++++++++----------------
 src/backend/utils/mmgr/portalmem.c |   9 +
 src/include/commands/explain.h     |   6 +-
 src/include/utils/portal.h         |   2 +
 7 files changed, 250 insertions(+), 238 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8570b14f62..59d57f9c10 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,77 @@ 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. */
+	ExecutorStart(queryDesc, eflags);
+
+	return queryDesc;
+}
+
 /*
  * ExplainOneUtility -
  *	  print out the execution plan for one utility statement
@@ -524,29 +590,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 +608,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)
 	{
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 18f70319fc..1e9a98ad6e 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -639,8 +639,16 @@ 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);
+			Assert(queryDesc != NULL);
+			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/executor/spi.c b/src/backend/executor/spi.c
index 33975687b3..d36ca35d3a 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);
 
@@ -2661,6 +2661,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 			{
 				QueryDesc  *qdesc;
 				Snapshot	snap;
+				int			eflags;
 
 				if (ActiveSnapshotSet())
 					snap = GetActiveSnapshot();
@@ -2674,8 +2675,17 @@ _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;
+
+				ExecutorStart(qdesc, eflags);
+
+				res = _SPI_pquery(qdesc, canSetTag ? options->tcount : 0);
+
 				FreeQueryDesc(qdesc);
 			}
 			else
@@ -2850,10 +2860,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 +2906,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/pquery.c b/src/backend/tcop/pquery.c
index 5565f200c3..701808f303 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.
@@ -435,10 +350,9 @@ PortalStart(Portal portal, ParamListInfo params,
 {
 	Portal		saveActivePortal;
 	ResourceOwner saveResourceOwner;
-	MemoryContext savePortalContext;
 	MemoryContext oldContext;
 	QueryDesc  *queryDesc;
-	int			myeflags;
+	int			myeflags = 0;
 
 	Assert(PortalIsValid(portal));
 	Assert(portal->status == PORTAL_DEFINED);
@@ -448,15 +362,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 +384,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 +403,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 +415,41 @@ 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. */
 				ExecutorStart(queryDesc, myeflags);
 
 				/*
-				 * 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 +461,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 +483,69 @@ 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;
+
+						ExecutorStart(queryDesc, myeflags);
+						PopActiveSnapshot();
+					}
+				}
+
 				portal->tupDesc = NULL;
 				break;
 		}
@@ -594,7 +558,6 @@ PortalStart(Portal portal, ParamListInfo params,
 		/* Restore global vars and propagate error */
 		ActivePortal = saveActivePortal;
 		CurrentResourceOwner = saveResourceOwner;
-		PortalContext = savePortalContext;
 
 		PG_RE_THROW();
 	}
@@ -604,7 +567,6 @@ PortalStart(Portal portal, ParamListInfo params,
 
 	ActivePortal = saveActivePortal;
 	CurrentResourceOwner = saveResourceOwner;
-	PortalContext = savePortalContext;
 
 	portal->status = PORTAL_READY;
 }
@@ -1193,7 +1155,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 +1176,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 +1196,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 +1224,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 +1311,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..08ea852b65 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,
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

