From 9e0ae8887a9f3d75feb4df969dde504a21d3700d Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Wed, 22 Dec 2021 16:55:17 +0900
Subject: [PATCH v8 4/4] Optimize AcquireExecutorLocks() to skip pruned
 partitions

Instead of locking all relations listed in the range table in the
cases where the PlannedStmt indicates that some nodes in the plan
tree can do partition pruning without depending on execution having
started (so called "initial" pruning), AcquireExecutorLocks() now
calls the new executor function ExecutorGetLockRels() which returns
a set of relations (their RT indexes) to be locked not including
those scanned by the subplans that pruned.

The result of pruning done this way must be remembered and reused
during actual execution of the plan, which is done by creating a
PlanInitPruningOutput nodes for for each plan node that undergoes
pruning and a set of those for the whole plan tree are added to
ExecLockRelsInfo which also stores the bitmapset of RT indexes of
relations that are actually locked by AcquireExecutorLocks().
ExecLockRelsInfos are passed down the executor alongside the
PlannedStmts.  This arrangement ensures that the executor doesn't
accidentally try to process a plan tree subnodes that has been
deemed pruned by AcquireExecutorLocks().
---
 src/backend/commands/copyto.c          |   2 +-
 src/backend/commands/createas.c        |   2 +-
 src/backend/commands/explain.c         |   7 +-
 src/backend/commands/extension.c       |  13 +-
 src/backend/commands/matview.c         |   2 +-
 src/backend/commands/portalcmds.c      |   1 +
 src/backend/commands/prepare.c         |  17 +-
 src/backend/executor/README            |  24 +++
 src/backend/executor/execMain.c        | 202 ++++++++++++++++++++
 src/backend/executor/execParallel.c    |  26 ++-
 src/backend/executor/execPartition.c   | 224 ++++++++++++++++++----
 src/backend/executor/execUtils.c       |   8 +
 src/backend/executor/functions.c       |   2 +-
 src/backend/executor/nodeAppend.c      |  52 ++++-
 src/backend/executor/nodeMergeAppend.c |  52 ++++-
 src/backend/executor/nodeModifyTable.c |  25 +++
 src/backend/executor/spi.c             |  14 +-
 src/backend/nodes/copyfuncs.c          |  49 ++++-
 src/backend/nodes/outfuncs.c           |  39 ++++
 src/backend/nodes/readfuncs.c          |  37 ++++
 src/backend/optimizer/plan/planner.c   |   2 +
 src/backend/optimizer/plan/setrefs.c   |   6 +
 src/backend/partitioning/partprune.c   |  37 +++-
 src/backend/tcop/postgres.c            |  15 +-
 src/backend/tcop/pquery.c              |  21 ++-
 src/backend/utils/cache/plancache.c    | 252 ++++++++++++++++++++++---
 src/backend/utils/mmgr/portalmem.c     |   2 +
 src/include/commands/explain.h         |   3 +-
 src/include/executor/execPartition.h   |   2 +
 src/include/executor/execdesc.h        |   2 +
 src/include/executor/executor.h        |   2 +
 src/include/executor/nodeAppend.h      |   1 +
 src/include/executor/nodeMergeAppend.h |   1 +
 src/include/executor/nodeModifyTable.h |   1 +
 src/include/nodes/execnodes.h          |  96 ++++++++++
 src/include/nodes/nodes.h              |   5 +
 src/include/nodes/pathnodes.h          |   4 +
 src/include/nodes/plannodes.h          |  15 ++
 src/include/tcop/tcopprot.h            |   2 +-
 src/include/utils/plancache.h          |   6 +
 src/include/utils/portal.h             |   5 +
 41 files changed, 1174 insertions(+), 104 deletions(-)

diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 55c38b04c4..d403eb2309 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -542,7 +542,7 @@ BeginCopyTo(ParseState *pstate,
 		((DR_copy *) dest)->cstate = cstate;
 
 		/* Create a QueryDesc requesting no output */
-		cstate->queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+		cstate->queryDesc = CreateQueryDesc(plan, NULL, pstate->p_sourcetext,
 											GetActiveSnapshot(),
 											InvalidSnapshot,
 											dest, NULL, NULL, 0);
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 9abbb6b555..f6607f2454 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -325,7 +325,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
 		UpdateActiveSnapshotCommandId();
 
 		/* Create a QueryDesc, redirecting output to our tuple receiver */
-		queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+		queryDesc = CreateQueryDesc(plan, NULL, pstate->p_sourcetext,
 									GetActiveSnapshot(), InvalidSnapshot,
 									dest, params, queryEnv, 0);
 
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index cb13227db1..e5dff2bc25 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -407,7 +407,7 @@ ExplainOneQuery(Query *query, int cursorOptions,
 		}
 
 		/* run it (if needed) and produce output */
-		ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
+		ExplainOnePlan(plan, NULL, into, es, queryString, params, queryEnv,
 					   &planduration, (es->buffers ? &bufusage : NULL));
 	}
 }
@@ -515,7 +515,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
  * to call it.
  */
 void
-ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
+ExplainOnePlan(PlannedStmt *plannedstmt, ExecLockRelsInfo *execlockrelsinfo,
+			   IntoClause *into, ExplainState *es,
 			   const char *queryString, ParamListInfo params,
 			   QueryEnvironment *queryEnv, const instr_time *planduration,
 			   const BufferUsage *bufusage)
@@ -563,7 +564,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 		dest = None_Receiver;
 
 	/* Create a QueryDesc for the query */
-	queryDesc = CreateQueryDesc(plannedstmt, queryString,
+	queryDesc = CreateQueryDesc(plannedstmt, execlockrelsinfo, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
 								dest, params, queryEnv, instrument_option);
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 1013790dbb..008b8ce0e9 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -741,8 +741,10 @@ execute_sql_string(const char *sql)
 		RawStmt    *parsetree = lfirst_node(RawStmt, lc1);
 		MemoryContext per_parsetree_context,
 					oldcontext;
-		List	   *stmt_list;
-		ListCell   *lc2;
+		List	   *stmt_list,
+				   *execlockrelsinfo_list;
+		ListCell   *lc2,
+				   *lc3;
 
 		/*
 		 * We do the work for each parsetree in a short-lived context, to
@@ -762,11 +764,13 @@ execute_sql_string(const char *sql)
 										   NULL,
 										   0,
 										   NULL);
-		stmt_list = pg_plan_queries(stmt_list, sql, CURSOR_OPT_PARALLEL_OK, NULL);
+		stmt_list = pg_plan_queries(stmt_list, sql, CURSOR_OPT_PARALLEL_OK, NULL,
+									&execlockrelsinfo_list);
 
-		foreach(lc2, stmt_list)
+		forboth(lc2, stmt_list, lc3, execlockrelsinfo_list)
 		{
 			PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2);
+			ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, lc3);
 
 			CommandCounterIncrement();
 
@@ -777,6 +781,7 @@ execute_sql_string(const char *sql)
 				QueryDesc  *qdesc;
 
 				qdesc = CreateQueryDesc(stmt,
+										execlockrelsinfo,
 										sql,
 										GetActiveSnapshot(), NULL,
 										dest, NULL, NULL, 0);
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 05e7b60059..4ef44aaf23 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -416,7 +416,7 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 	UpdateActiveSnapshotCommandId();
 
 	/* Create a QueryDesc, redirecting output to our tuple receiver */
-	queryDesc = CreateQueryDesc(plan, queryString,
+	queryDesc = CreateQueryDesc(plan, NULL, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
 								dest, NULL, NULL, 0);
 
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 9902c5c566..85e73ddded 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -107,6 +107,7 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa
 					  queryString,
 					  CMDTAG_SELECT,	/* cursor's query is always a SELECT */
 					  list_make1(plan),
+					  list_make1(NULL),	/* no ExecLockRelsInfo to pass */
 					  NULL);
 
 	/*----------
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 80738547ed..bbbf8bbcbd 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -155,6 +155,7 @@ ExecuteQuery(ParseState *pstate,
 	PreparedStatement *entry;
 	CachedPlan *cplan;
 	List	   *plan_list;
+	List	   *plan_execlockrelsinfo_list;
 	ParamListInfo paramLI = NULL;
 	EState	   *estate = NULL;
 	Portal		portal;
@@ -195,6 +196,7 @@ ExecuteQuery(ParseState *pstate,
 	/* Replan if needed, and increment plan refcount for portal */
 	cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL);
 	plan_list = cplan->stmt_list;
+	plan_execlockrelsinfo_list = cplan->execlockrelsinfo_list;
 
 	/*
 	 * DO NOT add any logic that could possibly throw an error between
@@ -204,7 +206,7 @@ ExecuteQuery(ParseState *pstate,
 					  NULL,
 					  query_string,
 					  entry->plansource->commandTag,
-					  plan_list,
+					  plan_list, plan_execlockrelsinfo_list,
 					  cplan);
 
 	/*
@@ -576,7 +578,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	const char *query_string;
 	CachedPlan *cplan;
 	List	   *plan_list;
-	ListCell   *p;
+	List	   *plan_execlockrelsinfo_list;
+	ListCell   *p,
+			   *pe;
 	ParamListInfo paramLI = NULL;
 	EState	   *estate = NULL;
 	instr_time	planstart;
@@ -632,15 +636,18 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	plan_list = cplan->stmt_list;
+	plan_execlockrelsinfo_list = cplan->execlockrelsinfo_list;
 
 	/* Explain each query */
-	foreach(p, plan_list)
+	forboth(p, plan_list, pe, plan_execlockrelsinfo_list)
 	{
 		PlannedStmt *pstmt = lfirst_node(PlannedStmt, p);
+		ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, pe);
 
 		if (pstmt->commandType != CMD_UTILITY)
-			ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv,
-						   &planduration, (es->buffers ? &bufusage : NULL));
+			ExplainOnePlan(pstmt, execlockrelsinfo, 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/README b/src/backend/executor/README
index 0b5183fc4a..b45ca508a8 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -65,6 +65,27 @@ found there.  This currently only occurs for Append and MergeAppend nodes.  In
 this case the non-required subplans are ignored and the executor state's
 subnode array will become out of sequence to the plan's subplan list.
 
+Actually, the so-called execution time pruning may also occur even before the
+execution has started.  One case where that occurs is when a cached generic
+plan is being validated for execution by plancache.c: GetCachedPlan(), which
+proceeds by locking all the relations that will be scanned by that plan.  If
+the generic plan has nodes that contain so-called initial pruning steps (a
+subset of execution pruning steps that do not depend on full-fledged execution
+having started), they are performed at this point to figure out the minimal
+set of child subplans that satisfy those pruning instructions and the result
+of performing that pruning is saved in a data structure that gets passed to
+the executor alongside the plan tree.  Relations scanned by only those
+surviving subplans are then locked while those scanned by the pruned subplans
+are not, even though the pruned subplans themselves are not removed from the
+plan tree. So, it is imperative that the executor and any third party code
+invoked by it that gets passed the plan tree look at the initial pruning result
+made available via the aforementioned data structure to determine whether or
+not a particular subplan is valid.  (The data structure basically consists of
+an array of PlanInitPruningOutput nodes containing one element for each node
+of the plan tree indexable using plan_node_id of the individual plan nodes,
+where each node contains a bitmapset of indexes of unpruned child subplans of
+a given node.)
+
 Each Plan node may have expression trees associated with it, to represent
 its target list, qualification conditions, etc.  These trees are also
 read-only to the executor, but the executor state for expression evaluation
@@ -286,6 +307,9 @@ Query Processing Control Flow
 
 This is a sketch of control flow for full query processing:
 
+  [ ExecutorGetLockRels ] --- an optional step to walk over the plan tree
+		to produce an ExecLockRelsInfo to be passed to CreateQueryDesc
+
 	CreateQueryDesc
 
 	ExecutorStart
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ef2fd46092..56946c12dd 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -49,11 +49,15 @@
 #include "commands/matview.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
+#include "executor/nodeAppend.h"
+#include "executor/nodeMergeAppend.h"
+#include "executor/nodeModifyTable.h"
 #include "executor/nodeSubplan.h"
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -101,9 +105,205 @@ static char *ExecBuildSlotValueDescription(Oid reloid,
 										   Bitmapset *modifiedCols,
 										   int maxfieldlen);
 static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
+static bool ExecGetScanLockRels(Scan *scan, ExecGetLockRelsContext *context);
 
 /* end of local decls */
 
+/* ----------------------------------------------------------------
+ *		ExecutorGetLockRels
+ *
+ *		Figure out the minimal set of relations to lock to be able to safely
+ *		execute a given plan
+ *
+ * This ignores the relations scanned by child subplans that are pruned away
+ * after performing initial pruning steps present in the plan using the
+ * provided set of EXTERN parameters.
+ *
+ * Along with the set of RT indexes of relations that must be locked, the
+ * returned struct also contains an array of PlanInitPruningOutput nodes each
+ * of which contains the result of initial pruning for a given plan node, which
+ * is basically a bitmapset of the indexes of surviving child subplans.  Each
+ * plan node in the tree that undergoes pruning will have an element in the
+ * array.
+ *
+ * Note that while relations scanned by the subplans that are pruned will not
+ * be locked, the subplans themselves are left as-is in the plan tree, assuming
+ * anything that reads the plan tree during execution knows to ignore them by
+ * looking at the PlanInitPruningOutput's list of valid subplans.
+ *
+ * Partitioned tables mentioned in PartitionedRelPruneInfo nodes that drive
+ * the pruning will be locked before doing the pruning and also added to the
+ * the returned set.
+ */
+ExecLockRelsInfo *
+ExecutorGetLockRels(PlannedStmt *plannedstmt, ParamListInfo params)
+{
+	int		numPlanNodes = plannedstmt->numPlanNodes;
+	ExecGetLockRelsContext context;
+	ExecLockRelsInfo *result;
+	ListCell *lc;
+
+	/* Only get here if there is any pruning to do. */
+	Assert(plannedstmt->containsInitialPruning);
+
+	context.stmt = plannedstmt;
+	context.params = params;
+
+	/*
+	 * Go walk all the plan tree(s) present in the PlannedStmt, filling
+	 * context.lockrels with only the relations from plan nodes that
+	 * survive initial pruning and also the tables mentioned in
+	 * partitioned_rels sets found in the plan.
+	 */
+	context.lockrels = NULL;
+	context.initPruningOutputs = NIL;
+	context.ipoIndexes = palloc0(sizeof(int) * numPlanNodes);
+
+	/* All the subplans. */
+	foreach(lc, plannedstmt->subplans)
+	{
+		Plan *subplan = lfirst(lc);
+
+		(void) ExecGetLockRels(subplan, &context);
+	}
+
+	/* And the main tree. */
+	(void) ExecGetLockRels(plannedstmt->planTree, &context);
+
+	/*
+	 * Also be sure to lock partitioned relations from any [Merge]Append nodes
+	 * that were originally present but were ultimately left out from the plan
+	 * due to being deemed no-op nodes.
+	 */
+	context.lockrels = bms_add_members(context.lockrels,
+									   plannedstmt->elidedAppendPartedRels);
+
+	result = makeNode(ExecLockRelsInfo);
+	result->lockrels = context.lockrels;
+	result->numPlanNodes = numPlanNodes;
+	result->initPruningOutputs = context.initPruningOutputs;
+	result->ipoIndexes = context.ipoIndexes;
+
+	return result;
+}
+
+/* ------------------------------------------------------------------------
+ * ExecGetLockRels
+ *		Adds all the relations that will be scanned by 'node' and its child
+ *		plans to context->lockrels after taking into the account the effect
+ *		of performing initial pruning if any
+ *
+ * context->stmt gives the PlannedStmt being inspected to access the plan's
+ * range table if needed and context->params the set of EXTERN parameters
+ * available to evaluate pruning parameters.
+ *
+ * If initial pruning is done, a PlanInitPruningOutput node containing the
+ * result of pruning will be stored in context->initPruningOutputs that will
+ * be made available to the executor to reuse.
+ * ------------------------------------------------------------------------
+ */
+bool
+ExecGetLockRels(Plan *node, ExecGetLockRelsContext *context)
+{
+	/* Do nothing when we get to the end of a leaf on tree. */
+	if (node == NULL)
+		return true;
+
+	/* Make sure there's enough stack available. */
+	check_stack_depth();
+
+	switch (nodeTag(node))
+	{
+		/* Currently, only these two nodes have prunable child subplans. */
+		case T_Append:
+			if (ExecGetAppendLockRels((Append *) node, context))
+				return true;
+			break;
+		case T_MergeAppend:
+			if (ExecGetMergeAppendLockRels((MergeAppend *) node,
+												context))
+				return true;
+			break;
+
+		/*
+		 * And these manipulate relations that must be added context->lockrels.
+		 */
+		case T_SeqScan:
+		case T_SampleScan:
+		case T_IndexScan:
+		case T_IndexOnlyScan:
+		case T_BitmapIndexScan:
+		case T_BitmapHeapScan:
+		case T_TidScan:
+		case T_TidRangeScan:
+		case T_ForeignScan:
+		case T_SubqueryScan:
+		case T_CustomScan:
+			if (ExecGetScanLockRels((Scan *) node, context))
+				return true;
+			break;
+		case T_ModifyTable:
+			if (ExecGetModifyTableLockRels((ModifyTable *) node, context))
+				return true;
+			/* plan_tree_walker() will visit the subplan (outerNode) */
+			break;
+
+		default:
+			break;
+	}
+
+	/* Recurse to subnodes. */
+	return plan_tree_walker(node, ExecGetLockRels, (void *) context);
+}
+
+/*
+ * ExecGetScanLockRels
+ * 		Do ExecGetLockRels()'s work for a leaf Scan node
+ */
+static bool
+ExecGetScanLockRels(Scan *scan, ExecGetLockRelsContext *context)
+{
+	switch (nodeTag(scan))
+	{
+		case T_ForeignScan:
+			{
+				ForeignScan *fscan = (ForeignScan *) scan;
+
+				context->lockrels = bms_add_members(context->lockrels,
+													fscan->fs_relids);
+			}
+			break;
+
+		case T_SubqueryScan:
+			{
+				SubqueryScan *sscan = (SubqueryScan *) scan;
+
+				(void) ExecGetLockRels((Plan *) sscan->subplan, context);
+			}
+			break;
+
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) scan;
+				ListCell *lc;
+
+				context->lockrels = bms_add_members(context->lockrels,
+													cscan->custom_relids);
+				foreach(lc, cscan->custom_plans)
+				{
+					(void) ExecGetLockRels((Plan *) lfirst(lc), context);
+				}
+			}
+			break;
+
+		default:
+			context->lockrels = bms_add_member(context->lockrels,
+											   scan->scanrelid);
+			break;
+	}
+
+	return true;
+}
 
 /* ----------------------------------------------------------------
  *		ExecutorStart
@@ -806,6 +1006,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 {
 	CmdType		operation = queryDesc->operation;
 	PlannedStmt *plannedstmt = queryDesc->plannedstmt;
+	ExecLockRelsInfo *execlockrelsinfo = queryDesc->execlockrelsinfo;
 	Plan	   *plan = plannedstmt->planTree;
 	List	   *rangeTable = plannedstmt->rtable;
 	EState	   *estate = queryDesc->estate;
@@ -825,6 +1026,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	ExecInitRangeTable(estate, rangeTable);
 
 	estate->es_plannedstmt = plannedstmt;
+	estate->es_execlockrelsinfo = execlockrelsinfo;
 
 	/*
 	 * Next, build the ExecRowMark array from the PlanRowMark(s), if any.
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index 9a0d5d59ef..fb6dbd298a 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -66,6 +66,7 @@
 #define PARALLEL_KEY_QUERY_TEXT		UINT64CONST(0xE000000000000008)
 #define PARALLEL_KEY_JIT_INSTRUMENTATION UINT64CONST(0xE000000000000009)
 #define PARALLEL_KEY_WAL_USAGE			UINT64CONST(0xE00000000000000A)
+#define PARALLEL_KEY_EXECLOCKRELSINFO	UINT64CONST(0xE00000000000000B)
 
 #define PARALLEL_TUPLE_QUEUE_SIZE		65536
 
@@ -182,6 +183,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->transientPlan = false;
 	pstmt->dependsOnRole = false;
 	pstmt->parallelModeNeeded = false;
+	pstmt->containsInitialPruning = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
 	pstmt->resultRelations = NIL;
@@ -596,12 +598,15 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	FixedParallelExecutorState *fpes;
 	char	   *pstmt_data;
 	char	   *pstmt_space;
+	char	   *execlockrelsinfo_data;
+	char	   *execlockrelsinfo_space;
 	char	   *paramlistinfo_space;
 	BufferUsage *bufusage_space;
 	WalUsage   *walusage_space;
 	SharedExecutorInstrumentation *instrumentation = NULL;
 	SharedJitInstrumentation *jit_instrumentation = NULL;
 	int			pstmt_len;
+	int			execlockrelsinfo_len;
 	int			paramlistinfo_len;
 	int			instrumentation_len = 0;
 	int			jit_instrumentation_len = 0;
@@ -630,6 +635,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 
 	/* Fix up and serialize plan to be sent to workers. */
 	pstmt_data = ExecSerializePlan(planstate->plan, estate);
+	execlockrelsinfo_data = nodeToString(estate->es_execlockrelsinfo);
 
 	/* Create a parallel context. */
 	pcxt = CreateParallelContext("postgres", "ParallelQueryMain", nworkers);
@@ -656,6 +662,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	shm_toc_estimate_chunk(&pcxt->estimator, pstmt_len);
 	shm_toc_estimate_keys(&pcxt->estimator, 1);
 
+	/* Estimate space for serialized ExecLockRelsInfo. */
+	execlockrelsinfo_len = strlen(execlockrelsinfo_data) + 1;
+	shm_toc_estimate_chunk(&pcxt->estimator, execlockrelsinfo_len);
+	shm_toc_estimate_keys(&pcxt->estimator, 1);
+
 	/* Estimate space for serialized ParamListInfo. */
 	paramlistinfo_len = EstimateParamListSpace(estate->es_param_list_info);
 	shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len);
@@ -750,6 +761,12 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
 	memcpy(pstmt_space, pstmt_data, pstmt_len);
 	shm_toc_insert(pcxt->toc, PARALLEL_KEY_PLANNEDSTMT, pstmt_space);
 
+	/* Store serialized ExecLockRelsInfo */
+	execlockrelsinfo_space = shm_toc_allocate(pcxt->toc, execlockrelsinfo_len);
+	memcpy(execlockrelsinfo_space, execlockrelsinfo_data, execlockrelsinfo_len);
+	shm_toc_insert(pcxt->toc, PARALLEL_KEY_EXECLOCKRELSINFO,
+				   execlockrelsinfo_space);
+
 	/* Store serialized ParamListInfo. */
 	paramlistinfo_space = shm_toc_allocate(pcxt->toc, paramlistinfo_len);
 	shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space);
@@ -1231,8 +1248,10 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
 						 int instrument_options)
 {
 	char	   *pstmtspace;
+	char	   *execlockrelsinfospace;
 	char	   *paramspace;
 	PlannedStmt *pstmt;
+	ExecLockRelsInfo *execlockrelsinfo;
 	ParamListInfo paramLI;
 	char	   *queryString;
 
@@ -1243,12 +1262,17 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
 	pstmtspace = shm_toc_lookup(toc, PARALLEL_KEY_PLANNEDSTMT, false);
 	pstmt = (PlannedStmt *) stringToNode(pstmtspace);
 
+	/* Reconstruct leader-supplied ExecLockRelsInfo. */
+	execlockrelsinfospace = shm_toc_lookup(toc, PARALLEL_KEY_EXECLOCKRELSINFO,
+										  false);
+	execlockrelsinfo = (ExecLockRelsInfo *) stringToNode(execlockrelsinfospace);
+
 	/* Reconstruct ParamListInfo. */
 	paramspace = shm_toc_lookup(toc, PARALLEL_KEY_PARAMLISTINFO, false);
 	paramLI = RestoreParamList(&paramspace);
 
 	/* Create a QueryDesc for the query. */
-	return CreateQueryDesc(pstmt,
+	return CreateQueryDesc(pstmt, execlockrelsinfo,
 						   queryString,
 						   GetActiveSnapshot(), InvalidSnapshot,
 						   receiver, paramLI, NULL, instrument_options);
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 84b4e4b3d6..e79ada16f0 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -25,6 +25,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
@@ -185,8 +186,13 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
 static List *adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri);
 static List *adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap);
 static PartitionPruneState *ExecCreatePartitionPruneState(PlanState *planstate,
-							  PartitionPruneInfo *partitionpruneinfo);
-static Bitmapset *ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate);
+							  PartitionPruneInfo *partitionpruneinfo,
+							  bool consider_initial_steps,
+							  bool consider_exec_steps,
+							  List *rtable, ExprContext *econtext,
+							  PartitionDirectory partdir);
+static Bitmapset *ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate,
+								PartitionPruneInfo *pruneinfo);
 static void ExecInitPruningContext(PartitionPruneContext *context,
 								   List *pruning_steps,
 								   PartitionDesc partdesc,
@@ -1588,8 +1594,9 @@ adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap)
  * considered to be a stable expression, it can change value from one plan
  * node scan to the next during query execution.  Stable comparison
  * expressions that don't involve such Params allow partition pruning to be
- * done once during executor startup.  Expressions that do involve such Params
- * require us to prune separately for each scan of the parent plan node.
+ * done once during executor startup or even before during ExecutorGetLockRels().
+ * Expressions that do involve such Params require us to prune separately for
+ * each scan of the parent plan node.
  *
  * Note that pruning away unneeded subplans during executor startup has the
  * added benefit of not having to initialize the unneeded subplans at all.
@@ -1601,10 +1608,17 @@ adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap)
  *		Creates the PartitionPruneState required by each of the two pruning
  *		functions.  Details stored include how to map the partition index
  *		returned by the partition pruning code into subplan indexes.  Also
- *		determines the set of initially valid subplans by performing initial
- *		pruning steps, only which need be initialized by the caller such as
- *		ExecInitAppend.  Maps in PartitionPruneState are updated to account
- *		for initial pruning having eliminated some of the subplans, if any.
+ *		determines the set of initially valid subplans by either looking that
+ *		up in the plan node's PlanInitPruningOutput if one found in
+ *		EState.es_execlockrelinfo or by performing initial pruning steps.
+ *		Only the subplans included in that need be initialized by the caller
+ *		such as ExecInitAppend.  Maps in PartitionPruneState are updated to
+ *		account for initial pruning having eliminated some of the subplans,
+ *		if any.
+ *
+ * ExecGetLockRelsDoInitialPruning:
+ *		Do initial pruning as part of ExecGetLockRels() on the parent plan
+ *		node
  *
  * ExecFindMatchingSubPlans:
  *		Returns indexes of matching subplans after evaluating all available
@@ -1619,9 +1633,10 @@ adjust_partition_colnos_using_map(List *colnos, AttrMap *attrMap)
  * ExecInitPartitionPruning
  * 		Initialize data structure needed for run-time partition pruning
  *
- * Initial pruning can be done immediately, so it is done here if needed and
- * the set of surviving partition subplans' indexes are added to the output
- * parameter *initially_valid_subplans.
+ * Initial pruning can be done immediately, so it is done here unless it has
+ * already been done by ExecGetLockRelsDoInitialPruning(), and the set of
+ * surviving partition subplans' indexes are added to the output parameter
+ * *initially_valid_subplans.
  *
  * If subplans are indeed pruned, subplan_map arrays contained in the returned
  * PartitionPruneState are re-sequenced to not count those, though only if the
@@ -1635,22 +1650,57 @@ ExecInitPartitionPruning(PlanState *planstate,
 {
 	PartitionPruneState *prunestate;
 	EState *estate = planstate->state;
+	Plan   *plan = planstate->plan;
+	PlanInitPruningOutput *initPruningOutput = NULL;
+	bool	do_pruning = (pruneinfo->needs_init_pruning ||
+						  pruneinfo->needs_exec_pruning);
 
-	/* We may need an expression context to evaluate partition exprs */
-	ExecAssignExprContext(estate, planstate);
+	/* Retrieve the parent plan's PlanInitPruningOutput, if any. */
+	if (estate->es_execlockrelsinfo)
+	{
+		initPruningOutput = (PlanInitPruningOutput *)
+			ExecFetchPlanInitPruningOutput(estate->es_execlockrelsinfo, plan);
 
-	/*
-	 * Create the working data structure for pruning.
-	 */
-	prunestate = ExecCreatePartitionPruneState(planstate, pruneinfo);
+		Assert(initPruningOutput != NULL &&
+			   IsA(initPruningOutput, PlanInitPruningOutput));
+		/* No need to do initial pruning again, only exec pruning. */
+		do_pruning = pruneinfo->needs_exec_pruning;
+	}
+
+	prunestate = NULL;
+	if (do_pruning)
+	{
+		/* We may need an expression context to evaluate partition exprs */
+		ExecAssignExprContext(estate, planstate);
+
+		/* For data reading, executor always omits detached partitions */
+		if (estate->es_partition_directory == NULL)
+			estate->es_partition_directory =
+				CreatePartitionDirectory(estate->es_query_cxt, false);
+
+		/*
+		 * Create the working data structure for pruning.  No need to consider
+		 * initial pruning steps if we have a PlanInitPruningOutput.
+		 */
+		prunestate = ExecCreatePartitionPruneState(planstate, pruneinfo,
+												   initPruningOutput == NULL, true,
+												   NIL, planstate->ps_ExprContext,
+												   estate->es_partition_directory);
+	}
 
 	/*
 	 * Perform an initial partition prune, if required.
 	 */
-	if (prunestate->do_initial_prune)
+	if (initPruningOutput)
+	{
+		/* ExecGetLockRelsDoInitialPruning() already did it for us! */
+		*initially_valid_subplans = initPruningOutput->initially_valid_subplans;
+	}
+	else if (prunestate && prunestate->do_initial_prune)
 	{
 		/* Determine which subplans survive initial pruning */
-		*initially_valid_subplans = ExecFindInitialMatchingSubPlans(prunestate);
+		*initially_valid_subplans = ExecFindInitialMatchingSubPlans(prunestate,
+																	pruneinfo);
 	}
 	else
 	{
@@ -1668,7 +1718,7 @@ ExecInitPartitionPruning(PlanState *planstate,
 	 * invalid data in prunestate, because that data won't be consulted again
 	 * (cf initial Assert in ExecFindMatchingSubPlans).
 	 */
-	if (prunestate->do_exec_prune &&
+	if (prunestate && prunestate->do_exec_prune &&
 		bms_num_members(*initially_valid_subplans) < n_total_subplans)
 		PartitionPruneStateFixSubPlanMap(prunestate,
 										 *initially_valid_subplans,
@@ -1677,12 +1727,75 @@ ExecInitPartitionPruning(PlanState *planstate,
 	return prunestate;
 }
 
+/*
+ * ExecGetLockRelsDoInitialPruning
+ *		Perform initial pruning as part of doing ExecGetLockRels() on the parent
+ *		plan node
+ */
+Bitmapset *
+ExecGetLockRelsDoInitialPruning(Plan *plan, ExecGetLockRelsContext *context,
+								PartitionPruneInfo *pruneinfo)
+{
+	List		 *rtable = context->stmt->rtable;
+	ParamListInfo params = context->params;
+	ExprContext	 *econtext;
+	PartitionDirectory pdir;
+	MemoryContext oldcontext,
+				  tmpcontext;
+	PartitionPruneState *prunestate;
+	PlanInitPruningOutput *initPruningOutput;
+
+	/*
+	 * A temporary context to allocate stuff needded to run the pruning steps.
+	 */
+	tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
+									   "initial pruning working data",
+									   ALLOCSET_DEFAULT_SIZES);
+	oldcontext = MemoryContextSwitchTo(tmpcontext);
+
+	/*
+	 * PartitionDirectory to look up partition descriptors, which omits
+	 * detached partitions, just like in the executor proper.
+	 */
+	pdir = CreatePartitionDirectory(CurrentMemoryContext, false);
+
+	/*
+	 * We don't yet have a PlanState for the parent plan node, so must create
+	 * a standalone ExprContext to evaluate pruning expressions, equipped with
+	 * the information about the EXTERN parameters that the caller passed us.
+	 * Note that that's okay because the initial pruning steps do not contain
+	 * anything that requires the execution to have started.
+	 */
+	econtext = CreateStandaloneExprContext();
+	econtext->ecxt_param_list_info = params;
+	prunestate = ExecCreatePartitionPruneState(NULL, pruneinfo,
+											   true, false,
+											   rtable, econtext,
+											   pdir);
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Do the pruning and populate a PlanInitPruningOutput for this node. */
+	initPruningOutput = makeNode(PlanInitPruningOutput);
+	initPruningOutput->initially_valid_subplans =
+		ExecFindInitialMatchingSubPlans(prunestate, pruneinfo);
+	ExecStorePlanInitPruningOutput(context, initPruningOutput, plan);
+
+	FreeExprContext(econtext, true);
+	DestroyPartitionDirectory(pdir);
+	MemoryContextDelete(tmpcontext);
+
+	return initPruningOutput->initially_valid_subplans;
+}
+
 /*
  * ExecCreatePartitionPruneState
  *		Build the data structure required for calling
  *		ExecFindInitialMatchingSubPlans and ExecFindMatchingSubPlans.
  *
- * 'planstate' is the parent plan node's execution state.
+ * 'planstate', if not NULL, is the parent plan node's execution state.  It
+ * can be NULL if being called before ExecutorStart(), in which case,
+ * 'rtable' (range table), 'econtext', and 'partdir' must be explicitly
+ * provided.
  *
  * 'partitionpruneinfo' is a PartitionPruneInfo as generated by
  * make_partition_pruneinfo.  Here we build a PartitionPruneState containing a
@@ -1697,19 +1810,20 @@ ExecInitPartitionPruning(PlanState *planstate,
  */
 static PartitionPruneState *
 ExecCreatePartitionPruneState(PlanState *planstate,
-							  PartitionPruneInfo *partitionpruneinfo)
+							  PartitionPruneInfo *partitionpruneinfo,
+							  bool consider_initial_steps,
+							  bool consider_exec_steps,
+							  List *rtable, ExprContext *econtext,
+							  PartitionDirectory partdir)
 {
-	EState	   *estate = planstate->state;
+	EState	   *estate = planstate ? planstate->state : NULL;
 	PartitionPruneState *prunestate;
 	int			n_part_hierarchies;
 	ListCell   *lc;
 	int			i;
-	ExprContext	*econtext = planstate->ps_ExprContext;
 
-	/* For data reading, executor always omits detached partitions */
-	if (estate->es_partition_directory == NULL)
-		estate->es_partition_directory =
-			CreatePartitionDirectory(estate->es_query_cxt, false);
+	Assert((estate != NULL) ||
+			(partdir != NULL && econtext != NULL && rtable != NIL));
 
 	n_part_hierarchies = list_length(partitionpruneinfo->prune_infos);
 	Assert(n_part_hierarchies > 0);
@@ -1760,19 +1874,48 @@ ExecCreatePartitionPruneState(PlanState *planstate,
 			PartitionedRelPruneInfo *pinfo = lfirst_node(PartitionedRelPruneInfo, lc2);
 			PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
 			Relation	partrel;
+			bool		close_partrel = false;
 			PartitionDesc partdesc;
 			PartitionKey partkey;
 
 			/*
-			 * We can rely on the copies of the partitioned table's partition
-			 * key and partition descriptor appearing in its relcache entry,
-			 * because that entry will be held open and locked for the
-			 * duration of this executor run.
+			 * Must open the relation by ourselves when called before the
+			 * execution has started, such as, when called during
+			 * ExecutorGetLockRels() on a cached plan.  In that case,
+			 * sub-partitions must be locked, because AcquirePlannerLocks()
+			 * would not have seen them. (1st relation in a partrelpruneinfos
+			 * list is always the root partitioned table appearing in the
+			 * query, which AcquirePlannerLocks() would have locked; the
+			 * Assert in relation_open() guards that assumption.)
+			 */
+			if (estate == NULL)
+			{
+				RangeTblEntry *rte = rt_fetch(pinfo->rtindex, rtable);
+				int		lockmode = (j == 0) ? NoLock : rte->rellockmode;
+
+				partrel = table_open(rte->relid, lockmode);
+				close_partrel = true;
+			}
+			else
+				partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex);
+
+			/*
+			 * We can rely on the copy of the partitioned table's partition
+			 * key from in its relcache entry, because it can't change (or
+			 * get destroyed) as long as the relation is locked.  Partition
+			 * descriptor is taken from the PartitionDirectory associated with
+			 * the table that is held open long enough for the descriptor to
+			 * remain valid while it's used to perform the pruning steps.
 			 */
-			partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex);
 			partkey = RelationGetPartitionKey(partrel);
-			partdesc = PartitionDirectoryLookup(estate->es_partition_directory,
-												partrel);
+			partdesc = PartitionDirectoryLookup(partdir, partrel);
+
+			/*
+			 * Must close partrel, keeping the lock taken, if we're not using
+			 * EState's entry.
+			 */
+			if (close_partrel)
+				table_close(partrel, NoLock);
 
 			/*
 			 * Initialize the subplan_map and subpart_map.
@@ -1874,7 +2017,7 @@ ExecCreatePartitionPruneState(PlanState *planstate,
 			 * Initialize pruning contexts as needed.
 			 */
 			pprune->initial_pruning_steps = pinfo->initial_pruning_steps;
-			if (pinfo->initial_pruning_steps)
+			if (consider_initial_steps && pinfo->initial_pruning_steps)
 			{
 				ExecInitPruningContext(&pprune->initial_context,
 									   pinfo->initial_pruning_steps,
@@ -1884,7 +2027,7 @@ ExecCreatePartitionPruneState(PlanState *planstate,
 				prunestate->do_initial_prune = true;
 			}
 			pprune->exec_pruning_steps = pinfo->exec_pruning_steps;
-			if (pinfo->exec_pruning_steps)
+			if (consider_exec_steps && pinfo->exec_pruning_steps)
 			{
 				ExecInitPruningContext(&pprune->exec_context,
 									   pinfo->exec_pruning_steps,
@@ -1998,7 +2141,8 @@ ExecInitPruningContext(PartitionPruneContext *context,
  * is required.
  */
 static Bitmapset *
-ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate)
+ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate,
+								PartitionPruneInfo *pruneinfo)
 {
 	Bitmapset  *result = NULL;
 	MemoryContext oldcontext;
@@ -2008,8 +2152,8 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate)
 	Assert(prunestate->do_initial_prune);
 
 	/*
-	 * Switch to a temp context to avoid leaking memory in the executor's
-	 * query-lifespan memory context.
+	 * Switch to a temp context to avoid leaking memory in the longer-term
+	 * memory context.
 	 */
 	oldcontext = MemoryContextSwitchTo(prunestate->prune_context);
 
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..7246f9175f 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -119,6 +119,7 @@ CreateExecutorState(void)
 	estate->es_relations = NULL;
 	estate->es_rowmarks = NULL;
 	estate->es_plannedstmt = NULL;
+	estate->es_execlockrelsinfo = NULL;
 
 	estate->es_junkFilter = NULL;
 
@@ -785,6 +786,13 @@ ExecGetRangeTableRelation(EState *estate, Index rti)
 
 	Assert(rti > 0 && rti <= estate->es_range_table_size);
 
+	/*
+	 * A cross-check that AcquireExecutorLocks() hasn't missed any relations
+	 * it must not have.
+	 */
+	Assert(estate->es_execlockrelsinfo == NULL ||
+		   bms_is_member(rti, estate->es_execlockrelsinfo->lockrels));
+
 	rel = estate->es_relations[rti - 1];
 	if (rel == NULL)
 	{
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f9460ae506..a2182a6b1f 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -844,7 +844,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
 	else
 		dest = None_Receiver;
 
-	es->qd = CreateQueryDesc(es->stmt,
+	es->qd = CreateQueryDesc(es->stmt, NULL,
 							 fcache->src,
 							 GetActiveSnapshot(),
 							 InvalidSnapshot,
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 5b6d3eb23b..9c6f907687 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -94,6 +94,55 @@ static bool ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result);
 static void ExecAppendAsyncEventWait(AppendState *node);
 static void classify_matching_subplans(AppendState *node);
 
+/* ----------------------------------------------------------------
+ *		ExecGetAppendLockRels
+ *			Do ExecGetLockRels()'s work for an Append plan
+ * ----------------------------------------------------------------
+ */
+bool
+ExecGetAppendLockRels(Append *node, ExecGetLockRelsContext *context)
+{
+	PartitionPruneInfo *pruneinfo = node->part_prune_info;
+
+	/*
+	 * Must always lock all the partitioned tables whose direct and indirect
+	 * partitions will be scanned by this Append.
+	 */
+	context->lockrels = bms_add_members(context->lockrels,
+										node->partitioned_rels);
+
+	/*
+	 * Now recurse to subplans to add relations scanned therein.
+	 *
+	 * If initial pruning can be done, do that now and only recurse to the
+	 * surviving subplans.
+	 */
+	if (pruneinfo && pruneinfo->needs_init_pruning)
+	{
+		List	   *subplans = node->appendplans;
+		Bitmapset  *validsubplans;
+		int			i;
+
+		validsubplans = ExecGetLockRelsDoInitialPruning((Plan *) node,
+														context, pruneinfo);
+
+		/* Recurse to surviving subplans. */
+		i = -1;
+		while ((i = bms_next_member(validsubplans, i)) >= 0)
+		{
+			Plan   *subplan = list_nth(subplans, i);
+
+			(void) ExecGetLockRels(subplan, context);
+		}
+
+		/* done with this node */
+		return true;
+	}
+
+	/* Tell the caller to recurse to *all* the subplans. */
+	return false;
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitAppend
  *
@@ -155,7 +204,8 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 		 * subplan, we can fill as_valid_subplans immediately, preventing
 		 * later calls to ExecFindMatchingSubPlans.
 		 */
-		if (!prunestate->do_exec_prune && nplans > 0)
+		if (appendstate->as_prune_state == NULL ||
+			(!appendstate->as_prune_state->do_exec_prune && nplans > 0))
 			appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
 	}
 	else
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 9a9f29e845..4b04fcdbc2 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -54,6 +54,55 @@ typedef int32 SlotNumber;
 static TupleTableSlot *ExecMergeAppend(PlanState *pstate);
 static int	heap_compare_slots(Datum a, Datum b, void *arg);
 
+/* ----------------------------------------------------------------
+ *		ExecGetMergeAppendLockRels
+ *			Do ExecGetLockRels()'s work for a MergeAppend plan
+ * ----------------------------------------------------------------
+ */
+bool
+ExecGetMergeAppendLockRels(MergeAppend *node, ExecGetLockRelsContext *context)
+{
+	PartitionPruneInfo *pruneinfo = node->part_prune_info;
+
+	/*
+	 * Must always lock all the partitioned tables whose direct and indirect
+	 * partitions will be scanned by this Append.
+	 */
+	context->lockrels = bms_add_members(context->lockrels,
+										node->partitioned_rels);
+
+	/*
+	 * Now recurse to subplans to add relations scanned therein.
+	 *
+	 * If initial pruning can be done, do that now and only recurse to the
+	 * surviving subplans.
+	 */
+	if (pruneinfo && pruneinfo->needs_init_pruning)
+	{
+		List	   *subplans = node->mergeplans;
+		Bitmapset  *validsubplans;
+		int			i;
+
+		validsubplans = ExecGetLockRelsDoInitialPruning((Plan *) node,
+														context, pruneinfo);
+
+		/* Recurse to surviving subplans. */
+		i = -1;
+		while ((i = bms_next_member(validsubplans, i)) >= 0)
+		{
+			Plan   *subplan = list_nth(subplans, i);
+
+			(void) ExecGetLockRels(subplan, context);
+		}
+
+		/* done with this node */
+		return true;
+	}
+
+	/* Tell the caller to recurse to *all* the subplans. */
+	return false;
+}
+
 
 /* ----------------------------------------------------------------
  *		ExecInitMergeAppend
@@ -103,7 +152,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 		 * subplan, we can fill as_valid_subplans immediately, preventing
 		 * later calls to ExecFindMatchingSubPlans.
 		 */
-		if (!prunestate->do_exec_prune && nplans > 0)
+		if (mergestate->ms_prune_state == NULL ||
+			(!mergestate->ms_prune_state->do_exec_prune && nplans > 0))
 			mergestate->ms_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
 	}
 	else
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 171575cd73..f17bede367 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -3853,6 +3853,31 @@ ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
 	return NULL;
 }
 
+/*
+ * ExecGetModifyTableLockRels
+ * 		Do ExecGetLockRels()'s work for a ModifyTable plan
+ */
+bool
+ExecGetModifyTableLockRels(ModifyTable *plan, ExecGetLockRelsContext *context)
+{
+	ListCell *lc;
+
+	/* First add the result relation RTIs mentioned in the node. */
+	if (plan->rootRelation > 0)
+		context->lockrels = bms_add_member(context->lockrels,
+										   plan->rootRelation);
+	context->lockrels = bms_add_member(context->lockrels,
+									   plan->nominalRelation);
+	foreach(lc, plan->resultRelations)
+	{
+		context->lockrels = bms_add_member(context->lockrels,
+										   lfirst_int(lc));
+	}
+
+	/* Tell the caller to recurse to the subplan (outerPlan(plan)). */
+	return false;
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 042a5f8b0a..64ebbfb31e 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1578,6 +1578,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	CachedPlanSource *plansource;
 	CachedPlan *cplan;
 	List	   *stmt_list;
+	List	   *execlockrelsinfo_list;
 	char	   *query_string;
 	Snapshot	snapshot;
 	MemoryContext oldcontext;
@@ -1659,6 +1660,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	/* Replan if needed, and increment plan refcount for portal */
 	cplan = GetCachedPlan(plansource, paramLI, NULL, _SPI_current->queryEnv);
 	stmt_list = cplan->stmt_list;
+	execlockrelsinfo_list = cplan->execlockrelsinfo_list;
 
 	if (!plan->saved)
 	{
@@ -1670,6 +1672,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 		 */
 		oldcontext = MemoryContextSwitchTo(portal->portalContext);
 		stmt_list = copyObject(stmt_list);
+		execlockrelsinfo_list = copyObject(execlockrelsinfo_list);
 		MemoryContextSwitchTo(oldcontext);
 		ReleaseCachedPlan(cplan, NULL);
 		cplan = NULL;			/* portal shouldn't depend on cplan */
@@ -1683,6 +1686,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 					  query_string,
 					  plansource->commandTag,
 					  stmt_list,
+					  execlockrelsinfo_list,
 					  cplan);
 
 	/*
@@ -2473,7 +2477,9 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 	{
 		CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
 		List	   *stmt_list;
-		ListCell   *lc2;
+		List	   *execlockrelsinfo_list;
+		ListCell   *lc2,
+				   *lc3;
 
 		spicallbackarg.query = plansource->query_string;
 
@@ -2552,6 +2558,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 							  plan_owner, _SPI_current->queryEnv);
 
 		stmt_list = cplan->stmt_list;
+		execlockrelsinfo_list = cplan->execlockrelsinfo_list;
 
 		/*
 		 * If we weren't given a specific snapshot to use, and the statement
@@ -2589,9 +2596,10 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 			}
 		}
 
-		foreach(lc2, stmt_list)
+		forboth(lc2, stmt_list, lc3, execlockrelsinfo_list)
 		{
 			PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2);
+			ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, lc3);
 			bool		canSetTag = stmt->canSetTag;
 			DestReceiver *dest;
 
@@ -2663,7 +2671,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 				else
 					snap = InvalidSnapshot;
 
-				qdesc = CreateQueryDesc(stmt,
+				qdesc = CreateQueryDesc(stmt, execlockrelsinfo,
 										plansource->query_string,
 										snap, crosscheck_snapshot,
 										dest,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 29c515d7db..afffabbea0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -68,6 +68,13 @@
 		} \
 	} while (0)
 
+/* Copy a field that is an array with numElem ints */
+#define COPY_INT_ARRAY(fldname, numElem) \
+	do { \
+		newnode->fldname = (numElem) > 0 ? palloc((numElem) * sizeof(int)) : NULL; \
+		memcpy(newnode->fldname, from->fldname, sizeof(int) * (numElem)); \
+	} while (0)
+
 /* Copy a parse location field (for Copy, this is same as scalar case) */
 #define COPY_LOCATION_FIELD(fldname) \
 	(newnode->fldname = from->fldname)
@@ -94,8 +101,10 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(transientPlan);
 	COPY_SCALAR_FIELD(dependsOnRole);
 	COPY_SCALAR_FIELD(parallelModeNeeded);
+	COPY_SCALAR_FIELD(containsInitialPruning);
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
+	COPY_SCALAR_FIELD(numPlanNodes);
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
@@ -1282,6 +1291,8 @@ _copyPartitionPruneInfo(const PartitionPruneInfo *from)
 	PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo);
 
 	COPY_NODE_FIELD(prune_infos);
+	COPY_SCALAR_FIELD(needs_init_pruning);
+	COPY_SCALAR_FIELD(needs_exec_pruning);
 	COPY_BITMAPSET_FIELD(other_subplans);
 
 	return newnode;
@@ -5373,6 +5384,33 @@ _copyExtensibleNode(const ExtensibleNode *from)
 	return newnode;
 }
 
+/* ****************************************************************
+ *					execnodes.h copy functions
+ * ****************************************************************
+ */
+static ExecLockRelsInfo *
+_copyExecLockRelsInfo(const ExecLockRelsInfo *from)
+{
+	ExecLockRelsInfo *newnode = makeNode(ExecLockRelsInfo);
+
+	COPY_BITMAPSET_FIELD(lockrels);
+	COPY_SCALAR_FIELD(numPlanNodes);
+	COPY_NODE_FIELD(initPruningOutputs);
+	COPY_INT_ARRAY(ipoIndexes, from->numPlanNodes);
+
+	return newnode;
+}
+
+static PlanInitPruningOutput *
+_copyPlanInitPruningOutput(const PlanInitPruningOutput *from)
+{
+	PlanInitPruningOutput *newnode = makeNode(PlanInitPruningOutput);
+
+	COPY_BITMAPSET_FIELD(initially_valid_subplans);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					value.h copy functions
  * ****************************************************************
@@ -5427,7 +5465,6 @@ _copyBitString(const BitString *from)
 	return newnode;
 }
 
-
 static ForeignKeyCacheInfo *
 _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 {
@@ -6454,6 +6491,16 @@ copyObjectImpl(const void *from)
 			retval = _copyPublicationTable(from);
 			break;
 
+			/*
+			 * EXECUTION NODES
+			 */
+		case T_ExecLockRelsInfo:
+			retval = _copyExecLockRelsInfo(from);
+			break;
+		case T_PlanInitPruningOutput:
+			retval = _copyPlanInitPruningOutput(from);
+			break;
+
 			/*
 			 * MISCELLANEOUS NODES
 			 */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 108ede9af9..e2d7e6bcac 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -312,8 +312,10 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_BOOL_FIELD(transientPlan);
 	WRITE_BOOL_FIELD(dependsOnRole);
 	WRITE_BOOL_FIELD(parallelModeNeeded);
+	WRITE_BOOL_FIELD(containsInitialPruning);
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
+	WRITE_INT_FIELD(numPlanNodes);
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
@@ -1008,6 +1010,8 @@ _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 	WRITE_NODE_TYPE("PARTITIONPRUNEINFO");
 
 	WRITE_NODE_FIELD(prune_infos);
+	WRITE_BOOL_FIELD(needs_init_pruning);
+	WRITE_BOOL_FIELD(needs_exec_pruning);
 	WRITE_BITMAPSET_FIELD(other_subplans);
 }
 
@@ -2818,6 +2822,31 @@ _outExtensibleNode(StringInfo str, const ExtensibleNode *node)
 	methods->nodeOut(str, node);
 }
 
+/*****************************************************************************
+ *
+ *	Stuff from execnodes.h
+ *
+ *****************************************************************************/
+
+static void
+_outExecLockRelsInfo(StringInfo str, const ExecLockRelsInfo *node)
+{
+	WRITE_NODE_TYPE("EXECLOCKRELSINFO");
+
+	WRITE_BITMAPSET_FIELD(lockrels);
+	WRITE_INT_FIELD(numPlanNodes);
+	WRITE_NODE_FIELD(initPruningOutputs);
+	WRITE_INT_ARRAY(ipoIndexes, node->numPlanNodes);
+}
+
+static void
+_outPlanInitPruningOutput(StringInfo str, const PlanInitPruningOutput *node)
+{
+	WRITE_NODE_TYPE("PLANINITPRUNINGOUTPUT");
+
+	WRITE_BITMAPSET_FIELD(initially_valid_subplans);
+}
+
 /*****************************************************************************
  *
  *	Stuff from parsenodes.h.
@@ -4720,6 +4749,16 @@ outNode(StringInfo str, const void *obj)
 				_outJsonItemCoercions(str, obj);
 				break;
 
+				/*
+				 * EXECUTION NODES
+				 */
+			case T_ExecLockRelsInfo:
+				_outExecLockRelsInfo(str, obj);
+				break;
+			case T_PlanInitPruningOutput:
+				_outPlanInitPruningOutput(str, obj);
+				break;
+
 			default:
 
 				/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ce146dd45e..88173f70a1 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1782,8 +1782,10 @@ _readPlannedStmt(void)
 	READ_BOOL_FIELD(transientPlan);
 	READ_BOOL_FIELD(dependsOnRole);
 	READ_BOOL_FIELD(parallelModeNeeded);
+	READ_BOOL_FIELD(containsInitialPruning);
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
+	READ_INT_FIELD(numPlanNodes);
 	READ_NODE_FIELD(rtable);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
@@ -2735,6 +2737,8 @@ _readPartitionPruneInfo(void)
 	READ_LOCALS(PartitionPruneInfo);
 
 	READ_NODE_FIELD(prune_infos);
+	READ_BOOL_FIELD(needs_init_pruning);
+	READ_BOOL_FIELD(needs_exec_pruning);
 	READ_BITMAPSET_FIELD(other_subplans);
 
 	READ_DONE();
@@ -2904,6 +2908,35 @@ _readPartitionRangeDatum(void)
 	READ_DONE();
 }
 
+/*
+ * _readExecLockRelsInfo
+ */
+static ExecLockRelsInfo *
+_readExecLockRelsInfo(void)
+{
+	READ_LOCALS(ExecLockRelsInfo);
+
+	READ_BITMAPSET_FIELD(lockrels);
+	READ_INT_FIELD(numPlanNodes);
+	READ_NODE_FIELD(initPruningOutputs);
+	READ_INT_ARRAY(ipoIndexes, local_node->numPlanNodes);
+
+	READ_DONE();
+}
+
+/*
+ * _readPlanInitPruningOutput
+ */
+static PlanInitPruningOutput *
+_readPlanInitPruningOutput(void)
+{
+	READ_LOCALS(PlanInitPruningOutput);
+
+	READ_BITMAPSET_FIELD(initially_valid_subplans);
+
+	READ_DONE();
+}
+
 /*
  * parseNodeString
  *
@@ -3197,6 +3230,10 @@ parseNodeString(void)
 		return_value = _readJsonCoercion();
 	else if (MATCH("JSONITEMCOERCIONS", 17))
 		return_value = _readJsonItemCoercions();
+	else if (MATCH("EXECLOCKRELSINFO", 16))
+		return_value = _readExecLockRelsInfo();
+	else if (MATCH("PLANINITPRUNINGOUTPUT", 21))
+		return_value = _readPlanInitPruningOutput();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index c769b4b4b9..4c586ac1ec 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -517,7 +517,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->transientPlan = glob->transientPlan;
 	result->dependsOnRole = glob->dependsOnRole;
 	result->parallelModeNeeded = glob->parallelModeNeeded;
+	result->containsInitialPruning = glob->containsInitialPruning;
 	result->planTree = top_plan;
+	result->numPlanNodes = glob->lastPlanNodeId;
 	result->rtable = glob->finalrtable;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 8214edec54..a1c6c3caa2 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1623,6 +1623,9 @@ set_append_references(PlannerInfo *root,
 				pinfo->rtindex += rtoffset;
 			}
 		}
+
+		if (aplan->part_prune_info->needs_init_pruning)
+			root->glob->containsInitialPruning = true;
 	}
 
 	/* We don't need to recurse to lefttree or righttree ... */
@@ -1710,6 +1713,9 @@ set_mergeappend_references(PlannerInfo *root,
 				pinfo->rtindex += rtoffset;
 			}
 		}
+
+		if (mplan->part_prune_info->needs_init_pruning)
+			root->glob->containsInitialPruning = true;
 	}
 
 	/* We don't need to recurse to lefttree or righttree ... */
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 7080cb25d9..3322dc79f2 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -144,7 +144,9 @@ static List *make_partitionedrel_pruneinfo(PlannerInfo *root,
 										   List *prunequal,
 										   Bitmapset *partrelids,
 										   int *relid_subplan_map,
-										   Bitmapset **matchedsubplans);
+										   Bitmapset **matchedsubplans,
+										   bool *needs_init_pruning,
+										   bool *needs_exec_pruning);
 static void gen_partprune_steps(RelOptInfo *rel, List *clauses,
 								PartClauseTarget target,
 								GeneratePruningStepsContext *context);
@@ -230,6 +232,8 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 	int		   *relid_subplan_map;
 	ListCell   *lc;
 	int			i;
+	bool		needs_init_pruning = false;
+	bool		needs_exec_pruning = false;
 
 	/*
 	 * Scan the subpaths to see which ones are scans of partition child
@@ -309,12 +313,16 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 		Bitmapset  *partrelids = (Bitmapset *) lfirst(lc);
 		List	   *pinfolist;
 		Bitmapset  *matchedsubplans = NULL;
+		bool		partrel_needs_init_pruning;
+		bool		partrel_needs_exec_pruning;
 
 		pinfolist = make_partitionedrel_pruneinfo(root, parentrel,
 												  prunequal,
 												  partrelids,
 												  relid_subplan_map,
-												  &matchedsubplans);
+												  &matchedsubplans,
+												  &partrel_needs_init_pruning,
+												  &partrel_needs_exec_pruning);
 
 		/* When pruning is possible, record the matched subplans */
 		if (pinfolist != NIL)
@@ -323,6 +331,10 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 			allmatchedsubplans = bms_join(matchedsubplans,
 										  allmatchedsubplans);
 		}
+		if (!needs_init_pruning)
+			needs_init_pruning = partrel_needs_init_pruning;
+		if (!needs_exec_pruning)
+			needs_exec_pruning = partrel_needs_exec_pruning;
 	}
 
 	pfree(relid_subplan_map);
@@ -337,6 +349,8 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 	/* Else build the result data structure */
 	pruneinfo = makeNode(PartitionPruneInfo);
 	pruneinfo->prune_infos = prunerelinfos;
+	pruneinfo->needs_init_pruning = needs_init_pruning;
+	pruneinfo->needs_exec_pruning = needs_exec_pruning;
 
 	/*
 	 * Some subplans may not belong to any of the identified partitioned rels.
@@ -435,13 +449,18 @@ add_part_relids(List *allpartrelids, Bitmapset *partrelids)
  * If we cannot find any useful run-time pruning steps, return NIL.
  * However, on success, each rel identified in partrelids will have
  * an element in the result list, even if some of them are useless.
+ * *needs_init_pruning and *needs_exec_pruning are set to indicate that the
+ * returned PartitionedRelPruneInfos contains pruning steps that can be
+ * performed before and after execution begins, respectively.
  */
 static List *
 make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 							  List *prunequal,
 							  Bitmapset *partrelids,
 							  int *relid_subplan_map,
-							  Bitmapset **matchedsubplans)
+							  Bitmapset **matchedsubplans,
+							  bool *needs_init_pruning,
+							  bool *needs_exec_pruning)
 {
 	RelOptInfo *targetpart = NULL;
 	List	   *pinfolist = NIL;
@@ -452,6 +471,10 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 	int			rti;
 	int			i;
 
+	/* Will find out below. */
+	*needs_init_pruning = false;
+	*needs_exec_pruning = false;
+
 	/*
 	 * Examine each partitioned rel, constructing a temporary array to map
 	 * from planner relids to index of the partitioned rel, and building a
@@ -539,6 +562,9 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 		 * executor per-scan pruning steps.  This first pass creates startup
 		 * pruning steps and detects whether there's any possibly-useful quals
 		 * that would require per-scan pruning.
+		 *
+		 * In the first pass, we note whether the 2nd pass is necessary by
+		 * by noting the presence of EXEC parameters.
 		 */
 		gen_partprune_steps(subpart, partprunequal, PARTTARGET_INITIAL,
 							&context);
@@ -613,6 +639,11 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 		pinfo->execparamids = execparamids;
 		/* Remaining fields will be filled in the next loop */
 
+		if (!*needs_init_pruning)
+			*needs_init_pruning = (initial_pruning_steps != NIL);
+		if (!*needs_exec_pruning)
+			*needs_exec_pruning = (exec_pruning_steps != NIL);
+
 		pinfolist = lappend(pinfolist, pinfo);
 	}
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ba2fcfeb4a..085eb3f209 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -945,15 +945,17 @@ pg_plan_query(Query *querytree, const char *query_string, int cursorOptions,
  * For normal optimizable statements, invoke the planner.  For utility
  * statements, just make a wrapper PlannedStmt node.
  *
- * The result is a list of PlannedStmt nodes.
+ * The result is a list of PlannedStmt nodes.  Also, a NULL is appended to
+ * *execlockrelsinfo_list for each PlannedStmt added to the returned list.
  */
 List *
 pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions,
-				ParamListInfo boundParams)
+				ParamListInfo boundParams, List **execlockrelsinfo_list)
 {
 	List	   *stmt_list = NIL;
 	ListCell   *query_list;
 
+	*execlockrelsinfo_list = NIL;
 	foreach(query_list, querytrees)
 	{
 		Query	   *query = lfirst_node(Query, query_list);
@@ -977,6 +979,7 @@ pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions,
 		}
 
 		stmt_list = lappend(stmt_list, stmt);
+		*execlockrelsinfo_list = lappend(*execlockrelsinfo_list, NULL);
 	}
 
 	return stmt_list;
@@ -1080,7 +1083,8 @@ exec_simple_query(const char *query_string)
 		QueryCompletion qc;
 		MemoryContext per_parsetree_context = NULL;
 		List	   *querytree_list,
-				   *plantree_list;
+				   *plantree_list,
+				   *plantree_execlockrelsinfo_list;
 		Portal		portal;
 		DestReceiver *receiver;
 		int16		format;
@@ -1167,7 +1171,8 @@ exec_simple_query(const char *query_string)
 												NULL, 0, NULL);
 
 		plantree_list = pg_plan_queries(querytree_list, query_string,
-										CURSOR_OPT_PARALLEL_OK, NULL);
+										CURSOR_OPT_PARALLEL_OK, NULL,
+										&plantree_execlockrelsinfo_list);
 
 		/*
 		 * Done with the snapshot used for parsing/planning.
@@ -1203,6 +1208,7 @@ exec_simple_query(const char *query_string)
 						  query_string,
 						  commandTag,
 						  plantree_list,
+						  plantree_execlockrelsinfo_list,
 						  NULL);
 
 		/*
@@ -1991,6 +1997,7 @@ exec_bind_message(StringInfo input_message)
 					  query_string,
 					  psrc->commandTag,
 					  cplan->stmt_list,
+					  cplan->execlockrelsinfo_list,
 					  cplan);
 
 	/* Done with the snapshot used for parameter I/O and parsing/planning */
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 5aa5a350f3..0fd8c65de7 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -35,7 +35,7 @@
 Portal		ActivePortal = NULL;
 
 
-static void ProcessQuery(PlannedStmt *plan,
+static void ProcessQuery(PlannedStmt *plan, ExecLockRelsInfo *execlockrelsinfo,
 						 const char *sourceText,
 						 ParamListInfo params,
 						 QueryEnvironment *queryEnv,
@@ -65,6 +65,7 @@ static void DoPortalRewind(Portal portal);
  */
 QueryDesc *
 CreateQueryDesc(PlannedStmt *plannedstmt,
+				ExecLockRelsInfo *execlockrelsinfo,
 				const char *sourceText,
 				Snapshot snapshot,
 				Snapshot crosscheck_snapshot,
@@ -77,6 +78,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt,
 
 	qd->operation = plannedstmt->commandType;	/* operation */
 	qd->plannedstmt = plannedstmt;	/* plan */
+	qd->execlockrelsinfo = execlockrelsinfo;		/* ExecutorGetLockRels() output for plan */
 	qd->sourceText = sourceText;	/* query text */
 	qd->snapshot = RegisterSnapshot(snapshot);	/* snapshot */
 	/* RI check snapshot */
@@ -122,6 +124,7 @@ FreeQueryDesc(QueryDesc *qdesc)
  *		PORTAL_ONE_RETURNING, or PORTAL_ONE_MOD_WITH portal
  *
  *	plan: the plan tree for the query
+ *	execlockrelsinfo: ExecutorGetLockRels() output for the plan tree
  *	sourceText: the source text of the query
  *	params: any parameters needed
  *	dest: where to send results
@@ -134,6 +137,7 @@ FreeQueryDesc(QueryDesc *qdesc)
  */
 static void
 ProcessQuery(PlannedStmt *plan,
+			 ExecLockRelsInfo *execlockrelsinfo,
 			 const char *sourceText,
 			 ParamListInfo params,
 			 QueryEnvironment *queryEnv,
@@ -145,7 +149,7 @@ ProcessQuery(PlannedStmt *plan,
 	/*
 	 * Create the QueryDesc object
 	 */
-	queryDesc = CreateQueryDesc(plan, sourceText,
+	queryDesc = CreateQueryDesc(plan, execlockrelsinfo, sourceText,
 								GetActiveSnapshot(), InvalidSnapshot,
 								dest, params, queryEnv, 0);
 
@@ -493,6 +497,7 @@ PortalStart(Portal portal, ParamListInfo params,
 				 * the destination to DestNone.
 				 */
 				queryDesc = CreateQueryDesc(linitial_node(PlannedStmt, portal->stmts),
+											linitial_node(ExecLockRelsInfo, portal->execlockrelsinfos),
 											portal->sourceText,
 											GetActiveSnapshot(),
 											InvalidSnapshot,
@@ -1193,7 +1198,8 @@ PortalRunMulti(Portal portal,
 			   QueryCompletion *qc)
 {
 	bool		active_snapshot_set = false;
-	ListCell   *stmtlist_item;
+	ListCell   *stmtlist_item,
+			   *execlockrelsinfolist_item;
 
 	/*
 	 * If the destination is DestRemoteExecute, change to DestNone.  The
@@ -1214,9 +1220,12 @@ PortalRunMulti(Portal portal,
 	 * Loop to handle the individual queries generated from a single parsetree
 	 * by analysis and rewrite.
 	 */
-	foreach(stmtlist_item, portal->stmts)
+	forboth(stmtlist_item, portal->stmts,
+			execlockrelsinfolist_item, portal->execlockrelsinfos)
 	{
 		PlannedStmt *pstmt = lfirst_node(PlannedStmt, stmtlist_item);
+		ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo,
+											   execlockrelsinfolist_item);
 
 		/*
 		 * If we got a cancel signal in prior command, quit
@@ -1274,7 +1283,7 @@ PortalRunMulti(Portal portal,
 			if (pstmt->canSetTag)
 			{
 				/* statement can set tag string */
-				ProcessQuery(pstmt,
+				ProcessQuery(pstmt, execlockrelsinfo,
 							 portal->sourceText,
 							 portal->portalParams,
 							 portal->queryEnv,
@@ -1283,7 +1292,7 @@ PortalRunMulti(Portal portal,
 			else
 			{
 				/* stmt added by rewrite cannot set tag */
-				ProcessQuery(pstmt,
+				ProcessQuery(pstmt, execlockrelsinfo,
 							 portal->sourceText,
 							 portal->portalParams,
 							 portal->queryEnv,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 4cf6db504f..9f5a40a0a6 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -99,14 +99,16 @@ static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_l
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
 								   QueryEnvironment *queryEnv);
-static bool CheckCachedPlan(CachedPlanSource *plansource);
+static bool CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams);
 static CachedPlan *BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 								   ParamListInfo boundParams, QueryEnvironment *queryEnv);
+static void CachedPlanSaveExecLockRelsInfos(CachedPlan *plan, List *execlockrelsinfo_list);
 static bool choose_custom_plan(CachedPlanSource *plansource,
 							   ParamListInfo boundParams);
 static double cached_plan_cost(CachedPlan *plan, bool include_planner);
 static Query *QueryListGetPrimaryStmt(List *stmts);
-static void AcquireExecutorLocks(List *stmt_list, bool acquire);
+static List *AcquireExecutorLocks(List *stmt_list, ParamListInfo boundParams);
+static void ReleaseExecutorLocks(List *stmt_list, List *execlockrelsinfo_list);
 static void AcquirePlannerLocks(List *stmt_list, bool acquire);
 static void ScanQueryForLocks(Query *parsetree, bool acquire);
 static bool ScanQueryWalker(Node *node, bool *acquire);
@@ -790,9 +792,21 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
  *
  * On a "true" return, we have acquired the locks needed to run the plan.
  * (We must do this for the "true" result to be race-condition-free.)
+ *
+ * If the CachedPlan is valid, this may in some cases call ExecutorGetLockRels
+ * on each PlannedStmt contained in it to determine the set of relations to be
+ * locked by AcquireExecutorLocks(), instead of just scanning its range table,
+ * which is done to prune away any nodes in the tree that need not be executed
+ * based on the result of initial partition pruning.  Resulting
+ * ExecLockRelsInfo nodes containing the result of such pruning, allocated in
+ * a child context of the context containing the plan itself, are added into
+ * plan->execlockrelsinfo_list.  The previous contents of the list from the
+ * last invocation on the same CachedPlan are deleted, because they would no
+ * longer be valid given the fresh set of parameter values which may be used
+ * as pruning parameters.
  */
 static bool
-CheckCachedPlan(CachedPlanSource *plansource)
+CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams)
 {
 	CachedPlan *plan = plansource->gplan;
 
@@ -820,13 +834,25 @@ CheckCachedPlan(CachedPlanSource *plansource)
 	 */
 	if (plan->is_valid)
 	{
+		List   *execlockrelsinfo_list;
+
 		/*
 		 * Plan must have positive refcount because it is referenced by
 		 * plansource; so no need to fear it disappears under us here.
 		 */
 		Assert(plan->refcount > 0);
 
-		AcquireExecutorLocks(plan->stmt_list, true);
+		/*
+		 * Lock relations scanned by the plan.  If ExecutorGetLockRels() asked
+		 * to omit some relations because the plan nodes that scan them were
+		 * found to be pruned, the executor will be informed of the omission of
+		 * the plan nodes themselves, so that it doesn't accidentally try to
+		 * execute those nodes, via the ExecLockRelsInfo nodes collected in the
+		 * returned list that is also passed to it along with the list of
+		 * PlannedStmts.
+		 */
+		execlockrelsinfo_list = AcquireExecutorLocks(plan->stmt_list,
+													 boundParams);
 
 		/*
 		 * If plan was transient, check to see if TransactionXmin has
@@ -844,11 +870,14 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		if (plan->is_valid)
 		{
 			/* Successfully revalidated and locked the query. */
+
+			/* Remember ExecLockRelsInfos in the CachedPlan. */
+			CachedPlanSaveExecLockRelsInfos(plan, execlockrelsinfo_list);
 			return true;
 		}
 
 		/* Oops, the race case happened.  Release useless locks. */
-		AcquireExecutorLocks(plan->stmt_list, false);
+		ReleaseExecutorLocks(plan->stmt_list, execlockrelsinfo_list);
 	}
 
 	/*
@@ -880,7 +909,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 				ParamListInfo boundParams, QueryEnvironment *queryEnv)
 {
 	CachedPlan *plan;
-	List	   *plist;
+	List	   *plist,
+			   *execlockrelsinfo_list;
 	bool		snapshot_set;
 	bool		is_transient;
 	MemoryContext plan_context;
@@ -933,7 +963,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	 * Generate the plan.
 	 */
 	plist = pg_plan_queries(qlist, plansource->query_string,
-							plansource->cursor_options, boundParams);
+							plansource->cursor_options, boundParams,
+							&execlockrelsinfo_list);
 
 	/* Release snapshot if we got one */
 	if (snapshot_set)
@@ -1002,6 +1033,16 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_saved = false;
 	plan->is_valid = true;
 
+	/*
+	 * Save the dummy ExecLockRelsInfo list, that is a list containing NULLs
+	 * as elements.  We must do this, becasue users of the CachedPlan expect
+	 * one to go with the list of PlannedStmts.
+	 * XXX maybe get rid of that contract.
+	 */
+	plan->execlockrelsinfo_context = NULL;
+	CachedPlanSaveExecLockRelsInfos(plan, execlockrelsinfo_list);
+	Assert(MemoryContextIsValid(plan->execlockrelsinfo_context));
+
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
 
@@ -1160,7 +1201,7 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 
 	if (!customplan)
 	{
-		if (CheckCachedPlan(plansource))
+		if (CheckCachedPlan(plansource, boundParams))
 		{
 			/* We want a generic plan, and we already have a valid one */
 			plan = plansource->gplan;
@@ -1586,6 +1627,49 @@ CopyCachedPlan(CachedPlanSource *plansource)
 	return newsource;
 }
 
+/*
+ * CachedPlanSaveExecLockRelsInfos
+ *		Save the list containing ExecLockRelsInfo nodes into the given
+ *		CachedPlan
+ *
+ * The provided list is copied into a dedicated context that is a child of
+ * plan->context.  If the child context already exists, it is emptied, because
+ * any ExecLockRelsInfo contained therein would no longer be useful.
+ */
+static void
+CachedPlanSaveExecLockRelsInfos(CachedPlan *plan, List *execlockrelsinfo_list)
+{
+	MemoryContext	execlockrelsinfo_context = plan->execlockrelsinfo_context,
+					oldcontext = CurrentMemoryContext;
+	List		   *execlockrelsinfo_list_copy;
+
+	/*
+	 * Set up the dedicated context if not already done, saving it as a child
+	 * of the CachedPlan's context.
+	 */
+	if (execlockrelsinfo_context == NULL)
+	{
+		execlockrelsinfo_context = AllocSetContextCreate(CurrentMemoryContext,
+												 "CachedPlan execlockrelsinfo list",
+												 ALLOCSET_START_SMALL_SIZES);
+		MemoryContextSetParent(execlockrelsinfo_context, plan->context);
+		MemoryContextSetIdentifier(execlockrelsinfo_context, plan->context->ident);
+		plan->execlockrelsinfo_context = execlockrelsinfo_context;
+	}
+	else
+	{
+		/* Just clear existing contents by resetting the context. */
+		Assert(MemoryContextIsValid(execlockrelsinfo_context));
+		MemoryContextReset(execlockrelsinfo_context);
+	}
+
+	MemoryContextSwitchTo(execlockrelsinfo_context);
+	execlockrelsinfo_list_copy = copyObject(execlockrelsinfo_list);
+	MemoryContextSwitchTo(oldcontext);
+
+	plan->execlockrelsinfo_list = execlockrelsinfo_list_copy;
+}
+
 /*
  * CachedPlanIsValid: test whether the rewritten querytree within a
  * CachedPlanSource is currently valid (that is, not marked as being in need
@@ -1737,17 +1821,21 @@ QueryListGetPrimaryStmt(List *stmts)
 
 /*
  * AcquireExecutorLocks: acquire locks needed for execution of a cached plan;
- * or release them if acquire is false.
+ *
+ * Returns a list of ExecLockRelsInfo nodes containing one element for each
+ * PlannedStmt in stmt_list or NULL if the latter is utility statement or its
+ * containsInitialPruning is false.
  */
-static void
-AcquireExecutorLocks(List *stmt_list, bool acquire)
+static List *
+AcquireExecutorLocks(List *stmt_list, ParamListInfo boundParams)
 {
 	ListCell   *lc1;
+	List	   *execlockrelsinfo_list = NIL;
 
 	foreach(lc1, stmt_list)
 	{
 		PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1);
-		ListCell   *lc2;
+		ExecLockRelsInfo *execlockrelsinfo = NULL;
 
 		if (plannedstmt->commandType == CMD_UTILITY)
 		{
@@ -1761,27 +1849,139 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
 			Query	   *query = UtilityContainsQuery(plannedstmt->utilityStmt);
 
 			if (query)
-				ScanQueryForLocks(query, acquire);
-			continue;
+				ScanQueryForLocks(query, true);
 		}
-
-		foreach(lc2, plannedstmt->rtable)
+		else
 		{
-			RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
+			/*
+			 * Figure out the set of relations that would need to be locked
+			 * before executing the plan.
+			 */
+			if (!plannedstmt->containsInitialPruning)
+			{
+				/*
+				 * If the plan contains no initial pruning steps, just lock
+				 * all the relations found in the range table.
+				 */
+				ListCell *lc;
 
-			if (rte->rtekind != RTE_RELATION)
-				continue;
+				foreach(lc, plannedstmt->rtable)
+				{
+					RangeTblEntry *rte = lfirst(lc);
+
+					if (rte->rtekind != RTE_RELATION)
+						continue;
+
+					/*
+					 * Acquire the appropriate type of lock on each relation
+					 * OID. Note that we don't actually try to open the rel,
+					 * and hence will not fail if it's been dropped entirely
+					 * --- we'll just transiently acquire a non-conflicting
+					 *  lock.
+					 */
+					LockRelationOid(rte->relid, rte->rellockmode);
+				}
+			}
+			else
+			{
+				int			rti;
+				Bitmapset  *lockrels;
+
+				/*
+				 * Walk the plan tree to find only the minimal set of
+				 * relations to be locked, considering the effect of performing
+				 * initial partition pruning.
+				 */
+				execlockrelsinfo = ExecutorGetLockRels(plannedstmt, boundParams);
+				lockrels = execlockrelsinfo->lockrels;
+
+				rti = -1;
+				while ((rti = bms_next_member(lockrels, rti)) >= 0)
+				{
+					RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
 
+					Assert(rte->rtekind == RTE_RELATION);
+
+					/* See the comment above. */
+					LockRelationOid(rte->relid, rte->rellockmode);
+				}
+			}
+		}
+
+		/*
+		 * Remember ExecLockRelsInfo for later adding to the QueryDesc that
+		 * will be passed to the executor when executing this plan.  May be
+		 * NULL, but must keep the list the same length as stmt_list.
+		 */
+		execlockrelsinfo_list = lappend(execlockrelsinfo_list,
+										execlockrelsinfo);
+	}
+
+	return execlockrelsinfo_list;
+}
+
+/*
+ * ReleaseExecutorLocks
+ * 		Release locks that would've been acquired by an earlier call to
+ * 		AcquireExecutorLocks()
+ */
+static void
+ReleaseExecutorLocks(List *stmt_list, List *execlockrelsinfo_list)
+{
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, stmt_list, lc2, execlockrelsinfo_list)
+	{
+		PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1);
+		ExecLockRelsInfo *execlockrelsinfo = lfirst_node(ExecLockRelsInfo, lc2);
+
+		if (plannedstmt->commandType == CMD_UTILITY)
+		{
 			/*
-			 * Acquire the appropriate type of lock on each relation OID. Note
-			 * that we don't actually try to open the rel, and hence will not
-			 * fail if it's been dropped entirely --- we'll just transiently
-			 * acquire a non-conflicting lock.
+			 * Ignore utility statements, except those (such as EXPLAIN) that
+			 * contain a parsed-but-not-planned query.  Note: it's okay to use
+			 * ScanQueryForLocks, even though the query hasn't been through
+			 * rule rewriting, because rewriting doesn't change the query
+			 * representation.
 			 */
-			if (acquire)
-				LockRelationOid(rte->relid, rte->rellockmode);
+			Query	   *query = UtilityContainsQuery(plannedstmt->utilityStmt);
+
+			if (query)
+				ScanQueryForLocks(query, false);
+		}
+		else
+		{
+			if (execlockrelsinfo == NULL)
+			{
+				ListCell *lc;
+
+				foreach(lc, plannedstmt->rtable)
+				{
+					RangeTblEntry *rte = lfirst(lc);
+
+					if (rte->rtekind != RTE_RELATION)
+						continue;
+
+					LockRelationOid(rte->relid, rte->rellockmode);
+				}
+			}
 			else
-				UnlockRelationOid(rte->relid, rte->rellockmode);
+			{
+				int			rti;
+				Bitmapset  *lockrels;
+
+				lockrels = execlockrelsinfo->lockrels;
+				rti = -1;
+				while ((rti = bms_next_member(lockrels, rti)) >= 0)
+				{
+					RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
+
+					Assert(rte->rtekind == RTE_RELATION);
+
+					UnlockRelationOid(rte->relid, rte->rellockmode);
+				}
+			}
 		}
 	}
 }
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index d549f66d4a..896f51be08 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -285,6 +285,7 @@ PortalDefineQuery(Portal portal,
 				  const char *sourceText,
 				  CommandTag commandTag,
 				  List *stmts,
+				  List *execlockrelsinfos,
 				  CachedPlan *cplan)
 {
 	AssertArg(PortalIsValid(portal));
@@ -299,6 +300,7 @@ PortalDefineQuery(Portal portal,
 	portal->qc.nprocessed = 0;
 	portal->commandTag = commandTag;
 	portal->stmts = stmts;
+	portal->execlockrelsinfos = execlockrelsinfos;
 	portal->cplan = cplan;
 	portal->status = PORTAL_DEFINED;
 }
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 666977fb1f..fef75ba147 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -87,7 +87,8 @@ extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
 							  ExplainState *es, const char *queryString,
 							  ParamListInfo params, QueryEnvironment *queryEnv);
 
-extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
+extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExecLockRelsInfo *execlockrelsinfo,
+						   IntoClause *into,
 						   ExplainState *es, const char *queryString,
 						   ParamListInfo params, QueryEnvironment *queryEnv,
 						   const instr_time *planduration,
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index fd5735a946..ded19b8cbb 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -124,4 +124,6 @@ extern PartitionPruneState *ExecInitPartitionPruning(PlanState *planstate,
 						 PartitionPruneInfo *pruneinfo,
 						 Bitmapset **initially_valid_subplans);
 extern Bitmapset *ExecFindMatchingSubPlans(PartitionPruneState *prunestate);
+extern Bitmapset *ExecGetLockRelsDoInitialPruning(Plan *plan, ExecGetLockRelsContext *context,
+								PartitionPruneInfo *pruneinfo);
 #endif							/* EXECPARTITION_H */
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index e79e2c001f..4338463479 100644
--- a/src/include/executor/execdesc.h
+++ b/src/include/executor/execdesc.h
@@ -35,6 +35,7 @@ typedef struct QueryDesc
 	/* These fields are provided by CreateQueryDesc */
 	CmdType		operation;		/* CMD_SELECT, CMD_UPDATE, etc. */
 	PlannedStmt *plannedstmt;	/* planner's output (could be utility, too) */
+	ExecLockRelsInfo *execlockrelsinfo;	/* ExecutorGetLockRels()'s output given plannedstmt */
 	const char *sourceText;		/* source text of the query */
 	Snapshot	snapshot;		/* snapshot to use for query */
 	Snapshot	crosscheck_snapshot;	/* crosscheck for RI update/delete */
@@ -57,6 +58,7 @@ typedef struct QueryDesc
 
 /* in pquery.c */
 extern QueryDesc *CreateQueryDesc(PlannedStmt *plannedstmt,
+								  ExecLockRelsInfo *execlockrelsinfo,
 								  const char *sourceText,
 								  Snapshot snapshot,
 								  Snapshot crosscheck_snapshot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 873772f188..d03bd5a026 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -185,6 +185,8 @@ ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull)
 /*
  * prototypes from functions in execMain.c
  */
+extern ExecLockRelsInfo *ExecutorGetLockRels(PlannedStmt *plannedstmt, ParamListInfo params);
+extern bool ExecGetLockRels(Plan *node, ExecGetLockRelsContext *context);
 extern void ExecutorStart(QueryDesc *queryDesc, int eflags);
 extern void standard_ExecutorStart(QueryDesc *queryDesc, int eflags);
 extern void ExecutorRun(QueryDesc *queryDesc,
diff --git a/src/include/executor/nodeAppend.h b/src/include/executor/nodeAppend.h
index 4cb78ee5b6..b53535c2a4 100644
--- a/src/include/executor/nodeAppend.h
+++ b/src/include/executor/nodeAppend.h
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern bool ExecGetAppendLockRels(Append *node, ExecGetLockRelsContext *context);
 extern AppendState *ExecInitAppend(Append *node, EState *estate, int eflags);
 extern void ExecEndAppend(AppendState *node);
 extern void ExecReScanAppend(AppendState *node);
diff --git a/src/include/executor/nodeMergeAppend.h b/src/include/executor/nodeMergeAppend.h
index 97fe3b0665..8eb4e9df93 100644
--- a/src/include/executor/nodeMergeAppend.h
+++ b/src/include/executor/nodeMergeAppend.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern bool ExecGetMergeAppendLockRels(MergeAppend *node, ExecGetLockRelsContext *context);
 extern MergeAppendState *ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags);
 extern void ExecEndMergeAppend(MergeAppendState *node);
 extern void ExecReScanMergeAppend(MergeAppendState *node);
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index c318681b9a..287baf6257 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -19,6 +19,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 									   EState *estate, TupleTableSlot *slot,
 									   CmdType cmdtype);
 
+extern bool ExecGetModifyTableLockRels(ModifyTable *plan, ExecGetLockRelsContext *context);
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cbbcff81d2..ee0c73e9a4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -596,6 +596,7 @@ typedef struct EState
 	struct ExecRowMark **es_rowmarks;	/* Array of per-range-table-entry
 										 * ExecRowMarks, or NULL if none */
 	PlannedStmt *es_plannedstmt;	/* link to top of plan tree */
+	struct ExecLockRelsInfo *es_execlockrelsinfo; /* QueryDesc.execlockrelsinfo */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
 	JunkFilter *es_junkFilter;	/* top-level junk filter, if any */
@@ -984,6 +985,101 @@ typedef struct DomainConstraintState
  */
 typedef TupleTableSlot *(*ExecProcNodeMtd) (struct PlanState *pstate);
 
+/*----------------
+ * ExecLockRelsInfo
+ *
+ * Result of performing ExecutorGetLockRels() for a given PlannedStmt
+ */
+typedef struct ExecLockRelsInfo
+{
+	NodeTag		type;
+
+	/*
+	 * Relations that must be locked to execute the plan tree contained in
+	 * the PlannedStmt.
+	 */
+	Bitmapset  *lockrels;
+
+	/* PlannedStmt.numPlanNodes */
+	int			numPlanNodes;
+
+	/*
+	 * List of PlanInitPruningOutput, each representing the output of
+	 * performing initial pruning on a given plan node, for all nodes in the
+	 * plan tree that have been marked as needing initial pruning.
+	 *
+	 * 'ipoIndexes' is an array of 'numPlanNodes' elements, indexed with
+	 * plan_node_id of the individual nodes in the plan tree, each a 1-based
+	 * index into 'initPruningOutputs' list for a given plan node.  0 means
+	 * that a given plan node has no entry in the list because of not needing
+	 * any initial pruning done on it.
+	 */
+	List	   *initPruningOutputs;
+	int		   *ipoIndexes;
+} ExecLockRelsInfo;
+
+/*----------------
+ * ExecGetLockRelsContext
+ *
+ * Information pertaining to ExecutorGetLockRels() invocation for a given
+ * plan.
+ */
+typedef struct ExecGetLockRelsContext
+{
+	NodeTag		type;
+
+	PlannedStmt	   *stmt;		/* target plan */
+	ParamListInfo	params;		/* EXTERN parameters available for pruning */
+
+	/* Output parameters for ExecGetLockRels and its subroutines. */
+	Bitmapset	   *lockrels;
+
+	/* See the omment in the definition of ExecLockRelsInfo struct. */
+	List		   *initPruningOutputs;
+	int			   *ipoIndexes;
+} ExecGetLockRelsContext;
+
+/*
+ * Appends the provided PlanInitPruningOutput to
+ * ExecGetLockRelsContext.initPruningOutput
+ */
+#define ExecStorePlanInitPruningOutput(cxt, initPruningOutput, plannode) \
+	do { \
+		(cxt)->initPruningOutputs = lappend((cxt)->initPruningOutputs, initPruningOutput); \
+		(cxt)->ipoIndexes[(plannode)->plan_node_id] = list_length((cxt)->initPruningOutputs); \
+	} while (0)
+
+/*
+ * Finds the PlanInitPruningOutput for a given Plan node in
+ * ExecLockRelsInfo.initPruningOutputs.
+ */
+#define ExecFetchPlanInitPruningOutput(execlockrelsinfo, plannode) \
+		(((execlockrelsinfo) != NULL && (execlockrelsinfo)->initPruningOutputs != NIL) ? \
+		 list_nth((execlockrelsinfo)->initPruningOutputs, \
+				  (execlockrelsinfo)->ipoIndexes[(plannode)->plan_node_id] - 1) : NULL)
+
+/* ---------------
+ * PlanInitPruningOutput
+ *
+ * Node to remember the result of performing initial partition pruning steps
+ * during ExecutorGetLockRels() on nodes that support pruning.
+ *
+ * ExecLockRelsDoInitPruning(), which runs during ExecutorGetLockRels(),
+ * creates it and stores it in the corresponding ExecLockRelsInfo.
+ *
+ * ExecInitPartitionPruning(), which runs during ExecuorStart(), fetches it
+ * from the EState's ExecLockRelsInfo (if any) and uses the value of
+ * initially_valid_subplans contained in it as-is to select the subplans to be
+ * initialized for execution, instead of re-evaluating that by performing
+ * initial pruning again.
+ */
+typedef struct PlanInitPruningOutput
+{
+	NodeTag		type;
+
+	Bitmapset  *initially_valid_subplans;
+} PlanInitPruningOutput;
+
 /* ----------------
  *		PlanState node
  *
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 53f6b05a3f..928a30c7c6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -97,6 +97,11 @@ typedef enum NodeTag
 	T_PartitionPruneStepCombine,
 	T_PlanInvalItem,
 
+	/* TAGS FOR EXECUTOR PREP NODES (execnodes.h) */
+	T_ExecGetLockRelsContext,
+	T_ExecLockRelsInfo,
+	T_PlanInitPruningOutput,
+
 	/*
 	 * TAGS FOR PLAN STATE NODES (execnodes.h)
 	 *
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index ef9b54739a..0ed171d3f5 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -129,6 +129,10 @@ typedef struct PlannerGlobal
 
 	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
 
+	bool		containsInitialPruning;	/* Do some Plan nodes in the tree
+										 * have initial (pre-exec) pruning
+										 * steps? */
+
 	PartitionDirectory partition_directory; /* partition descriptors */
 
 	Bitmapset *elidedAppendPartedRels;	/* Combined partitioned_rels of all
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index a823c7c20d..4fcba0e55c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -60,10 +60,16 @@ typedef struct PlannedStmt
 
 	bool		parallelModeNeeded; /* parallel mode required to execute? */
 
+	bool		containsInitialPruning;	/* Do some Plan nodes in the tree
+										 * have initial (pre-exec) pruning
+										 * steps? */
+
 	int			jitFlags;		/* which forms of JIT should be performed */
 
 	struct Plan *planTree;		/* tree of Plan nodes */
 
+	int			numPlanNodes;	/* number of nodes in planTree */
+
 	List	   *rtable;			/* list of RangeTblEntry nodes */
 
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
@@ -1192,6 +1198,13 @@ typedef struct PlanRowMark
  * prune_infos			List of Lists containing PartitionedRelPruneInfo nodes,
  *						one sublist per run-time-prunable partition hierarchy
  *						appearing in the parent plan node's subplans.
+ *
+ * needs_init_pruning	Does any of the PartitionedRelPruneInfos in
+ *						prune_infos have its initial_pruning_steps set?
+ *
+ * needs_exec_pruning	Does any of the PartitionedRelPruneInfos in
+ *						prune_infos have its exec_pruning_steps set?
+ *
  * other_subplans		Indexes of any subplans that are not accounted for
  *						by any of the PartitionedRelPruneInfo nodes in
  *						"prune_infos".  These subplans must not be pruned.
@@ -1200,6 +1213,8 @@ typedef struct PartitionPruneInfo
 {
 	NodeTag		type;
 	List	   *prune_infos;
+	bool		needs_init_pruning;
+	bool		needs_exec_pruning;
 	Bitmapset  *other_subplans;
 } PartitionPruneInfo;
 
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 92291a750d..bf80c53bed 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -64,7 +64,7 @@ extern PlannedStmt *pg_plan_query(Query *querytree, const char *query_string,
 								  ParamListInfo boundParams);
 extern List *pg_plan_queries(List *querytrees, const char *query_string,
 							 int cursorOptions,
-							 ParamListInfo boundParams);
+							 ParamListInfo boundParams, List **execlockrelsinfo_list);
 
 extern bool check_max_stack_depth(int *newval, void **extra, GucSource source);
 extern void assign_max_stack_depth(int newval, void *extra);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 95b99e3d25..56b0dcc6bd 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -148,6 +148,9 @@ typedef struct CachedPlan
 {
 	int			magic;			/* should equal CACHEDPLAN_MAGIC */
 	List	   *stmt_list;		/* list of PlannedStmts */
+	List	   *execlockrelsinfo_list;	/* list of ExecutorGetLockRelsResult with one
+									 * element for each of stmt_list; NIL
+									 * if not a generic plan */
 	bool		is_oneshot;		/* is it a "oneshot" plan? */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
@@ -158,6 +161,9 @@ typedef struct CachedPlan
 	int			generation;		/* parent's generation number for this plan */
 	int			refcount;		/* count of live references to this struct */
 	MemoryContext context;		/* context containing this CachedPlan */
+	MemoryContext execlockrelsinfo_context;	/* context containing
+											 * execlockrelsinfo_list,
+											 * a child of the above context */
 } CachedPlan;
 
 /*
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index aeddbdafe5..9abace6734 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -137,6 +137,10 @@ typedef struct PortalData
 	CommandTag	commandTag;		/* command tag for original query */
 	QueryCompletion qc;			/* command completion data for executed query */
 	List	   *stmts;			/* list of PlannedStmts */
+	List	   *execlockrelsinfos;	/* list of ExecutorGetLockRelsResults with one element
+								 * for each of 'stmts'; same as
+								 * cplan->execlockrelsinfo_list if cplan is
+								 * not NULL */
 	CachedPlan *cplan;			/* CachedPlan, if stmts are from one */
 
 	ParamListInfo portalParams; /* params to pass to query */
@@ -241,6 +245,7 @@ extern void PortalDefineQuery(Portal portal,
 							  const char *sourceText,
 							  CommandTag commandTag,
 							  List *stmts,
+							  List *execlockrelsinfos,
 							  CachedPlan *cplan);
 extern PlannedStmt *PortalGetPrimaryStmt(Portal portal);
 extern void PortalCreateHoldStore(Portal portal);
-- 
2.24.1

