From 7d29fea0fcf8e6aec2877804555dd0239fdaf1be Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Wed, 22 Dec 2021 16:55:17 +0900
Subject: [PATCH v4] Invent a new executor "prep" phase

The new phase, implemented by execMain.c:ExecutorPrep() and its
recursive underling execProcnode.c:ExecPrepNode(), takes a query's
PlannedStmt and processes the plan tree contained in it to produce
a ExecPrepOutput node as result.

As the plan tree is walked, each node must add the RT index(es) of
any relation(s) that it directly manipulates to a bitmapset member of
ExecPrepOutput (for example, an IndexScan node must add the Scan's
scanrelid).  Also, each node may want to make a PlanPrepOutput node
containing additional information that may be of interest to the
calling module or to the later execution phases, if the node can
provide one (for example, an Append node may perform initial pruning
and add a set of "initially valid subplans" to the PlanPrepOutput).
The PlanPrepOutput nodess of all the plan nodes are added to an array
in the ExecPrepOutput, which is indexed using the individual nodes'
plan_node_id; a NULL is stored in the array slots of nodes that
don't have anything interesting to add to the PlanPrepOutput.

The ExecPrepOutput thus produced is passed to CreateQueryDesc()
and subsequently to ExecutorStart() via QueryDesc, which then makes
it available to the executor routines via the query's EState.

The main goal of adding this new phase is, for now, to allow cached
cached generic plans containing scans of partitioned tables using
Append/MergeAppend to be executed more efficiently by the prep phase
doing any initial pruning, instead of deferring that to
ExecutorStart().  That may allow AcquireExecutorLocks() on the plan
to lock only only the minimal set of relations/partitions, that is
those whose subplans survive the initial pruning.

Implementation notes:

* To allow initial pruning to be done as part of the pre-execution
prep phase as opposed to as part of ExecutorStart(), this refactors
ExecCreatePartitionPruneState() and ExecFindInitialMatchingSubPlans()
to pass the information needed to do initial pruning directly as
parameters instead of getting that from the EState and the PlanState
of the parent Append/MergeAppend, both of which would not be
available in ExecutorPrep().  Another, sort of non-essential-to-this-
goal, refactoring this does is moving the partition pruning
initialization stanzas in ExecInitAppend() and ExecInitMergeAppend()
both of which contain the same cod into its own function
ExecInitPartitionPruning().

* To pass the ExecPrepOutput(s) created by the plancache module's
invocation of ExecutorPrep() to the callers of the module, which in
turn would pass them down to ExecutorStart(), CachedPlan gets a new
List field that stores those ExecPrepOutputs, containing one element
for each PlannedStmt also contained in the CachedPlan.  The new list
is stored in a child context of the context containing the
PlannedStmts, though unlike the latter, it is reset on every
invocation of CheckCachedPlan(), which in turn calls ExecutorPrep()
with a new set of bound Params.

* AcquireExecutorLocks() is now made to loop over a bitmapset of RT
indexes, those of relations returned in ExecPrepOutput, instead of
over the whole range table.  With initial pruning that is also done
as part of ExcecutorPrep(), only relations from non-pruned nodes of
the plan tree would get locked as a result of this new arrangement.

* PlannedStmt gets a new field usesPrepExecPruning that indicates
whether any of the nodes of the plan tree contain "initial" (or
"pre-execution") pruning steps, which saves ExecutorPrep() the
trouble of walking the plan tree only to find out whether that's
the case.

* PartitionPruneInfo nodes now explicitly stores whether the steps
contained in any of the individual PartitionedRelPruneInfos embedded
in it contain initial pruning steps (those that can be performed
during ExecutorPrep) and execution pruning steps (those that can only
be performed during ExecutorRun), as flags contains_initial_steps and
contains_exec_steps, respectively.  In fact, the aforementioned
PlannedStmt field's value is a logical OR of the values of the former
across all PartitionPruneInfo nodes embedded in the plan tree.

* PlannedStmt also gets a bitmapset field to store the RT indexes of
all relation RTEs referenced in the query that is populated when
contructing the flat range table in setrefs.c, which effectively
contains all the relations that the planner must have locked. In the
case of a cached plan, AcquireExecutorLocks() must lock all of those
relations, except those whose subnodes get pruned as result of
ExecutorPrep().

* PlannedStmt gets yet another field numPlanNodes that records the
highest plan_node_id assigned to any of the node contained in the
tree, which serves as the size to use when allocating the
PlanPrepOutput array.
---
 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                   |  18 +
 src/backend/executor/execMain.c               |  48 ++
 src/backend/executor/execParallel.c           |   4 +-
 src/backend/executor/execPartition.c          | 538 +++++++++++++-----
 src/backend/executor/execProcnode.c           | 206 +++++++
 src/backend/executor/execUtils.c              |   8 +
 src/backend/executor/functions.c              |   2 +-
 src/backend/executor/nodeAgg.c                |  13 +
 src/backend/executor/nodeAppend.c             |  91 ++-
 src/backend/executor/nodeBitmapAnd.c          |  18 +
 src/backend/executor/nodeBitmapHeapscan.c     |  14 +
 src/backend/executor/nodeBitmapIndexscan.c    |  14 +
 src/backend/executor/nodeBitmapOr.c           |  18 +
 src/backend/executor/nodeCtescan.c            |  12 +
 src/backend/executor/nodeCustom.c             |  18 +
 src/backend/executor/nodeForeignscan.c        |  12 +
 src/backend/executor/nodeFunctionscan.c       |  13 +
 src/backend/executor/nodeGather.c             |  13 +
 src/backend/executor/nodeGatherMerge.c        |  13 +
 src/backend/executor/nodeGroup.c              |  13 +
 src/backend/executor/nodeHash.c               |  13 +
 src/backend/executor/nodeHashjoin.c           |  14 +
 src/backend/executor/nodeIncrementalSort.c    |  14 +
 src/backend/executor/nodeIndexonlyscan.c      |  14 +
 src/backend/executor/nodeIndexscan.c          |  14 +
 src/backend/executor/nodeLimit.c              |  13 +
 src/backend/executor/nodeLockRows.c           |  13 +
 src/backend/executor/nodeMaterial.c           |  13 +
 src/backend/executor/nodeMemoize.c            |  13 +
 src/backend/executor/nodeMergeAppend.c        |  90 ++-
 src/backend/executor/nodeMergejoin.c          |  14 +
 src/backend/executor/nodeModifyTable.c        |  26 +
 .../executor/nodeNamedtuplestorescan.c        |  13 +
 src/backend/executor/nodeNestloop.c           |  14 +
 src/backend/executor/nodeProjectSet.c         |  13 +
 src/backend/executor/nodeRecursiveunion.c     |  14 +
 src/backend/executor/nodeResult.c             |  13 +
 src/backend/executor/nodeSamplescan.c         |  14 +
 src/backend/executor/nodeSeqscan.c            |  13 +
 src/backend/executor/nodeSetOp.c              |  13 +
 src/backend/executor/nodeSort.c               |  13 +
 src/backend/executor/nodeSubplan.c            |  12 +
 src/backend/executor/nodeSubqueryscan.c       |  14 +
 src/backend/executor/nodeTableFuncscan.c      |  13 +
 src/backend/executor/nodeTidrangescan.c       |  14 +
 src/backend/executor/nodeTidscan.c            |  15 +-
 src/backend/executor/nodeUnique.c             |  13 +
 src/backend/executor/nodeValuesscan.c         |  13 +
 src/backend/executor/nodeWindowAgg.c          |  13 +
 src/backend/executor/nodeWorktablescan.c      |  12 +
 src/backend/executor/spi.c                    |  14 +-
 src/backend/nodes/copyfuncs.c                 |  49 ++
 src/backend/nodes/outfuncs.c                  |   6 +
 src/backend/nodes/readfuncs.c                 |   5 +
 src/backend/optimizer/plan/planner.c          |   3 +
 src/backend/optimizer/plan/setrefs.c          |  10 +
 src/backend/partitioning/partprune.c          |  57 +-
 src/backend/tcop/postgres.c                   |  15 +-
 src/backend/tcop/pquery.c                     |  21 +-
 src/backend/utils/cache/plancache.c           | 155 +++--
 src/backend/utils/mmgr/portalmem.c            |   2 +
 src/include/commands/explain.h                |   3 +-
 src/include/executor/execPartition.h          |  19 +-
 src/include/executor/execdesc.h               |   2 +
 src/include/executor/executor.h               |   3 +
 src/include/executor/nodeAgg.h                |   1 +
 src/include/executor/nodeAppend.h             |   1 +
 src/include/executor/nodeBitmapAnd.h          |   1 +
 src/include/executor/nodeBitmapHeapscan.h     |   1 +
 src/include/executor/nodeBitmapIndexscan.h    |   1 +
 src/include/executor/nodeBitmapOr.h           |   1 +
 src/include/executor/nodeCtescan.h            |   1 +
 src/include/executor/nodeCustom.h             |   1 +
 src/include/executor/nodeForeignscan.h        |   1 +
 src/include/executor/nodeFunctionscan.h       |   1 +
 src/include/executor/nodeGather.h             |   1 +
 src/include/executor/nodeGatherMerge.h        |   1 +
 src/include/executor/nodeGroup.h              |   1 +
 src/include/executor/nodeHash.h               |   1 +
 src/include/executor/nodeHashjoin.h           |   1 +
 src/include/executor/nodeIncrementalSort.h    |   1 +
 src/include/executor/nodeIndexonlyscan.h      |   1 +
 src/include/executor/nodeIndexscan.h          |   1 +
 src/include/executor/nodeLimit.h              |   1 +
 src/include/executor/nodeLockRows.h           |   1 +
 src/include/executor/nodeMaterial.h           |   1 +
 src/include/executor/nodeMemoize.h            |   1 +
 src/include/executor/nodeMergeAppend.h        |   1 +
 src/include/executor/nodeMergejoin.h          |   1 +
 src/include/executor/nodeModifyTable.h        |   1 +
 .../executor/nodeNamedtuplestorescan.h        |   1 +
 src/include/executor/nodeNestloop.h           |   1 +
 src/include/executor/nodeProjectSet.h         |   1 +
 src/include/executor/nodeRecursiveunion.h     |   1 +
 src/include/executor/nodeResult.h             |   2 +
 src/include/executor/nodeSamplescan.h         |   1 +
 src/include/executor/nodeSeqscan.h            |   1 +
 src/include/executor/nodeSetOp.h              |   1 +
 src/include/executor/nodeSort.h               |   1 +
 src/include/executor/nodeSubplan.h            |   1 +
 src/include/executor/nodeSubqueryscan.h       |   1 +
 src/include/executor/nodeTableFuncscan.h      |   1 +
 src/include/executor/nodeTidrangescan.h       |   1 +
 src/include/executor/nodeTidscan.h            |   1 +
 src/include/executor/nodeUnique.h             |   1 +
 src/include/executor/nodeValuesscan.h         |   1 +
 src/include/executor/nodeWindowAgg.h          |   1 +
 src/include/executor/nodeWorktablescan.h      |   1 +
 src/include/nodes/execnodes.h                 |  78 +++
 src/include/nodes/nodeFuncs.h                 |   3 +
 src/include/nodes/nodes.h                     |   5 +
 src/include/nodes/pathnodes.h                 |   6 +
 src/include/nodes/plannodes.h                 |  17 +
 src/include/partitioning/partprune.h          |   2 +
 src/include/tcop/tcopprot.h                   |   2 +-
 src/include/utils/plancache.h                 |   5 +
 src/include/utils/portal.h                    |   5 +
 124 files changed, 1866 insertions(+), 285 deletions(-)

diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 3283ef50d0..bb7d5e65ea 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 b970997c34..9ee82824a1 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, ExecPrepOutput *execprep,
+			   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, execprep, queryString,
 								GetActiveSnapshot(), InvalidSnapshot,
 								dest, params, queryEnv, instrument_option);
 
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index a2e77c418a..214a345aa2 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,
+				   *stmt_execprep_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,
+									&stmt_execprep_list);
 
-		foreach(lc2, stmt_list)
+		forboth(lc2, stmt_list, lc3, stmt_execprep_list)
 		{
 			PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2);
+			ExecPrepOutput *execprep = lfirst_node(ExecPrepOutput, lc3);
 
 			CommandCounterIncrement();
 
@@ -777,6 +781,7 @@ execute_sql_string(const char *sql)
 				QueryDesc  *qdesc;
 
 				qdesc = CreateQueryDesc(stmt,
+										execprep,
 										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..0bea2dd18f 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 ExecPrepOutput to pass */
 					  NULL);
 
 	/*----------
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 206d2bbbf9..ac188a7347 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -189,6 +189,7 @@ ExecuteQuery(ParseState *pstate,
 	PreparedStatement *entry;
 	CachedPlan *cplan;
 	List	   *plan_list;
+	List	   *plan_execprep_list;
 	ParamListInfo paramLI = NULL;
 	EState	   *estate = NULL;
 	Portal		portal;
@@ -229,6 +230,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_execprep_list = cplan->stmt_execprep_list;
 
 	/*
 	 * DO NOT add any logic that could possibly throw an error between
@@ -238,7 +240,7 @@ ExecuteQuery(ParseState *pstate,
 					  NULL,
 					  query_string,
 					  entry->plansource->commandTag,
-					  plan_list,
+					  plan_list, plan_execprep_list,
 					  cplan);
 
 	/*
@@ -610,7 +612,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	const char *query_string;
 	CachedPlan *cplan;
 	List	   *plan_list;
-	ListCell   *p;
+	List	   *plan_execprep_list;
+	ListCell   *p,
+			   *pe;
 	ParamListInfo paramLI = NULL;
 	EState	   *estate = NULL;
 	instr_time	planstart;
@@ -666,15 +670,18 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 	}
 
 	plan_list = cplan->stmt_list;
+	plan_execprep_list = cplan->stmt_execprep_list;
 
 	/* Explain each query */
-	foreach(p, plan_list)
+	forboth(p, plan_list, pe, plan_execprep_list)
 	{
 		PlannedStmt *pstmt = lfirst_node(PlannedStmt, p);
+		ExecPrepOutput *execprep = lfirst_node(ExecPrepOutput, pe);
 
 		if (pstmt->commandType != CMD_UTILITY)
-			ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv,
-						   &planduration, (es->buffers ? &bufusage : NULL));
+			ExplainOnePlan(pstmt, execprep, 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 bf5e70860d..c25db66ff0 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -65,6 +65,21 @@ 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.
 
+A plan tree may also be made to go through ExecutorPrep() to collect some
+information about the individual plan nodes that may help optimize the
+actual execution of the plan.  Such information about each plan node is put
+into a PlanPrepOutput node if the plan node type supports producing one and
+stored in an array in ExecPrepOutput that in turn represents the output of
+a ExecutorPrep() run.  The PlanPrepOutput array is indexed with plan_node_id
+of the individual plan nodes.  An example of what such information may look
+like is in the "prep" routine of the Append node (ExecPrepAppend), which does
+partition pruning using "initial steps", that is, pruning with expressions
+that can evaluated even before the actual execution has started. That produces
+a set of "initially valid subplans" that is put into the PlanPrepOutput
+belonging to Append that can be used as-is by the initializer routine of the
+Append node (nodeAppend.c: ExecInitAppend) to only initialize the plan state
+trees of those subplans.
+
 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
@@ -247,6 +262,9 @@ Query Processing Control Flow
 
 This is a sketch of control flow for full query processing:
 
+  [ ExecutorPrep ] --- an optional step to walk over the plan tree to produce
+		an ExecPrepOutput to be passed to CreateQueryDesc
+
 	CreateQueryDesc
 
 	ExecutorStart
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 549d9eb696..e38966295e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -103,6 +103,52 @@ static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree);
 
 /* end of local decls */
 
+/* ----------------------------------------------------------------
+ *		ExecutorPrep
+ *
+ *		This optional executor routine must be called if the PlannedStmt
+ *		indicates that some nodes in the planTree can perform preparatory
+ *		actions, such as pre-execution/initial pruning
+ *
+ * Returned information includes the set of RT indexes of relations referenced
+ * in the plan, and a PlanPrepOutput node for each node in the planTree if the
+ * node type supports producing one.
+ *
+ * This may lock relations whose information may be used to produce the
+ * PlanPrepOutput nodes. For example, a partitioned table before perusing its
+ * PartitionPruneInfo contained in an Append node to do the pruning the result
+ * of which is used to populate the Append node's PlanPrepOutput.
+ */
+ExecPrepOutput *
+ExecutorPrep(ExecPrepContext *context)
+{
+	ExecPrepOutput *result = makeNode(ExecPrepOutput);
+
+	result->numPlanNodes = context->stmt->numPlanNodes;
+	result->planPrepResults = palloc0(sizeof(PlanPrepOutput *) *
+									  result->numPlanNodes);
+	if (!context->stmt->usesPreExecPruning)
+	{
+		/* Shortcut */
+		result->relationRTIs = bms_copy(context->stmt->relationRTIs);
+	}
+	else
+	{
+		/* Go find the nodes that need any "prep" work done. */
+		ListCell *lc;
+
+		foreach(lc, context->stmt->subplans)
+		{
+			Plan *subplan = lfirst(lc);
+
+			ExecPrepNode(subplan, context, result);
+		}
+
+		ExecPrepNode(context->stmt->planTree, context, result);
+	}
+
+	return result;
+}
 
 /* ----------------------------------------------------------------
  *		ExecutorStart
@@ -804,6 +850,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 {
 	CmdType		operation = queryDesc->operation;
 	PlannedStmt *plannedstmt = queryDesc->plannedstmt;
+	ExecPrepOutput *execprep = queryDesc->execprep;
 	Plan	   *plan = plannedstmt->planTree;
 	List	   *rangeTable = plannedstmt->rtable;
 	EState	   *estate = queryDesc->estate;
@@ -823,6 +870,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	ExecInitRangeTable(estate, rangeTable);
 
 	estate->es_plannedstmt = plannedstmt;
+	estate->es_execprep = execprep;
 
 	/*
 	 * 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 5dd8ab7db2..0567534358 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -182,8 +182,10 @@ ExecSerializePlan(Plan *plan, EState *estate)
 	pstmt->transientPlan = false;
 	pstmt->dependsOnRole = false;
 	pstmt->parallelModeNeeded = false;
+	pstmt->usesPreExecPruning = false;
 	pstmt->planTree = plan;
 	pstmt->rtable = estate->es_range_table;
+	pstmt->relationRTIs = NULL;
 	pstmt->resultRelations = NIL;
 	pstmt->appendRelations = NIL;
 
@@ -1248,7 +1250,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver,
 	paramLI = RestoreParamList(&paramspace);
 
 	/* Create a QueryDesc for the query. */
-	return CreateQueryDesc(pstmt,
+	return CreateQueryDesc(pstmt, NULL,	/* XXX pass ExecPrepOutput too? */
 						   queryString,
 						   GetActiveSnapshot(), InvalidSnapshot,
 						   receiver, paramLI, NULL, instrument_options);
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 90ed1485d1..75292fbd21 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -24,6 +24,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"
@@ -186,7 +187,11 @@ static void ExecInitPruningContext(PartitionPruneContext *context,
 								   List *pruning_steps,
 								   PartitionDesc partdesc,
 								   PartitionKey partkey,
-								   PlanState *planstate);
+								   PlanState *planstate,
+								   ExprContext *econtext);
+static void ExecPartitionPruneFixSubPlanIndexes(PartitionPruneState *prunestate,
+									Bitmapset *initially_valid_subplans,
+									int n_total_subplans);
 static void find_matching_subplans_recurse(PartitionPruningData *prunedata,
 										   PartitionedRelPruningData *pprune,
 										   bool initial_prune,
@@ -1476,8 +1481,9 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
  * 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 ExecutorPrep().
+ * 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.
@@ -1485,10 +1491,28 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
  *
  * Functions:
  *
+ * ExecInitPartitionPruning:
+ *		This determines the initially valid subplans by doing pruning with
+ *		only pre-execution pruning expressions, that is, expressions in the
+ *		query that were matched to the partition key(s), whose values are
+ *		known at executor startup (excludeing expressions containing
+ *		PARAM_EXEC Params); see ExecFindInitialMatchingSubPlans().  The
+ *		PartitionPruneState thus created, which stores the details about
+ *		mapping the partition indexes returned by the partition pruning code
+ *		into subplan indexes, is also returned for use during subsquent
+ *		pruning.  Pruned subplans must be removed from the parent plan's list
+ *		of subplans to be executed, so this also remaps the partition indexes
+ *		in the PartitionPruneState to the new indexes of surviving subplans.
+ *
+ * ExecPrepDoInitialPruning:
+ * 		Do ExecFindInitialMatchingSubPlans as part of ExecPrepNode() on the
+ * 		parent plan node
+ *
  * ExecCreatePartitionPruneState:
  *		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.
+ *		(Note: Use ExecInitPartitionPruning() rather than use this directly.)
  *
  * ExecFindInitialMatchingSubPlans:
  *		Returns indexes of matching subplans.  Partition pruning is attempted
@@ -1500,6 +1524,7 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
  *		remap of the partition index to subplan index map and the newly
  *		created map provides indexes only for subplans which remain after
  *		calling this function.
+ *		(Note: Use ExecInitPartitionPruning() rather than use this directly.)
  *
  * ExecFindMatchingSubPlans:
  *		Returns indexes of matching subplans after evaluating all available
@@ -1514,7 +1539,9 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
  *		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', 'econtext', and 'partdir' must be provided.
  *
  * 'partitionpruneinfo' is a PartitionPruneInfo as generated by
  * make_partition_pruneinfo.  Here we build a PartitionPruneState containing a
@@ -1529,18 +1556,20 @@ adjust_partition_colnos(List *colnos, ResultRelInfo *leaf_part_rri)
  */
 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;
 
-	/* 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(partdir != NULL && econtext != NULL &&
+		   (estate != NULL || rtable != NIL));
 
 	n_part_hierarchies = list_length(partitionpruneinfo->prune_infos);
 	Assert(n_part_hierarchies > 0);
@@ -1591,19 +1620,34 @@ 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.
+			 * 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);
+			if (estate == NULL)
+			{
+				RangeTblEntry *rte = rt_fetch(pinfo->rtindex, rtable);
+
+				partrel = table_open(rte->relid, rte->rellockmode);
+				close_partrel = true;
+			}
+			else
+				partrel = ExecGetRangeTableRelation(estate, pinfo->rtindex);
+
 			partkey = RelationGetPartitionKey(partrel);
-			partdesc = PartitionDirectoryLookup(estate->es_partition_directory,
-												partrel);
+			partdesc = PartitionDirectoryLookup(partdir, partrel);
+
+			/* Safe to close partrel, if necessary, keeping the lock taken. */
+			if (close_partrel)
+				table_close(partrel, NoLock);
 
 			/*
 			 * Initialize the subplan_map and subpart_map.
@@ -1705,30 +1749,32 @@ 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,
-									   partdesc, partkey, planstate);
+									   partdesc, partkey, planstate,
+									   econtext);
 				/* Record whether initial pruning is needed at any level */
 				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,
-									   partdesc, partkey, planstate);
+									   partdesc, partkey, planstate,
+									   econtext);
 				/* Record whether exec pruning is needed at any level */
 				prunestate->do_exec_prune = true;
-			}
 
-			/*
-			 * Accumulate the IDs of all PARAM_EXEC Params affecting the
-			 * partitioning decisions at this plan node.
-			 */
-			prunestate->execparamids = bms_add_members(prunestate->execparamids,
-													   pinfo->execparamids);
+				/*
+				 * Accumulate the IDs of all PARAM_EXEC Params affecting the
+				 * partitioning decisions at this plan node.
+				 */
+				prunestate->execparamids = bms_add_members(prunestate->execparamids,
+														   pinfo->execparamids);
+			}
 
 			j++;
 		}
@@ -1740,13 +1786,18 @@ ExecCreatePartitionPruneState(PlanState *planstate,
 
 /*
  * Initialize a PartitionPruneContext for the given list of pruning steps.
+ *
+ * At least one of 'planstate' or 'econtext' must be passed to be able to
+ * successfully evaluate any non-Const expressions contained in the
+ * steps.
  */
 static void
 ExecInitPruningContext(PartitionPruneContext *context,
 					   List *pruning_steps,
 					   PartitionDesc partdesc,
 					   PartitionKey partkey,
-					   PlanState *planstate)
+					   PlanState *planstate,
+					   ExprContext *econtext)
 {
 	int			n_steps;
 	int			partnatts;
@@ -1767,6 +1818,7 @@ ExecInitPruningContext(PartitionPruneContext *context,
 
 	context->ppccontext = CurrentMemoryContext;
 	context->planstate = planstate;
+	context->exprcontext = econtext;
 
 	/* Initialize expression state for each expression we need */
 	context->exprstates = (ExprState **)
@@ -1795,14 +1847,269 @@ ExecInitPruningContext(PartitionPruneContext *context,
 														step->step.step_id,
 														keyno);
 
-				context->exprstates[stateidx] =
-					ExecInitExpr(expr, context->planstate);
+				if (planstate == NULL)
+					context->exprstates[stateidx] =
+						ExecInitExprWithParams(expr,
+											   econtext->ecxt_param_list_info);
+				else
+					context->exprstates[stateidx] =
+						ExecInitExpr(expr, context->planstate);
 			}
 			keyno++;
 		}
 	}
 }
 
+Bitmapset *
+ExecInitPartitionPruning(PlanState *planstate, int n_total_subplans,
+						 PartitionPruneInfo *pruneinfo,
+						 PartitionPruneState **prunestate)
+{
+	Bitmapset *validsubplans;
+	Plan   *plan = planstate->plan;
+	EState *estate = planstate->state;
+	PlanPrepOutput *planPrepResult = NULL;
+	bool	do_pruning = (pruneinfo->contains_init_steps ||
+						  pruneinfo->contains_exec_steps);
+
+	*prunestate = NULL;
+	if (estate->es_execprep)
+	{
+		planPrepResult = ExecPrepFetchPlanPrepOutput(estate->es_execprep,
+													 plan);
+
+		Assert(planPrepResult != NULL);
+		/* No need to do initial pruning again, only exec pruning. */
+		do_pruning = pruneinfo->contains_exec_steps;
+	}
+
+	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 PlanPrepOutput.
+		 */
+		*prunestate = ExecCreatePartitionPruneState(planstate, pruneinfo,
+													planPrepResult == NULL, true,
+													NIL, planstate->ps_ExprContext,
+													estate->es_partition_directory);
+	}
+
+	/*
+	 * Perform an initial partition prune, if required.
+	 */
+	if (planPrepResult)
+	{
+		/* ExecutorPrep() already did it for us! */
+		validsubplans = planPrepResult->initially_valid_subnodes;
+	}
+	else if (*prunestate && (*prunestate)->do_initial_prune)
+	{
+		/* Determine which subplans survive initial pruning */
+		validsubplans = ExecFindInitialMatchingSubPlans(*prunestate, pruneinfo,
+														NULL);
+	}
+	else
+	{
+		/* We'll need to initialize all subplans */
+		Assert(n_total_subplans > 0);
+		validsubplans = bms_add_range(NULL, 0, n_total_subplans - 1);
+	}
+
+	/*
+	 * If exec-time pruning is required and subplans are pruned by initial
+	 * pruning, then we must re-sequence the subplan indexes so that
+	 * ExecFindMatchingSubPlans properly returns the indexes from the
+	 * subplans which will remain after initial pruning.
+	 *
+	 * We can safely skip this when !do_exec_prune, even though that leaves
+	 * invalid data in prunestate, because that data won't be consulted again
+	 * (cf initial Assert in ExecFindMatchingSubPlans).
+	 */
+	if (*prunestate && (*prunestate)->do_exec_prune &&
+		bms_num_members(validsubplans) < n_total_subplans)
+		ExecPartitionPruneFixSubPlanIndexes(*prunestate, validsubplans,
+											n_total_subplans);
+
+	return validsubplans;
+}
+
+/*
+ * ExecPrepDoInitialPruning
+ *		Perform initial pruning as part of doing ExecPrepNode() on the parent
+ *		plan node
+ */
+Bitmapset *
+ExecPrepDoInitialPruning(PartitionPruneInfo *pruneinfo,
+						 List *rtable, ParamListInfo params,
+						 Bitmapset **parentrelids)
+{
+	ExprContext	 *econtext;
+	PartitionDirectory pdir;
+	MemoryContext oldcontext,
+				  tmpcontext;
+	PartitionPruneState *prunestate;
+	Bitmapset	 *validsubplans;
+
+	/*
+	 * 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);
+
+	/* An ExprContext to evaluate expressions. */
+	econtext = CreateStandaloneExprContext();
+	econtext->ecxt_param_list_info = params;
+
+	/*
+	 * PartitionDirectory, to look up partition descriptors
+	 * Omits detached partitions, just like in the executor
+	 * proper.
+	 */
+	pdir = CreatePartitionDirectory(CurrentMemoryContext, false);
+	prunestate = ExecCreatePartitionPruneState(NULL, pruneinfo,
+											   true, false,
+											   rtable, econtext,
+											   pdir);
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Do the "initial" pruning. */
+	validsubplans =
+		ExecFindInitialMatchingSubPlans(prunestate,
+										pruneinfo,
+										parentrelids);
+
+	FreeExprContext(econtext, true);
+	DestroyPartitionDirectory(pdir);
+	MemoryContextDelete(tmpcontext);
+
+	return validsubplans;
+}
+
+/*
+ * ExecPartitionPruneFixSubPlanIndexes
+ *		Fix mapping of partition indexes to subplan indexes contained in
+ *		prunestate by considering the new list of subplans that survived
+ *		initial pruning
+ *
+ * Subplans would be previously indexed 0..(n_total_subplans - 1), though
+ * now should be changed to index range 0..num(initially_valid_subplans).
+ */
+static void
+ExecPartitionPruneFixSubPlanIndexes(PartitionPruneState *prunestate,
+									Bitmapset *initially_valid_subplans,
+									int n_total_subplans)
+{
+	int		   *new_subplan_indexes;
+	Bitmapset  *new_other_subplans;
+	int			i;
+	int			newidx;
+
+	/*
+	 * First we must build a temporary array which maps old subplan
+	 * indexes to new ones.  For convenience of initialization, we use
+	 * 1-based indexes in this array and leave pruned items as 0.
+	 */
+	new_subplan_indexes = (int *) palloc0(sizeof(int) * n_total_subplans);
+	newidx = 1;
+	i = -1;
+	while ((i = bms_next_member(initially_valid_subplans, i)) >= 0)
+	{
+		Assert(i < n_total_subplans);
+		new_subplan_indexes[i] = newidx++;
+	}
+
+	/*
+	 * Now we can update each PartitionedRelPruneInfo's subplan_map with
+	 * new subplan indexes.  We must also recompute its present_parts
+	 * bitmap.
+	 */
+	for (i = 0; i < prunestate->num_partprunedata; i++)
+	{
+		PartitionPruningData *prunedata = prunestate->partprunedata[i];
+		int			j;
+
+		/*
+		 * Within each hierarchy, we perform this loop in back-to-front
+		 * order so that we determine present_parts for the lowest-level
+		 * partitioned tables first.  This way we can tell whether a
+		 * sub-partitioned table's partitions were entirely pruned so we
+		 * can exclude it from the current level's present_parts.
+		 */
+		for (j = prunedata->num_partrelprunedata - 1; j >= 0; j--)
+		{
+			PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
+			int			nparts = pprune->nparts;
+			int			k;
+
+			/* We just rebuild present_parts from scratch */
+			bms_free(pprune->present_parts);
+			pprune->present_parts = NULL;
+
+			for (k = 0; k < nparts; k++)
+			{
+				int			oldidx = pprune->subplan_map[k];
+				int			subidx;
+
+				/*
+				 * If this partition existed as a subplan then change the
+				 * old subplan index to the new subplan index.  The new
+				 * index may become -1 if the partition was pruned above,
+				 * or it may just come earlier in the subplan list due to
+				 * some subplans being removed earlier in the list.  If
+				 * it's a subpartition, add it to present_parts unless
+				 * it's entirely pruned.
+				 */
+				if (oldidx >= 0)
+				{
+					Assert(oldidx < n_total_subplans);
+					pprune->subplan_map[k] = new_subplan_indexes[oldidx] - 1;
+
+					if (new_subplan_indexes[oldidx] > 0)
+						pprune->present_parts =
+							bms_add_member(pprune->present_parts, k);
+				}
+				else if ((subidx = pprune->subpart_map[k]) >= 0)
+				{
+					PartitionedRelPruningData *subprune;
+
+					subprune = &prunedata->partrelprunedata[subidx];
+
+					if (!bms_is_empty(subprune->present_parts))
+						pprune->present_parts =
+							bms_add_member(pprune->present_parts, k);
+				}
+			}
+		}
+	}
+
+	/*
+	 * We must also recompute the other_subplans set, since indexes in it
+	 * may change.
+	 */
+	new_other_subplans = NULL;
+	i = -1;
+	while ((i = bms_next_member(prunestate->other_subplans, i)) >= 0)
+		new_other_subplans = bms_add_member(new_other_subplans,
+											new_subplan_indexes[i] - 1);
+
+	bms_free(prunestate->other_subplans);
+	prunestate->other_subplans = new_other_subplans;
+
+	pfree(new_subplan_indexes);
+}
+
 /*
  * ExecFindInitialMatchingSubPlans
  *		Identify the set of subplans that cannot be eliminated by initial
@@ -1817,10 +2124,14 @@ ExecInitPruningContext(PartitionPruneContext *context,
  * Must only be called once per 'prunestate', and only if initial pruning
  * is required.
  *
- * 'nsubplans' must be passed as the total number of unpruned subplans.
+ * The RT indexes of unpruned parents are returned in *parentrelids if asked
+ * for by the caller, in which case 'pruneinfo' must also be passed because
+ * that is where the RT indexes are to be found.
  */
 Bitmapset *
-ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
+ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate,
+								PartitionPruneInfo *pruneinfo,
+								Bitmapset **parentrelids)
 {
 	Bitmapset  *result = NULL;
 	MemoryContext oldcontext;
@@ -1830,11 +2141,14 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
 	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);
 
+	if (parentrelids)
+		*parentrelids = NULL;
+
 	/*
 	 * For each hierarchy, do the pruning tests, and add nondeletable
 	 * subplans' indexes to "result".
@@ -1845,14 +2159,42 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
 		PartitionedRelPruningData *pprune;
 
 		prunedata = prunestate->partprunedata[i];
+
+		/*
+		 * We pass the 1st item belonging to the root table of the hierarchy
+		 * and find_matching_subplans_recurse() takes care of recursing to
+		 * other (lower-level) parents as needed.
+		 */
 		pprune = &prunedata->partrelprunedata[0];
 
 		/* Perform pruning without using PARAM_EXEC Params */
 		find_matching_subplans_recurse(prunedata, pprune, true, &result);
 
-		/* Expression eval may have used space in node's ps_ExprContext too */
+		/*
+		 * Collect the RT indexes of surviving parents if the callers asked
+		 * to see them.
+		 */
+		if (parentrelids)
+		{
+			int		j;
+			List   *partrelpruneinfos = list_nth_node(List,
+													  pruneinfo->prune_infos,
+													  i);
+
+			for (j = 0; j < prunedata->num_partrelprunedata; j++)
+			{
+				PartitionedRelPruneInfo *pinfo = list_nth_node(PartitionedRelPruneInfo,
+															   partrelpruneinfos, j);
+
+				pprune = &prunedata->partrelprunedata[j];
+				if (!bms_is_empty(pprune->present_parts))
+					*parentrelids = bms_add_member(*parentrelids, pinfo->rtindex);
+			}
+		}
+
+		/* Expression eval may have used space in ExprContext too */
 		if (pprune->initial_pruning_steps)
-			ResetExprContext(pprune->initial_context.planstate->ps_ExprContext);
+			ResetExprContext(pprune->initial_context.exprcontext);
 	}
 
 	/* Add in any subplans that partition pruning didn't account for */
@@ -1862,120 +2204,11 @@ ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate, int nsubplans)
 
 	/* Copy result out of the temp context before we reset it */
 	result = bms_copy(result);
+	if (parentrelids)
+		*parentrelids = bms_copy(*parentrelids);
 
 	MemoryContextReset(prunestate->prune_context);
 
-	/*
-	 * If exec-time pruning is required and we pruned subplans above, then we
-	 * must re-sequence the subplan indexes so that ExecFindMatchingSubPlans
-	 * properly returns the indexes from the subplans which will remain after
-	 * execution of this function.
-	 *
-	 * We can safely skip this when !do_exec_prune, even though that leaves
-	 * invalid data in prunestate, because that data won't be consulted again
-	 * (cf initial Assert in ExecFindMatchingSubPlans).
-	 */
-	if (prunestate->do_exec_prune && bms_num_members(result) < nsubplans)
-	{
-		int		   *new_subplan_indexes;
-		Bitmapset  *new_other_subplans;
-		int			i;
-		int			newidx;
-
-		/*
-		 * First we must build a temporary array which maps old subplan
-		 * indexes to new ones.  For convenience of initialization, we use
-		 * 1-based indexes in this array and leave pruned items as 0.
-		 */
-		new_subplan_indexes = (int *) palloc0(sizeof(int) * nsubplans);
-		newidx = 1;
-		i = -1;
-		while ((i = bms_next_member(result, i)) >= 0)
-		{
-			Assert(i < nsubplans);
-			new_subplan_indexes[i] = newidx++;
-		}
-
-		/*
-		 * Now we can update each PartitionedRelPruneInfo's subplan_map with
-		 * new subplan indexes.  We must also recompute its present_parts
-		 * bitmap.
-		 */
-		for (i = 0; i < prunestate->num_partprunedata; i++)
-		{
-			PartitionPruningData *prunedata = prunestate->partprunedata[i];
-			int			j;
-
-			/*
-			 * Within each hierarchy, we perform this loop in back-to-front
-			 * order so that we determine present_parts for the lowest-level
-			 * partitioned tables first.  This way we can tell whether a
-			 * sub-partitioned table's partitions were entirely pruned so we
-			 * can exclude it from the current level's present_parts.
-			 */
-			for (j = prunedata->num_partrelprunedata - 1; j >= 0; j--)
-			{
-				PartitionedRelPruningData *pprune = &prunedata->partrelprunedata[j];
-				int			nparts = pprune->nparts;
-				int			k;
-
-				/* We just rebuild present_parts from scratch */
-				bms_free(pprune->present_parts);
-				pprune->present_parts = NULL;
-
-				for (k = 0; k < nparts; k++)
-				{
-					int			oldidx = pprune->subplan_map[k];
-					int			subidx;
-
-					/*
-					 * If this partition existed as a subplan then change the
-					 * old subplan index to the new subplan index.  The new
-					 * index may become -1 if the partition was pruned above,
-					 * or it may just come earlier in the subplan list due to
-					 * some subplans being removed earlier in the list.  If
-					 * it's a subpartition, add it to present_parts unless
-					 * it's entirely pruned.
-					 */
-					if (oldidx >= 0)
-					{
-						Assert(oldidx < nsubplans);
-						pprune->subplan_map[k] = new_subplan_indexes[oldidx] - 1;
-
-						if (new_subplan_indexes[oldidx] > 0)
-							pprune->present_parts =
-								bms_add_member(pprune->present_parts, k);
-					}
-					else if ((subidx = pprune->subpart_map[k]) >= 0)
-					{
-						PartitionedRelPruningData *subprune;
-
-						subprune = &prunedata->partrelprunedata[subidx];
-
-						if (!bms_is_empty(subprune->present_parts))
-							pprune->present_parts =
-								bms_add_member(pprune->present_parts, k);
-					}
-				}
-			}
-		}
-
-		/*
-		 * We must also recompute the other_subplans set, since indexes in it
-		 * may change.
-		 */
-		new_other_subplans = NULL;
-		i = -1;
-		while ((i = bms_next_member(prunestate->other_subplans, i)) >= 0)
-			new_other_subplans = bms_add_member(new_other_subplans,
-												new_subplan_indexes[i] - 1);
-
-		bms_free(prunestate->other_subplans);
-		prunestate->other_subplans = new_other_subplans;
-
-		pfree(new_subplan_indexes);
-	}
-
 	return result;
 }
 
@@ -2018,11 +2251,16 @@ ExecFindMatchingSubPlans(PartitionPruneState *prunestate)
 		prunedata = prunestate->partprunedata[i];
 		pprune = &prunedata->partrelprunedata[0];
 
+		/*
+		 * We pass the 1st item belonging to the root table of the hierarchy
+		 * and find_matching_subplans_recurse() takes care of recursing to
+		 * other (lower-level) parents as needed.
+		 */
 		find_matching_subplans_recurse(prunedata, pprune, false, &result);
 
-		/* Expression eval may have used space in node's ps_ExprContext too */
+		/* Expression eval may have used space in ExprContext too */
 		if (pprune->exec_pruning_steps)
-			ResetExprContext(pprune->exec_context.planstate->ps_ExprContext);
+			ResetExprContext(pprune->exec_context.exprcontext);
 	}
 
 	/* Add in any subplans that partition pruning didn't account for */
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index b5667e53e5..d5e10756ac 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -123,6 +123,209 @@ static TupleTableSlot *ExecProcNodeFirst(PlanState *node);
 static TupleTableSlot *ExecProcNodeInstr(PlanState *node);
 
 
+/* ------------------------------------------------------------------------
+ * ExecPrepNode
+ *		Recursively "prep" all the nodes in the plan tree rooted
+ *		at 'node'.
+ *
+ *		'node' is the current node of the plan produced by the query planner
+ *		'context' is the information that may be necessary to do the prep
+ *			work, (such as any EXTERN parameters in the query to do partition
+ *			pruning with)
+ *		'result' is the output variable to add the result into
+ *
+ * NOTE: ExecPrepNode subroutine for a given node must add the RT indexes of
+ * any relations that it manipulates to result->relationRTIs.  Optionally, it
+ * can produce a PlanPrepOutput node containing the information that may be of
+ * interest to later execution steps or to any intervening modules that have
+ * access to the ExecPrepOutput and put that in
+ * result->planPrepResults[plan->plan_node_id].  For example, nodes that
+ * supports partition pruning can perform the "initial" pruning steps to
+ * produce the set of "initially valid" subnodes that can be used as-is by the
+ * node's ExecInit* routine to only initialize those subnodes.
+ * ------------------------------------------------------------------------
+ */
+void
+ExecPrepNode(Plan *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	ListCell *l;
+
+	/* Do nothing when we get to the end of a leaf on tree. */
+	if (node == NULL)
+		return;
+
+	/* Make sure there's enough stack available. */
+	check_stack_depth();
+
+	/*
+	 * Write NULL for the node's PlanPruneOutput which the node's Prep routine
+	 * might write over.
+	 */
+	ExecPrepStorePlanPrepOutput(result, NULL, node);
+
+	switch (nodeTag(node))
+	{
+			/*
+			 * control nodes
+			 */
+		case T_Result:
+			ExecPrepResult((Result *) node, context, result);
+			break;
+		case T_ProjectSet:
+			ExecPrepProjectSet((ProjectSet *) node, context, result);
+			break;
+		case T_RecursiveUnion:
+			ExecPrepRecursiveUnion((RecursiveUnion *) node, context, result);
+			break;
+		case T_BitmapAnd:
+			ExecPrepBitmapAnd((BitmapAnd *) node, context, result);
+			break;
+		case T_BitmapOr:
+			ExecPrepBitmapOr((BitmapOr *) node, context, result);
+			break;
+		case T_ModifyTable:
+			ExecPrepModifyTable((ModifyTable *) node, context, result);
+			break;
+		case T_Append:
+			ExecPrepAppend((Append *) node, context, result);
+			break;
+		case T_MergeAppend:
+			ExecPrepMergeAppend((MergeAppend *) node, context, result);
+			break;
+
+			/*
+			 * scan nodes
+			 */
+		case T_SeqScan:
+			ExecPrepSeqScan((SeqScan *) node, context, result);
+			break;
+		case T_SampleScan:
+			ExecPrepSampleScan((SampleScan *) node, context, result);
+			break;
+		case T_IndexScan:
+			ExecPrepIndexScan((IndexScan *) node, context, result);
+			break;
+		case T_IndexOnlyScan:
+			ExecPrepIndexOnlyScan((IndexOnlyScan *) node, context, result);
+			break;
+		case T_BitmapIndexScan:
+			ExecPrepBitmapIndexScan((BitmapIndexScan *) node, context, result);
+			break;
+		case T_BitmapHeapScan:
+			ExecPrepBitmapHeapScan((BitmapHeapScan *) node, context, result);
+			break;
+		case T_TidScan:
+			ExecPrepTidScan((TidScan *) node, context, result);
+			break;
+		case T_TidRangeScan:
+			ExecPrepTidRangeScan((TidRangeScan *) node, context, result);
+			break;
+		case T_SubqueryScan:
+			ExecPrepSubqueryScan((SubqueryScan *) node, context, result);
+			break;
+		case T_FunctionScan:
+			ExecPrepFunctionScan((FunctionScan *) node, context, result);
+			break;
+		case T_TableFuncScan:
+			ExecPrepTableFuncScan((TableFuncScan *) node, context, result);
+			break;
+		case T_ValuesScan:
+			ExecPrepValuesScan((ValuesScan *) node, context, result);
+			break;
+		case T_CteScan:
+			ExecPrepCteScan((CteScan *) node, context, result);
+			break;
+		case T_NamedTuplestoreScan:
+			ExecPrepNamedTuplestoreScan((NamedTuplestoreScan *) node, context, result);
+			break;
+		case T_WorkTableScan:
+			ExecPrepWorkTableScan((WorkTableScan *) node, context, result);
+			break;
+		case T_ForeignScan:
+			ExecPrepForeignScan((ForeignScan *) node, context, result);
+			break;
+		case T_CustomScan:
+			ExecPrepCustomScan((CustomScan *) node, context, result);
+			break;
+
+			/*
+			 * join nodes: subnodes handled below
+			 */
+		case T_NestLoop:
+			ExecPrepNestLoop((NestLoop *) node, context, result);
+			break;
+		case T_MergeJoin:
+			ExecPrepMergeJoin((MergeJoin *) node, context, result);
+			break;
+		case T_HashJoin:
+			ExecPrepHashJoin((HashJoin *) node, context, result);
+			break;
+
+			/*
+			 * materialization nodes: subnodes handled below
+			 */
+		case T_Material:
+			ExecPrepMaterial((Material *) node, context, result);
+			break;
+		case T_Sort:
+			ExecPrepSort((Sort *) node, context, result);
+			break;
+		case T_IncrementalSort:
+			ExecPrepIncrementalSort((IncrementalSort *) node, context, result);
+			break;
+		case T_Memoize:
+			ExecPrepMemoize((Memoize *) node, context, result);
+			break;
+		case T_Group:
+			ExecPrepGroup((Group *) node, context, result);
+			break;
+		case T_Agg:
+			ExecPrepAgg((Agg *) node, context, result);
+			break;
+		case T_WindowAgg:
+			ExecPrepWindowAgg((WindowAgg *) node, context, result);
+			break;
+		case T_Unique:
+			ExecPrepUnique((Unique *) node, context, result);
+			break;
+		case T_Gather:
+			ExecPrepGather((Gather *) node, context, result);
+			break;
+		case T_GatherMerge:
+			ExecPrepGatherMerge((GatherMerge *) node, context, result);
+			break;
+		case T_Hash:
+			ExecPrepHash((Hash *) node, context, result);
+			break;
+		case T_SetOp:
+			ExecPrepSetOp((SetOp *) node, context, result);
+			break;
+		case T_LockRows:
+			ExecPrepLockRows((LockRows *) node, context, result);
+			break;
+		case T_Limit:
+			ExecPrepLimit((Limit *) node, context, result);
+			break;
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
+			result = NULL;		/* keep compiler quiet */
+			break;
+	}
+
+	/*
+	 * Prep any initPlans present in this node.  The planner put them in
+	 * a separate list for us.
+	 */
+	foreach(l, node->initPlan)
+	{
+		SubPlan    *subplan = (SubPlan *) lfirst(l);
+
+		Assert(IsA(subplan, SubPlan));
+		ExecPrepSubPlan(subplan, context, result);
+	}
+}
+
 /* ------------------------------------------------------------------------
  *		ExecInitNode
  *
@@ -157,6 +360,9 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 	 */
 	check_stack_depth();
 
+	/* Check that the PlanPrepOutput for the node looks sane if any. */
+	EXEC_PREP_OUTPUT_SANITY(node, estate);
+
 	switch (nodeTag(node))
 	{
 			/*
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9df1f81ea8..5c85148b37 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_execprep = 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_execprep == NULL ||
+		   bms_is_member(rti, estate->es_execprep->relationRTIs));
+
 	rel = estate->es_relations[rti - 1];
 	if (rel == NULL)
 	{
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 29a68879ee..5f0ff2df2a 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/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 08cf569d8f..f3b0ec75d3 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3142,6 +3142,19 @@ hashagg_reset_spill_state(AggState *aggstate)
 	}
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepAgg
+ *
+ *		This "preps" the Agg node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepAgg(Agg *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 
 /* -----------------
  * ExecInitAgg
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 7937f1c88f..a44c8079bd 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -62,6 +62,7 @@
 #include "executor/execPartition.h"
 #include "executor/nodeAppend.h"
 #include "miscadmin.h"
+#include "partitioning/partdesc.h"
 #include "pgstat.h"
 #include "storage/latch.h"
 
@@ -94,6 +95,62 @@ static bool ExecAppendAsyncRequest(AppendState *node, TupleTableSlot **result);
 static void ExecAppendAsyncEventWait(AppendState *node);
 static void classify_matching_subplans(AppendState *node);
 
+/* ----------------------------------------------------------------
+ *		ExecPrepAppend
+ *
+ *		Prep an append node
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepAppend(Append *node, ExecPrepContext *context,
+			   ExecPrepOutput *result)
+{
+	PartitionPruneInfo *pruneinfo = node->part_prune_info;
+
+	if (pruneinfo && pruneinfo->contains_init_steps)
+	{
+		List	   *rtable = context->stmt->rtable;
+		List	   *subplans = node->appendplans;
+		ParamListInfo params = context->params;
+		Bitmapset  *parentrelids;
+		int			i;
+		PlanPrepOutput *planPrepResult = makeNode(PlanPrepOutput);
+
+		planPrepResult->plan_node_id = node->plan.plan_node_id;
+		planPrepResult->initially_valid_subnodes =
+			ExecPrepDoInitialPruning(pruneinfo, rtable, params, &parentrelids);
+		/* Replace the NULL that ExecPrepNode() would've written. */
+		ExecPrepStorePlanPrepOutput(result, planPrepResult, &node->plan);
+
+		/* All relevant parents must be reported too. */
+		Assert(bms_num_members(parentrelids) > 0);
+		result->relationRTIs = bms_add_members(result->relationRTIs,
+											   parentrelids);
+
+		/* And all leaf partitions that will be scanned. */
+		i = -1;
+		while ((i = bms_next_member(planPrepResult->initially_valid_subnodes, i)) >= 0)
+		{
+			Plan   *subplan = list_nth(subplans, i);
+
+			ExecPrepNode(subplan, context, result);
+		}
+	}
+	else
+	{
+		List	   *subplans = node->appendplans;
+		ListCell   *lc;
+
+		/* Recurse to prep *all* of the node's child subplans. */
+		foreach(lc, subplans)
+		{
+			Plan *subplan = (Plan *) lfirst(lc);
+
+			ExecPrepNode(subplan, context, result);
+		}
+	}
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitAppend
  *
@@ -136,39 +193,19 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	/* If run-time partition pruning is enabled, then set that up now */
 	if (node->part_prune_info != NULL)
 	{
-		PartitionPruneState *prunestate;
-
-		/* We may need an expression context to evaluate partition exprs */
-		ExecAssignExprContext(estate, &appendstate->ps);
-
-		/* Create the working data structure for pruning. */
-		prunestate = ExecCreatePartitionPruneState(&appendstate->ps,
-												   node->part_prune_info);
-		appendstate->as_prune_state = prunestate;
-
-		/* Perform an initial partition prune, if required. */
-		if (prunestate->do_initial_prune)
-		{
-			/* Determine which subplans survive initial pruning */
-			validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
-															list_length(node->appendplans));
-
-			nplans = bms_num_members(validsubplans);
-		}
-		else
-		{
-			/* We'll need to initialize all subplans */
-			nplans = list_length(node->appendplans);
-			Assert(nplans > 0);
-			validsubplans = bms_add_range(NULL, 0, nplans - 1);
-		}
+		validsubplans = ExecInitPartitionPruning(&appendstate->ps,
+												 list_length(node->appendplans),
+												 node->part_prune_info,
+												 &appendstate->as_prune_state);
+		nplans = bms_num_members(validsubplans);
 
 		/*
 		 * When no run-time pruning is required and there's at least one
 		 * 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/nodeBitmapAnd.c b/src/backend/executor/nodeBitmapAnd.c
index b54c79f853..4ad3e5ff81 100644
--- a/src/backend/executor/nodeBitmapAnd.c
+++ b/src/backend/executor/nodeBitmapAnd.c
@@ -45,6 +45,24 @@ ExecBitmapAnd(PlanState *pstate)
 	return NULL;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepBitmapAnd
+ *
+ *		This "preps" the BitmapAnd node and the subplans.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepBitmapAnd(BitmapAnd *node, ExecPrepContext *context,
+				  ExecPrepOutput *result)
+{
+	ListCell *lc;
+
+	foreach(lc, node->bitmapplans)
+	{
+		ExecPrepNode((Plan *) lfirst(lc), context, result);
+	}
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitBitmapAnd
  *
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index f6fe07ad70..aaf215a4cc 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -696,6 +696,20 @@ ExecEndBitmapHeapScan(BitmapHeapScanState *node)
 	table_endscan(scanDesc);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepBitmapHeapScan
+ *
+ *		This "preps" the BitmapHeapScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepBitmapHeapScan(BitmapHeapScan *node, ExecPrepContext *context,
+					   ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitBitmapHeapScan
  *
diff --git a/src/backend/executor/nodeBitmapIndexscan.c b/src/backend/executor/nodeBitmapIndexscan.c
index 551e47630d..bb766f71a2 100644
--- a/src/backend/executor/nodeBitmapIndexscan.c
+++ b/src/backend/executor/nodeBitmapIndexscan.c
@@ -201,6 +201,20 @@ ExecEndBitmapIndexScan(BitmapIndexScanState *node)
 		index_close(indexRelationDesc, NoLock);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepBitmapIndexScan
+ *
+ *		This "preps" the BitmapIndexScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepBitmapIndexScan(BitmapIndexScan *node, ExecPrepContext *context,
+						ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitBitmapIndexScan
  *
diff --git a/src/backend/executor/nodeBitmapOr.c b/src/backend/executor/nodeBitmapOr.c
index 2d57f11fe7..feb3e4a8d6 100644
--- a/src/backend/executor/nodeBitmapOr.c
+++ b/src/backend/executor/nodeBitmapOr.c
@@ -46,6 +46,24 @@ ExecBitmapOr(PlanState *pstate)
 	return NULL;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepBitmapOr
+ *
+ *		This "preps" the BitmapOr node and the subplans.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepBitmapOr(BitmapOr *node, ExecPrepContext *context,
+				 ExecPrepOutput *result)
+{
+	ListCell *lc;
+
+	foreach(lc, node->bitmapplans)
+	{
+		ExecPrepNode((Plan *) lfirst(lc), context, result);
+	}
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitBitmapOr
  *
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index b9d7dec8a2..533cfb7874 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -166,6 +166,18 @@ ExecCteScan(PlanState *pstate)
 					(ExecScanRecheckMtd) CteScanRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepCteScan
+ *
+ *		This "preps" the CteScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepCteScan(CteScan *node, ExecPrepContext *context,
+				ExecPrepOutput *result)
+{
+	/* nothing to do */
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitCteScan
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index 8f56bd8a23..0bf1636326 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -24,6 +24,24 @@
 
 static TupleTableSlot *ExecCustomScan(PlanState *pstate);
 
+/* ----------------------------------------------------------------
+ *		ExecPrepCustomScan
+ *
+ *		This "preps" the CustomScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepCustomScan(CustomScan *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	ListCell *lc;
+
+	result->relationRTIs = bms_add_members(result->relationRTIs,
+										   node->custom_relids);
+	foreach(lc, node->custom_plans)
+	{
+		ExecPrepNode((Plan *) lfirst(lc), context, result);
+	}
+}
 
 CustomScanState *
 ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 5b9737c2ab..ffe17ec6d5 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -134,6 +134,18 @@ ExecForeignScan(PlanState *pstate)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepForeignScan
+ *
+ *		This "preps" the ForeignScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepForeignScan(ForeignScan *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_members(result->relationRTIs,
+										   node->fs_relids);
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 434379a5aa..df055ce01f 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -272,6 +272,19 @@ ExecFunctionScan(PlanState *pstate)
 					(ExecScanRecheckMtd) FunctionRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepFunctionScan
+ *
+ *		This "preps" the FunctionScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepFunctionScan(FunctionScan *node, ExecPrepContext *context,
+					 ExecPrepOutput *result)
+{
+	/* nothing to do*/
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitFunctionScan
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 4f8a17df7d..0edb0ae13a 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -49,6 +49,19 @@ static TupleTableSlot *gather_getnext(GatherState *gatherstate);
 static MinimalTuple gather_readnext(GatherState *gatherstate);
 static void ExecShutdownGatherWorkers(GatherState *node);
 
+/* ----------------------------------------------------------------
+ *		ExecPrepGather
+ *
+ *		This "preps" the Gather node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepGather(Gather *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 
 /* ----------------------------------------------------------------
  *		ExecInitGather
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index a488cc6d8b..c564d4ac25 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -64,6 +64,19 @@ static bool gather_merge_readnext(GatherMergeState *gm_state, int reader,
 								  bool nowait);
 static void load_tuple_array(GatherMergeState *gm_state, int reader);
 
+/* ----------------------------------------------------------------
+ *		ExecPrepGatherMerge
+ *
+ *		This "preps" the GatherMerge node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepGatherMerge(GatherMerge *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitGather
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 666d02b58f..0e5bcf89bf 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -151,6 +151,19 @@ ExecGroup(PlanState *pstate)
 	}
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepGroup
+ *
+ *		This "preps" the Group node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepGroup(Group *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* -----------------
  * ExecInitGroup
  *
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 4d68a8b97b..d20e14c7fc 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -344,6 +344,19 @@ MultiExecParallelHash(HashState *node)
 		   BarrierPhase(build_barrier) == PHJ_BUILD_DONE);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepHash
+ *
+ *		This "preps" the hash node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepHash(Hash *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitHash
  *
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 88b870655e..5665c31873 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -607,6 +607,20 @@ ExecParallelHashJoin(PlanState *pstate)
 	return ExecHashJoinImpl(pstate, true);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepHashJoin
+ *
+ *		This "preps" the HashJoin node and the node's children.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepHashJoin(HashJoin *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the children. */
+	ExecPrepNode(outerPlan(node), context, result);
+	ExecPrepNode(innerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitHashJoin
  *
diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c
index d6fb56dec7..c1c8fe2af6 100644
--- a/src/backend/executor/nodeIncrementalSort.c
+++ b/src/backend/executor/nodeIncrementalSort.c
@@ -964,6 +964,20 @@ ExecIncrementalSort(PlanState *pstate)
 	return slot;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepIncrementalSort
+ *
+ *		This "preps" the IncrementalSort node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepIncrementalSort(IncrementalSort *node, ExecPrepContext *context,
+						ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitIncrementalSort
  *
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index eb3ddd2943..ccc60c38f5 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -476,6 +476,20 @@ ExecIndexOnlyRestrPos(IndexOnlyScanState *node)
 	index_restrpos(node->ioss_ScanDesc);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepIndexOnlyScan
+ *
+ *		This "preps" the IndexOnlyScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepIndexOnlyScan(IndexOnlyScan *node, ExecPrepContext *context,
+					  ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitIndexOnlyScan
  *
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index a91f135be7..5080abdd9d 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -885,6 +885,20 @@ ExecIndexRestrPos(IndexScanState *node)
 	index_restrpos(node->iss_ScanDesc);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepIndexScan
+ *
+ *		This "preps" the IndexScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepIndexScan(IndexScan *node, ExecPrepContext *context,
+				  ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitIndexScan
  *
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 1b91b123fa..00aa5dd577 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -437,6 +437,19 @@ compute_tuples_needed(LimitState *node)
 	return node->count + node->offset;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepLimit
+ *
+ *		This "preps" the limit node and	the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepLimit(Limit *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitLimit
  *
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 1a9dab25dd..9a3d2c5583 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -281,6 +281,19 @@ lnext:
 	return slot;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepLockRows
+ *
+ *		This "preps" the LockRows node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepLockRows(LockRows *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitLockRows
  *
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index 2cb27e0e9a..802bf37ff1 100644
--- a/src/backend/executor/nodeMaterial.c
+++ b/src/backend/executor/nodeMaterial.c
@@ -156,6 +156,19 @@ ExecMaterial(PlanState *pstate)
 	return ExecClearTuple(slot);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepMaterial
+ *
+ *		This "preps" the Material node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepMaterial(Material *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitMaterial
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeMemoize.c b/src/backend/executor/nodeMemoize.c
index 55cdd5c4d9..eacfd5f3cb 100644
--- a/src/backend/executor/nodeMemoize.c
+++ b/src/backend/executor/nodeMemoize.c
@@ -902,6 +902,19 @@ ExecMemoize(PlanState *pstate)
 	}							/* switch */
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepMemoize
+ *
+ *		This "preps" the Memoize node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepMemoize(Memoize *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 MemoizeState *
 ExecInitMemoize(Memoize *node, EState *estate, int eflags)
 {
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 418f89dea8..50f6429533 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -43,6 +43,7 @@
 #include "executor/nodeMergeAppend.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
+#include "partitioning/partdesc.h"
 
 /*
  * We have one slot for each item in the heap array.  We use SlotNumber
@@ -54,6 +55,62 @@ typedef int32 SlotNumber;
 static TupleTableSlot *ExecMergeAppend(PlanState *pstate);
 static int	heap_compare_slots(Datum a, Datum b, void *arg);
 
+/* ----------------------------------------------------------------
+ *		ExecPrepMergeAppend
+ *
+ *		Prep an MergeAppend node
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepMergeAppend(MergeAppend *node, ExecPrepContext *context,
+					ExecPrepOutput *result)
+{
+	PartitionPruneInfo *pruneinfo = node->part_prune_info;
+
+	if (pruneinfo && pruneinfo->contains_init_steps)
+	{
+		List	   *rtable = context->stmt->rtable;
+		List	   *subplans = node->mergeplans;
+		ParamListInfo params = context->params;
+		Bitmapset  *parentrelids;
+		int			i;
+		PlanPrepOutput *planPrepResult = makeNode(PlanPrepOutput);
+
+		planPrepResult->plan_node_id = node->plan.plan_node_id;
+		planPrepResult->initially_valid_subnodes =
+			ExecPrepDoInitialPruning(pruneinfo, rtable, params, &parentrelids);
+		/* Replace the NULL that ExecPrepNode() would've written. */
+		ExecPrepStorePlanPrepOutput(result, planPrepResult, &node->plan);
+
+		/* All relevant parents must be reported too. */
+		Assert(bms_num_members(parentrelids) > 0);
+		result->relationRTIs = bms_add_members(result->relationRTIs,
+											   parentrelids);
+
+		/* And all leaf partitions that will be scanned. */
+		i = -1;
+		while ((i = bms_next_member(planPrepResult->initially_valid_subnodes, i)) >= 0)
+		{
+			Plan   *subplan = list_nth(subplans, i);
+
+			ExecPrepNode(subplan, context, result);
+		}
+	}
+	else
+	{
+		List	   *subplans = node->mergeplans;
+		ListCell   *lc;
+
+		/* Recurse to prep *all* of the node's child subplans. */
+		foreach(lc, subplans)
+		{
+			Plan *subplan = (Plan *) lfirst(lc);
+
+			ExecPrepNode(subplan, context, result);
+		}
+	}
+}
+
 
 /* ----------------------------------------------------------------
  *		ExecInitMergeAppend
@@ -84,38 +141,19 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	/* If run-time partition pruning is enabled, then set that up now */
 	if (node->part_prune_info != NULL)
 	{
-		PartitionPruneState *prunestate;
-
-		/* We may need an expression context to evaluate partition exprs */
-		ExecAssignExprContext(estate, &mergestate->ps);
-
-		prunestate = ExecCreatePartitionPruneState(&mergestate->ps,
-												   node->part_prune_info);
-		mergestate->ms_prune_state = prunestate;
-
-		/* Perform an initial partition prune, if required. */
-		if (prunestate->do_initial_prune)
-		{
-			/* Determine which subplans survive initial pruning */
-			validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
-															list_length(node->mergeplans));
-
-			nplans = bms_num_members(validsubplans);
-		}
-		else
-		{
-			/* We'll need to initialize all subplans */
-			nplans = list_length(node->mergeplans);
-			Assert(nplans > 0);
-			validsubplans = bms_add_range(NULL, 0, nplans - 1);
-		}
+		validsubplans = ExecInitPartitionPruning(&mergestate->ps,
+												 list_length(node->mergeplans),
+												 node->part_prune_info,
+												 &mergestate->ms_prune_state);
+		nplans = bms_num_members(validsubplans);
 
 		/*
 		 * When no run-time pruning is required and there's at least one
 		 * 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/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index a049bc4ae0..12b1790c8a 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -1428,6 +1428,20 @@ ExecMergeJoin(PlanState *pstate)
 	}
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepMergeJoin
+ *
+ *		This "preps" the MergeJoin node and the node's children.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepMergeJoin(MergeJoin *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the children. */
+	ExecPrepNode(outerPlan(node), context, result);
+	ExecPrepNode(innerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitMergeJoin
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5ec699a9bd..93a6ac062f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2700,6 +2700,32 @@ ExecLookupResultRelByOid(ModifyTableState *node, Oid resultoid,
 	return NULL;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepModifyTable
+ *
+ *		This "preps" the ModifyTable node and the subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepModifyTable(ModifyTable *node, ExecPrepContext *context,
+					ExecPrepOutput *result)
+{
+	ListCell *lc;
+
+	if (node->rootRelation > 0)
+		result->relationRTIs = bms_add_member(result->relationRTIs,
+											  node->rootRelation);
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->nominalRelation);
+	foreach(lc, node->resultRelations)
+	{
+		result->relationRTIs = bms_add_member(result->relationRTIs,
+											  lfirst_int(lc));
+	}
+
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitModifyTable
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeNamedtuplestorescan.c b/src/backend/executor/nodeNamedtuplestorescan.c
index ca637b1b0e..5db23af93c 100644
--- a/src/backend/executor/nodeNamedtuplestorescan.c
+++ b/src/backend/executor/nodeNamedtuplestorescan.c
@@ -74,6 +74,19 @@ ExecNamedTuplestoreScan(PlanState *pstate)
 					(ExecScanRecheckMtd) NamedTuplestoreScanRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepNamedTuplestoreScan
+ *
+ *		This "preps" the NamedTuplestoreScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepNamedTuplestoreScan(NamedTuplestoreScan *node,
+							ExecPrepContext *context,
+							ExecPrepOutput *result)
+{
+	/* nothing to do */
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitNamedTuplestoreScan
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 06767c3133..ffb3a94f07 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -255,6 +255,20 @@ ExecNestLoop(PlanState *pstate)
 	}
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepNestLoop
+ *
+ *		This "preps" the NestLoop node and the node's children.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepNestLoop(NestLoop *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the children. */
+	ExecPrepNode(outerPlan(node), context, result);
+	ExecPrepNode(innerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitNestLoop
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index ea40d61b0b..1d6085a3b4 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -208,6 +208,19 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 	return NULL;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepProjectSet
+ *
+ *		This "preps" the ProjectSet node and the subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepProjectSet(ProjectSet *node, ExecPrepContext *context,
+				   ExecPrepOutput *result)
+{
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitProjectSet
  *
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index 2d01ed7711..806c653c56 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -159,6 +159,20 @@ ExecRecursiveUnion(PlanState *pstate)
 	return NULL;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepRecursiveUnion
+ *
+ *		This "preps" the RecursiveUnion node and the children.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepRecursiveUnion(RecursiveUnion *node, ExecPrepContext *context,
+					   ExecPrepOutput *result)
+{
+	ExecPrepNode(outerPlan(node), context, result);
+	ExecPrepNode(innerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitRecursiveUnion
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index d0413e05de..14883b6764 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -169,6 +169,19 @@ ExecResultRestrPos(ResultState *node)
 		elog(ERROR, "Result nodes do not support mark/restore");
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepResult
+ *
+ *		This "preps" the Result node and the subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepResult(Result *node, ExecPrepContext *context,
+			   ExecPrepOutput *result)
+{
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitResult
  *
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index a03ae120f8..ef4c0775f7 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -89,6 +89,20 @@ ExecSampleScan(PlanState *pstate)
 					(ExecScanRecheckMtd) SampleRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepSampleScan
+ *
+ *		This "preps" the SampleScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepSampleScan(SampleScan *node, ExecPrepContext *context,
+				   ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitSampleScan
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 7b58cd9162..8964c1e9b2 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -114,6 +114,19 @@ ExecSeqScan(PlanState *pstate)
 					(ExecScanRecheckMtd) SeqRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepSeqScanScan
+ *
+ *		This "preps" the SeqScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepSeqScan(SeqScan *node, ExecPrepContext *context,
+				ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitSeqScan
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 4b428cfa39..312aa8511f 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -470,6 +470,19 @@ setop_retrieve_hash_table(SetOpState *setopstate)
 	return NULL;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepSetOp
+ *
+ *		This "preps" the setop node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepSetOp(SetOp *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitSetOp
  *
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index 9481a622bf..c31f2634e8 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -203,6 +203,19 @@ ExecSort(PlanState *pstate)
 	return slot;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepSort
+ *
+ *		This "preps" the Sort node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepSort(Sort *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitSort
  *
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 60d2290030..b95084ddb2 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -775,6 +775,18 @@ slotNoNulls(TupleTableSlot *slot)
 	return true;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepSubPlan
+ *
+ *		This "preps" the SubPlan node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepSubPlan(SubPlan *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* nothing to do */
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitSubPlan
  *
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index 242c9cd4b9..cc0d62ca85 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -89,6 +89,20 @@ ExecSubqueryScan(PlanState *pstate)
 					(ExecScanRecheckMtd) SubqueryRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepSubqueryScan
+ *
+ *		This "preps" the SubqueryScan node and the subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepSubqueryScan(SubqueryScan *node, ExecPrepContext *context,
+					 ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode((Plan *) node->subplan, context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitSubqueryScan
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index 0db4ed0c2f..dccecb3916 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -83,6 +83,19 @@ TableFuncRecheck(TableFuncScanState *node, TupleTableSlot *slot)
 	return true;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepTableFuncScan
+ *
+ *		This "preps" the TableFuncScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepTableFuncScan(TableFuncScan *node, ExecPrepContext *context,
+					  ExecPrepOutput *result)
+{
+	/* nothing to do*/
+}
+
 /* ----------------------------------------------------------------
  *		ExecTableFuncScan(node)
  *
diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c
index d5bf1be787..1c05ce8035 100644
--- a/src/backend/executor/nodeTidrangescan.c
+++ b/src/backend/executor/nodeTidrangescan.c
@@ -340,6 +340,20 @@ ExecEndTidRangeScan(TidRangeScanState *node)
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepTidRangeScan
+ *
+ *		This "preps" the TidRangeScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepTidRangeScan(TidRangeScan *node, ExecPrepContext *context,
+					 ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitTidRangeScan
  *
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 4116d1f3b5..6031ab52b6 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -408,7 +408,6 @@ TidRecheck(TidScanState *node, TupleTableSlot *slot)
 	return true;
 }
 
-
 /* ----------------------------------------------------------------
  *		ExecTidScan(node)
  *
@@ -483,6 +482,20 @@ ExecEndTidScan(TidScanState *node)
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepTidScan
+ *
+ *		This "preps" the TidScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepTidScan(TidScan *node, ExecPrepContext *context,
+				ExecPrepOutput *result)
+{
+	result->relationRTIs = bms_add_member(result->relationRTIs,
+										  node->scan.scanrelid);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitTidScan
  *
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index 6c99d13a39..87c1b53515 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -104,6 +104,19 @@ ExecUnique(PlanState *pstate)
 	return ExecCopySlot(resultTupleSlot, slot);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepUnique
+ *
+ *		This "preps" the unique node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepUnique(Unique *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitUnique
  *
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index dda1c59b23..6cf7fd77d6 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -203,6 +203,19 @@ ExecValuesScan(PlanState *pstate)
 					(ExecScanRecheckMtd) ValuesRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepValuesScan
+ *
+ *		This "preps" the ValuesScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepValuesScan(ValuesScan *node, ExecPrepContext *context,
+				   ExecPrepOutput *result)
+{
+	/* nothing to do */
+}
+
 /* ----------------------------------------------------------------
  *		ExecInitValuesScan
  * ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 08ce05ca5a..90b7494bee 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2238,6 +2238,19 @@ ExecWindowAgg(PlanState *pstate)
 	return ExecProject(winstate->ss.ps.ps_ProjInfo);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepWindowAgg
+ *
+ *		This "preps" the WindowAgg node and the node's subplan.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepWindowAgg(WindowAgg *node, ExecPrepContext *context, ExecPrepOutput *result)
+{
+	/* Nothing to do beside recursing to the subplan. */
+	ExecPrepNode(outerPlan(node), context, result);
+}
+
 /* -----------------
  * ExecInitWindowAgg
  *
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
index 15fd71fb32..71a2ac7e40 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -121,6 +121,18 @@ ExecWorkTableScan(PlanState *pstate)
 					(ExecScanRecheckMtd) WorkTableScanRecheck);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecPrepWorkTableScan
+ *
+ *		This "preps" the WorkTableScan node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecPrepWorkTableScan(WorkTableScan *node, ExecPrepContext *context,
+					  ExecPrepOutput *result)
+{
+	/* nothing to do */
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitWorkTableScan
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index c93f90de9b..84c1b22ccb 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1485,6 +1485,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 	CachedPlanSource *plansource;
 	CachedPlan *cplan;
 	List	   *stmt_list;
+	List	   *stmt_execprep_list;
 	char	   *query_string;
 	Snapshot	snapshot;
 	MemoryContext oldcontext;
@@ -1566,6 +1567,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;
+	stmt_execprep_list = cplan->stmt_execprep_list;
 
 	if (!plan->saved)
 	{
@@ -1577,6 +1579,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 		 */
 		oldcontext = MemoryContextSwitchTo(portal->portalContext);
 		stmt_list = copyObject(stmt_list);
+		stmt_execprep_list = copyObject(stmt_execprep_list);
 		MemoryContextSwitchTo(oldcontext);
 		ReleaseCachedPlan(cplan, NULL);
 		cplan = NULL;			/* portal shouldn't depend on cplan */
@@ -1590,6 +1593,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 					  query_string,
 					  plansource->commandTag,
 					  stmt_list,
+					  stmt_execprep_list,
 					  cplan);
 
 	/*
@@ -2380,7 +2384,9 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 	{
 		CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
 		List	   *stmt_list;
-		ListCell   *lc2;
+		List	   *stmt_execprep_list;
+		ListCell   *lc2,
+				   *lc3;
 
 		spicallbackarg.query = plansource->query_string;
 
@@ -2459,6 +2465,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 							  plan_owner, _SPI_current->queryEnv);
 
 		stmt_list = cplan->stmt_list;
+		stmt_execprep_list = cplan->stmt_execprep_list;
 
 		/*
 		 * If we weren't given a specific snapshot to use, and the statement
@@ -2496,9 +2503,10 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 			}
 		}
 
-		foreach(lc2, stmt_list)
+		forboth(lc2, stmt_list, lc3, stmt_execprep_list)
 		{
 			PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2);
+			ExecPrepOutput *execprep = lfirst_node(ExecPrepOutput, lc3);
 			bool		canSetTag = stmt->canSetTag;
 			DestReceiver *dest;
 
@@ -2570,7 +2578,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
 				else
 					snap = InvalidSnapshot;
 
-				qdesc = CreateQueryDesc(stmt,
+				qdesc = CreateQueryDesc(stmt, execprep,
 										plansource->query_string,
 										snap, crosscheck_snapshot,
 										dest,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6bd95bbce2..89101256cf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -68,6 +68,18 @@
 		} \
 	} while (0)
 
+/* Copy a field that is an array with numElem of Node objects */
+#define COPY_NODE_ARRAY(fldname, numElem) \
+	do { \
+		int		i; \
+		newnode->fldname = numElem > 0 ? \
+			palloc(numElem * sizeof(from->fldname[0])) : NULL; \
+		for (i = 0; i < numElem; i++) \
+		{ \
+			newnode->fldname[i] = copyObject(from->fldname[i]); \
+		} \
+	} 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,9 +106,12 @@ _copyPlannedStmt(const PlannedStmt *from)
 	COPY_SCALAR_FIELD(transientPlan);
 	COPY_SCALAR_FIELD(dependsOnRole);
 	COPY_SCALAR_FIELD(parallelModeNeeded);
+	COPY_SCALAR_FIELD(usesPreExecPruning);
 	COPY_SCALAR_FIELD(jitFlags);
 	COPY_NODE_FIELD(planTree);
+	COPY_SCALAR_FIELD(numPlanNodes);
 	COPY_NODE_FIELD(rtable);
+	COPY_BITMAPSET_FIELD(relationRTIs);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(appendRelations);
 	COPY_NODE_FIELD(subplans);
@@ -1278,6 +1293,8 @@ _copyPartitionPruneInfo(const PartitionPruneInfo *from)
 	PartitionPruneInfo *newnode = makeNode(PartitionPruneInfo);
 
 	COPY_NODE_FIELD(prune_infos);
+	COPY_SCALAR_FIELD(contains_init_steps);
+	COPY_SCALAR_FIELD(contains_exec_steps);
 	COPY_BITMAPSET_FIELD(other_subplans);
 
 	return newnode;
@@ -4984,6 +5001,28 @@ _copyBitString(const BitString *from)
 	return newnode;
 }
 
+static ExecPrepOutput *
+_copyExecPrepOutput(const ExecPrepOutput *from)
+{
+	ExecPrepOutput *newnode = makeNode(ExecPrepOutput);
+
+	COPY_BITMAPSET_FIELD(relationRTIs);
+	COPY_SCALAR_FIELD(numPlanNodes);
+	COPY_NODE_ARRAY(planPrepResults, from->numPlanNodes);
+
+	return newnode;
+}
+
+static PlanPrepOutput *
+_copyPlanPrepOutput(const PlanPrepOutput *from)
+{
+	PlanPrepOutput *newnode = makeNode(PlanPrepOutput);
+
+	COPY_SCALAR_FIELD(plan_node_id);
+	COPY_BITMAPSET_FIELD(initially_valid_subnodes);
+
+	return newnode;
+}
 
 static ForeignKeyCacheInfo *
 _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
@@ -5930,6 +5969,16 @@ copyObjectImpl(const void *from)
 			retval = _copyPublicationTable(from);
 			break;
 
+			/*
+			 * EXECUTION NODES
+			 */
+		case T_ExecPrepOutput:
+			retval = _copyExecPrepOutput(from);
+			break;
+		case T_PlanPrepOutput:
+			retval = _copyPlanPrepOutput(from);
+			break;
+
 			/*
 			 * MISCELLANEOUS NODES
 			 */
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6bdad462c7..9fe247d505 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -312,9 +312,12 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
 	WRITE_BOOL_FIELD(transientPlan);
 	WRITE_BOOL_FIELD(dependsOnRole);
 	WRITE_BOOL_FIELD(parallelModeNeeded);
+	WRITE_BOOL_FIELD(usesPreExecPruning);
 	WRITE_INT_FIELD(jitFlags);
 	WRITE_NODE_FIELD(planTree);
+	WRITE_INT_FIELD(numPlanNodes);
 	WRITE_NODE_FIELD(rtable);
+	WRITE_BITMAPSET_FIELD(relationRTIs);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
 	WRITE_NODE_FIELD(subplans);
@@ -1004,6 +1007,8 @@ _outPartitionPruneInfo(StringInfo str, const PartitionPruneInfo *node)
 	WRITE_NODE_TYPE("PARTITIONPRUNEINFO");
 
 	WRITE_NODE_FIELD(prune_infos);
+	WRITE_BOOL_FIELD(contains_init_steps);
+	WRITE_BOOL_FIELD(contains_exec_steps);
 	WRITE_BITMAPSET_FIELD(other_subplans);
 }
 
@@ -2274,6 +2279,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(finalrtable);
+	WRITE_BITMAPSET_FIELD(relationRTIs);
 	WRITE_NODE_FIELD(finalrowmarks);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(appendRelations);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3f68f7c18d..7ecb9ad73c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1585,9 +1585,12 @@ _readPlannedStmt(void)
 	READ_BOOL_FIELD(transientPlan);
 	READ_BOOL_FIELD(dependsOnRole);
 	READ_BOOL_FIELD(parallelModeNeeded);
+	READ_BOOL_FIELD(usesPreExecPruning);
 	READ_INT_FIELD(jitFlags);
 	READ_NODE_FIELD(planTree);
+	READ_INT_FIELD(numPlanNodes);
 	READ_NODE_FIELD(rtable);
+	READ_BITMAPSET_FIELD(relationRTIs);
 	READ_NODE_FIELD(resultRelations);
 	READ_NODE_FIELD(appendRelations);
 	READ_NODE_FIELD(subplans);
@@ -2534,6 +2537,8 @@ _readPartitionPruneInfo(void)
 	READ_LOCALS(PartitionPruneInfo);
 
 	READ_NODE_FIELD(prune_infos);
+	READ_BOOL_FIELD(contains_init_steps);
+	READ_BOOL_FIELD(contains_exec_steps);
 	READ_BITMAPSET_FIELD(other_subplans);
 
 	READ_DONE();
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea..70c5b9d88b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -517,8 +517,11 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
 	result->transientPlan = glob->transientPlan;
 	result->dependsOnRole = glob->dependsOnRole;
 	result->parallelModeNeeded = glob->parallelModeNeeded;
+	result->usesPreExecPruning = glob->usesPreExecPruning;
 	result->planTree = top_plan;
+	result->numPlanNodes = glob->lastPlanNodeId;
 	result->rtable = glob->finalrtable;
+	result->relationRTIs = glob->relationRTIs;
 	result->resultRelations = glob->resultRelations;
 	result->appendRelations = glob->appendRelations;
 	result->subplans = glob->subplans;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index a7b11b7f03..c1b1cf503d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -483,6 +483,7 @@ static void
 add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 {
 	RangeTblEntry *newrte;
+	Index		rti = list_length(glob->finalrtable) + 1;
 
 	/* flat copy to duplicate all the scalar fields */
 	newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
@@ -517,7 +518,10 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 	 * but it would probably cost more cycles than it would save.
 	 */
 	if (newrte->rtekind == RTE_RELATION)
+	{
+		glob->relationRTIs = bms_add_member(glob->relationRTIs, rti);
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
+	}
 }
 
 /*
@@ -1548,6 +1552,9 @@ set_append_references(PlannerInfo *root,
 				pinfo->rtindex += rtoffset;
 			}
 		}
+
+		if (aplan->part_prune_info->contains_init_steps)
+			root->glob->usesPreExecPruning = true;
 	}
 
 	/* We don't need to recurse to lefttree or righttree ... */
@@ -1620,6 +1627,9 @@ set_mergeappend_references(PlannerInfo *root,
 				pinfo->rtindex += rtoffset;
 			}
 		}
+
+		if (mplan->part_prune_info->contains_init_steps)
+			root->glob->usesPreExecPruning = 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 1bc00826c1..390d4e4c06 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 *contains_init_steps,
+										   bool *contains_exec_steps);
 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		contains_init_steps = false;
+	bool		contains_exec_steps = 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_contains_init_steps;
+		bool		partrel_contains_exec_steps;
 
 		pinfolist = make_partitionedrel_pruneinfo(root, parentrel,
 												  prunequal,
 												  partrelids,
 												  relid_subplan_map,
-												  &matchedsubplans);
+												  &matchedsubplans,
+												  &partrel_contains_init_steps,
+												  &partrel_contains_exec_steps);
 
 		/* 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 (!contains_init_steps)
+			contains_init_steps = partrel_contains_init_steps;
+		if (!contains_exec_steps)
+			contains_exec_steps = partrel_contains_exec_steps;
 	}
 
 	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->contains_init_steps = contains_init_steps;
+	pruneinfo->contains_exec_steps = contains_exec_steps;
 
 	/*
 	 * 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.
+ * *contains_init_steps and *contains_exec_steps 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 *contains_init_steps,
+							  bool *contains_exec_steps)
 {
 	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. */
+	*contains_init_steps = false;
+	*contains_exec_steps = 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 (!*contains_init_steps)
+			*contains_init_steps = (initial_pruning_steps != NIL);
+		if (!*contains_exec_steps)
+			*contains_exec_steps = (exec_pruning_steps != NIL);
+
 		pinfolist = lappend(pinfolist, pinfo);
 	}
 
@@ -798,6 +829,7 @@ prune_append_rel_partitions(RelOptInfo *rel)
 
 	/* These are not valid when being called from the planner */
 	context.planstate = NULL;
+	context.exprcontext = NULL;
 	context.exprstates = NULL;
 
 	/* Actual pruning happens here. */
@@ -808,8 +840,8 @@ prune_append_rel_partitions(RelOptInfo *rel)
  * get_matching_partitions
  *		Determine partitions that survive partition pruning
  *
- * Note: context->planstate must be set to a valid PlanState when the
- * pruning_steps were generated with a target other than PARTTARGET_PLANNER.
+ * Note: context->exprcontext must be valid when the pruning_steps were
+ * generated with a target other than PARTTARGET_PLANNER.
  *
  * Returns a Bitmapset of the RelOptInfo->part_rels indexes of the surviving
  * partitions.
@@ -3654,7 +3686,7 @@ match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
  * exprstate array.
  *
  * Note that the evaluated result may be in the per-tuple memory context of
- * context->planstate->ps_ExprContext, and we may have leaked other memory
+ * context->exprcontext, and we may have leaked other memory
  * there too.  This memory must be recovered by resetting that ExprContext
  * after we're done with the pruning operation (see execPartition.c).
  */
@@ -3677,13 +3709,18 @@ partkey_datum_from_expr(PartitionPruneContext *context,
 		ExprContext *ectx;
 
 		/*
-		 * We should never see a non-Const in a step unless we're running in
-		 * the executor.
+		 * We should never see a non-Const in a step unless the caller has
+		 * passed a valid ExprContext.
+		 *
+		 * When context->planstate is valid, context->exprcontext is same
+		 * as context->planstate->ps_ExprContext.
 		 */
-		Assert(context->planstate != NULL);
+		Assert(context->planstate != NULL || context->exprcontext != NULL);
+		Assert(context->planstate == NULL ||
+			   (context->exprcontext == context->planstate->ps_ExprContext));
 
 		exprstate = context->exprstates[stateidx];
-		ectx = context->planstate->ps_ExprContext;
+		ectx = context->exprcontext;
 		*value = ExecEvalExprSwitchContext(exprstate, ectx, isnull);
 	}
 }
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index fda2e9360e..5d8f3fc3cb 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -910,15 +910,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
+ * *execPrepResults 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 **stmt_execprep_list)
 {
 	List	   *stmt_list = NIL;
 	ListCell   *query_list;
 
+	*stmt_execprep_list = NIL;
 	foreach(query_list, querytrees)
 	{
 		Query	   *query = lfirst_node(Query, query_list);
@@ -942,6 +944,7 @@ pg_plan_queries(List *querytrees, const char *query_string, int cursorOptions,
 		}
 
 		stmt_list = lappend(stmt_list, stmt);
+		*stmt_execprep_list = lappend(*stmt_execprep_list, NULL);
 	}
 
 	return stmt_list;
@@ -1045,7 +1048,8 @@ exec_simple_query(const char *query_string)
 		QueryCompletion qc;
 		MemoryContext per_parsetree_context = NULL;
 		List	   *querytree_list,
-				   *plantree_list;
+				   *plantree_list,
+				   *plantree_execprep_list;
 		Portal		portal;
 		DestReceiver *receiver;
 		int16		format;
@@ -1132,7 +1136,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_execprep_list);
 
 		/*
 		 * Done with the snapshot used for parsing/planning.
@@ -1168,6 +1173,7 @@ exec_simple_query(const char *query_string)
 						  query_string,
 						  commandTag,
 						  plantree_list,
+						  plantree_execprep_list,
 						  NULL);
 
 		/*
@@ -1978,6 +1984,7 @@ exec_bind_message(StringInfo input_message)
 					  query_string,
 					  psrc->commandTag,
 					  cplan->stmt_list,
+					  cplan->stmt_execprep_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 5f907831a3..b76aa3ef3b 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, ExecPrepOutput *execprep,
 						 const char *sourceText,
 						 ParamListInfo params,
 						 QueryEnvironment *queryEnv,
@@ -65,6 +65,7 @@ static void DoPortalRewind(Portal portal);
  */
 QueryDesc *
 CreateQueryDesc(PlannedStmt *plannedstmt,
+				ExecPrepOutput *execprep,
 				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->execprep = execprep;		/* ExecutorPrep() 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
+ *	execprep: ExecutorPrep() 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,
+			 ExecPrepOutput *execprep,
 			 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, execprep, sourceText,
 								GetActiveSnapshot(), InvalidSnapshot,
 								dest, params, queryEnv, 0);
 
@@ -490,6 +494,7 @@ PortalStart(Portal portal, ParamListInfo params,
 				 * the destination to DestNone.
 				 */
 				queryDesc = CreateQueryDesc(linitial_node(PlannedStmt, portal->stmts),
+											linitial_node(ExecPrepOutput, portal->stmt_execpreps),
 											portal->sourceText,
 											GetActiveSnapshot(),
 											InvalidSnapshot,
@@ -1190,7 +1195,8 @@ PortalRunMulti(Portal portal,
 			   QueryCompletion *qc)
 {
 	bool		active_snapshot_set = false;
-	ListCell   *stmtlist_item;
+	ListCell   *stmtlist_item,
+			   *execpreplist_item;
 
 	/*
 	 * If the destination is DestRemoteExecute, change to DestNone.  The
@@ -1211,9 +1217,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,
+			execpreplist_item, portal->stmt_execpreps)
 	{
 		PlannedStmt *pstmt = lfirst_node(PlannedStmt, stmtlist_item);
+		ExecPrepOutput *execprep = lfirst_node(ExecPrepOutput,
+											   execpreplist_item);
 
 		/*
 		 * If we got a cancel signal in prior command, quit
@@ -1271,7 +1280,7 @@ PortalRunMulti(Portal portal,
 			if (pstmt->canSetTag)
 			{
 				/* statement can set tag string */
-				ProcessQuery(pstmt,
+				ProcessQuery(pstmt, execprep,
 							 portal->sourceText,
 							 portal->portalParams,
 							 portal->queryEnv,
@@ -1280,7 +1289,7 @@ PortalRunMulti(Portal portal,
 			else
 			{
 				/* stmt added by rewrite cannot set tag */
-				ProcessQuery(pstmt,
+				ProcessQuery(pstmt, execprep,
 							 portal->sourceText,
 							 portal->portalParams,
 							 portal->queryEnv,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 4a9055e6bb..221738dddc 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -58,12 +58,14 @@
 
 #include "access/transam.h"
 #include "catalog/namespace.h"
+#include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
+#include "partitioning/partdesc.h"
 #include "storage/lmgr.h"
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
@@ -99,14 +101,15 @@ 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 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, bool acquire,
+								 ParamListInfo boundParams);
 static void AcquirePlannerLocks(List *stmt_list, bool acquire);
 static void ScanQueryForLocks(Query *parsetree, bool acquire);
 static bool ScanQueryWalker(Node *node, bool *acquire);
@@ -782,6 +785,47 @@ RevalidateCachedQuery(CachedPlanSource *plansource,
 	return tlist;
 }
 
+/*
+ * CachedPlanSaveExecPrepOutputs
+ *		Save the list containing ExecPrepOutput nodes in the given CachedPlan
+ *
+ * The provided list is copied into a dedicated context that is a child of
+ * plan->context.
+ */
+static void
+CachedPlanSaveExecPrepOutputs(CachedPlan *plan, List *execprep_list)
+{
+	MemoryContext	execprep_context = plan->execprep_context,
+					oldcontext = CurrentMemoryContext;
+	List		   *execprep_list_copy;
+
+	/*
+	 * Set up the dedicated context if not already done, saving it as a child
+	 * of the CachedPlan's context.
+	 */
+	if (execprep_context == NULL)
+	{
+		execprep_context = AllocSetContextCreate(CurrentMemoryContext,
+												 "CachedPlan execprep list",
+												 ALLOCSET_START_SMALL_SIZES);
+		MemoryContextSetParent(execprep_context, plan->context);
+		MemoryContextSetIdentifier(execprep_context, plan->context->ident);
+		plan->execprep_context = execprep_context;
+	}
+	else
+	{
+		/* Just lear existing contents by resetting the context. */
+		Assert(MemoryContextIsValid(execprep_context));
+		MemoryContextReset(execprep_context);
+	}
+
+	MemoryContextSwitchTo(execprep_context);
+	execprep_list_copy = copyObject(execprep_list);
+	MemoryContextSwitchTo(oldcontext);
+
+	plan->stmt_execprep_list = execprep_list_copy;
+}
+
 /*
  * CheckCachedPlan: see if the CachedPlanSource's generic plan is valid.
  *
@@ -790,9 +834,16 @@ 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 prepares the PlannedStmts contained in it
+ * for execution by invoking ExecutorPrep() on each.  Resulting ExecPrepOutput
+ * nodes, allocated in a child context of the context containing the plan
+ * itself, are added into plan->stmt_execprep_list.  ExecPrepOutput nodes that
+ * may be present in the list from the last invocation of CheckCachedPlan() on
+ * the same CachedPlan are deleted.
  */
 static bool
-CheckCachedPlan(CachedPlanSource *plansource)
+CheckCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams)
 {
 	CachedPlan *plan = plansource->gplan;
 
@@ -820,13 +871,22 @@ CheckCachedPlan(CachedPlanSource *plansource)
 	 */
 	if (plan->is_valid)
 	{
+		List   *execprep_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);
+		/*
+		 * Take executor locks on the plan tree and perform other
+		 * preparatatory actions on it by invoking ExecutorPrep().  A list of
+		 * ExecPrepOutput nodes is generated as result which is saved in the
+		 * CachedPlan.
+		 */
+		execprep_list = AcquireExecutorLocks(plan->stmt_list, true, boundParams);
+		CachedPlanSaveExecPrepOutputs(plan, execprep_list);
 
 		/*
 		 * If plan was transient, check to see if TransactionXmin has
@@ -848,7 +908,7 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		}
 
 		/* Oops, the race case happened.  Release useless locks. */
-		AcquireExecutorLocks(plan->stmt_list, false);
+		(void) AcquireExecutorLocks(plan->stmt_list, false, boundParams);
 	}
 
 	/*
@@ -880,7 +940,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 				ParamListInfo boundParams, QueryEnvironment *queryEnv)
 {
 	CachedPlan *plan;
-	List	   *plist;
+	List	   *plist,
+			   *execprep_list;
 	bool		snapshot_set;
 	bool		is_transient;
 	MemoryContext plan_context;
@@ -933,7 +994,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,
+							&execprep_list);
 
 	/* Release snapshot if we got one */
 	if (snapshot_set)
@@ -1002,6 +1064,11 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_saved = false;
 	plan->is_valid = true;
 
+	/* Save the dummy ExecPrepOutput list. */
+	plan->execprep_context = NULL;
+	CachedPlanSaveExecPrepOutputs(plan, execprep_list);
+	Assert(MemoryContextIsValid(plan->execprep_context));
+
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
 
@@ -1160,7 +1227,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;
@@ -1366,7 +1433,6 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 	foreach(lc, plan->stmt_list)
 	{
 		PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc);
-		ListCell   *lc2;
 
 		if (plannedstmt->commandType == CMD_UTILITY)
 			return false;
@@ -1375,13 +1441,8 @@ CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
 		 * We have to grovel through the rtable because it's likely to contain
 		 * an RTE_RESULT relation, rather than being totally empty.
 		 */
-		foreach(lc2, plannedstmt->rtable)
-		{
-			RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
-
-			if (rte->rtekind == RTE_RELATION)
-				return false;
-		}
+		if (!bms_is_empty(plannedstmt->relationRTIs))
+			return false;
 	}
 
 	/*
@@ -1738,16 +1799,22 @@ QueryListGetPrimaryStmt(List *stmts)
 /*
  * AcquireExecutorLocks: acquire locks needed for execution of a cached plan;
  * or release them if acquire is false.
+ *
+ * Returns a list of ExecPrepOutput nodes containing one element for each
+ * PlannedStmt in stmt_list; NULL if the latter is utility statement.
  */
-static void
-AcquireExecutorLocks(List *stmt_list, bool acquire)
+static List *
+AcquireExecutorLocks(List *stmt_list, bool acquire, ParamListInfo boundParams)
 {
 	ListCell   *lc1;
+	List	   *stmt_execprep_list = NIL;
 
 	foreach(lc1, stmt_list)
 	{
 		PlannedStmt *plannedstmt = lfirst_node(PlannedStmt, lc1);
-		ListCell   *lc2;
+		ExecPrepContext *context;
+		ExecPrepOutput *execprep = NULL;
+		int			rti;
 
 		if (plannedstmt->commandType == CMD_UTILITY)
 		{
@@ -1762,28 +1829,46 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
 
 			if (query)
 				ScanQueryForLocks(query, acquire);
-			continue;
 		}
-
-		foreach(lc2, plannedstmt->rtable)
+		else
 		{
-			RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
-
-			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.
+			 * Prep the plan tree for execution.
 			 */
-			if (acquire)
-				LockRelationOid(rte->relid, rte->rellockmode);
-			else
-				UnlockRelationOid(rte->relid, rte->rellockmode);
+			context = makeNode(ExecPrepContext);
+			context->stmt = plannedstmt;
+			context->params = boundParams;
+			execprep = ExecutorPrep(context);
+
+			rti = -1;
+			while ((rti = bms_next_member(execprep->relationRTIs, rti)) >= 0)
+			{
+				RangeTblEntry *rte = rt_fetch(rti, plannedstmt->rtable);
+
+				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.
+				 */
+				if (acquire)
+					LockRelationOid(rte->relid, rte->rellockmode);
+				else
+					UnlockRelationOid(rte->relid, rte->rellockmode);
+			}
 		}
+
+		/*
+		 * Keep the invariant that stmt_execprep_list is same length as
+		 * stmt_list.
+		 */
+		stmt_execprep_list = lappend(stmt_execprep_list, execprep);
 	}
+
+	return stmt_execprep_list;
 }
 
 /*
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 236f450a2b..5cf1339ffd 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -284,6 +284,7 @@ PortalDefineQuery(Portal portal,
 				  const char *sourceText,
 				  CommandTag commandTag,
 				  List *stmts,
+				  List *stmt_execpreps,
 				  CachedPlan *cplan)
 {
 	AssertArg(PortalIsValid(portal));
@@ -298,6 +299,7 @@ PortalDefineQuery(Portal portal,
 	portal->qc.nprocessed = 0;
 	portal->commandTag = commandTag;
 	portal->stmts = stmts;
+	portal->stmt_execpreps = stmt_execpreps;
 	portal->cplan = cplan;
 	portal->status = PORTAL_DEFINED;
 }
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 666977fb1f..f553649a5d 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, ExecPrepOutput *execprep,
+						   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 603d8becc4..785a09f15f 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -119,10 +119,21 @@ extern ResultRelInfo *ExecFindPartition(ModifyTableState *mtstate,
 										EState *estate);
 extern void ExecCleanupTupleRouting(ModifyTableState *mtstate,
 									PartitionTupleRouting *proute);
+
 extern PartitionPruneState *ExecCreatePartitionPruneState(PlanState *planstate,
-														  PartitionPruneInfo *partitionpruneinfo);
-extern Bitmapset *ExecFindMatchingSubPlans(PartitionPruneState *prunestate);
+														  PartitionPruneInfo *partitionpruneinfo,
+														  bool consider_initial_steps,
+														  bool consider_exec_steps,
+														  List *rtable, ExprContext *econtext,
+														  PartitionDirectory partdir);
 extern Bitmapset *ExecFindInitialMatchingSubPlans(PartitionPruneState *prunestate,
-												  int nsubplans);
-
+								PartitionPruneInfo *pruneinfo,
+								Bitmapset **parentrelids);
+extern Bitmapset *ExecFindMatchingSubPlans(PartitionPruneState *prunestate);
+extern Bitmapset *ExecInitPartitionPruning(PlanState *planstate, int n_total_subplans,
+						 PartitionPruneInfo *pruneinfo,
+						 PartitionPruneState **prunestate);
+extern Bitmapset *ExecPrepDoInitialPruning(PartitionPruneInfo *pruneinfo,
+						 List *rtable, ParamListInfo params,
+						 Bitmapset **parentrelids);
 #endif							/* EXECPARTITION_H */
diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h
index e79e2c001f..491ceef401 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) */
+	ExecPrepOutput *execprep;	/* ExecutorPrep()'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,
+								  ExecPrepOutput *execprep,
 								  const char *sourceText,
 								  Snapshot snapshot,
 								  Snapshot crosscheck_snapshot,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 344399f6a8..627cb19a4c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -185,6 +185,7 @@ ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull)
 /*
  * prototypes from functions in execMain.c
  */
+extern ExecPrepOutput *ExecutorPrep(ExecPrepContext *context);
 extern void ExecutorStart(QueryDesc *queryDesc, int eflags);
 extern void standard_ExecutorStart(QueryDesc *queryDesc, int eflags);
 extern void ExecutorRun(QueryDesc *queryDesc,
@@ -233,6 +234,8 @@ extern void EvalPlanQualEnd(EPQState *epqstate);
 /*
  * functions in execProcnode.c
  */
+extern void ExecPrepNode(Plan *node, ExecPrepContext *context,
+						 ExecPrepOutput *result);
 extern PlanState *ExecInitNode(Plan *node, EState *estate, int eflags);
 extern void ExecSetExecProcNode(PlanState *node, ExecProcNodeMtd function);
 extern Node *MultiExecProcNode(PlanState *node);
diff --git a/src/include/executor/nodeAgg.h b/src/include/executor/nodeAgg.h
index 4d1bd92999..2dd7570067 100644
--- a/src/include/executor/nodeAgg.h
+++ b/src/include/executor/nodeAgg.h
@@ -314,6 +314,7 @@ typedef struct AggStatePerHashData
 }			AggStatePerHashData;
 
 
+extern void ExecPrepAgg(Agg *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern AggState *ExecInitAgg(Agg *node, EState *estate, int eflags);
 extern void ExecEndAgg(AggState *node);
 extern void ExecReScanAgg(AggState *node);
diff --git a/src/include/executor/nodeAppend.h b/src/include/executor/nodeAppend.h
index 4cb78ee5b6..85bc9d30a6 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 void ExecPrepAppend(Append *node, ExecPrepContext *context, ExecPrepOutput *execprep);
 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/nodeBitmapAnd.h b/src/include/executor/nodeBitmapAnd.h
index bae6a83826..aafb10a2aa 100644
--- a/src/include/executor/nodeBitmapAnd.h
+++ b/src/include/executor/nodeBitmapAnd.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepBitmapAnd(BitmapAnd *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern BitmapAndState *ExecInitBitmapAnd(BitmapAnd *node, EState *estate, int eflags);
 extern Node *MultiExecBitmapAnd(BitmapAndState *node);
 extern void ExecEndBitmapAnd(BitmapAndState *node);
diff --git a/src/include/executor/nodeBitmapHeapscan.h b/src/include/executor/nodeBitmapHeapscan.h
index 789522cb8d..7240d9fa93 100644
--- a/src/include/executor/nodeBitmapHeapscan.h
+++ b/src/include/executor/nodeBitmapHeapscan.h
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepBitmapHeapScan(BitmapHeapScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern BitmapHeapScanState *ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags);
 extern void ExecEndBitmapHeapScan(BitmapHeapScanState *node);
 extern void ExecReScanBitmapHeapScan(BitmapHeapScanState *node);
diff --git a/src/include/executor/nodeBitmapIndexscan.h b/src/include/executor/nodeBitmapIndexscan.h
index 01fb6ef536..6759724c2e 100644
--- a/src/include/executor/nodeBitmapIndexscan.h
+++ b/src/include/executor/nodeBitmapIndexscan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepBitmapIndexScan(BitmapIndexScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern BitmapIndexScanState *ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate, int eflags);
 extern Node *MultiExecBitmapIndexScan(BitmapIndexScanState *node);
 extern void ExecEndBitmapIndexScan(BitmapIndexScanState *node);
diff --git a/src/include/executor/nodeBitmapOr.h b/src/include/executor/nodeBitmapOr.h
index ad90812cc1..66ddc18f63 100644
--- a/src/include/executor/nodeBitmapOr.h
+++ b/src/include/executor/nodeBitmapOr.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepBitmapOr(BitmapOr *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern BitmapOrState *ExecInitBitmapOr(BitmapOr *node, EState *estate, int eflags);
 extern Node *MultiExecBitmapOr(BitmapOrState *node);
 extern void ExecEndBitmapOr(BitmapOrState *node);
diff --git a/src/include/executor/nodeCtescan.h b/src/include/executor/nodeCtescan.h
index 317d142b16..7908ae51df 100644
--- a/src/include/executor/nodeCtescan.h
+++ b/src/include/executor/nodeCtescan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepCteScan(CteScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern CteScanState *ExecInitCteScan(CteScan *node, EState *estate, int eflags);
 extern void ExecEndCteScan(CteScanState *node);
 extern void ExecReScanCteScan(CteScanState *node);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
index 5ef890144f..8c1d05f64b 100644
--- a/src/include/executor/nodeCustom.h
+++ b/src/include/executor/nodeCustom.h
@@ -18,6 +18,7 @@
 /*
  * General executor code
  */
+extern void ExecPrepCustomScan(CustomScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern CustomScanState *ExecInitCustomScan(CustomScan *cscan,
 										   EState *estate, int eflags);
 extern void ExecEndCustomScan(CustomScanState *node);
diff --git a/src/include/executor/nodeForeignscan.h b/src/include/executor/nodeForeignscan.h
index c9fbaed79c..a2d6667011 100644
--- a/src/include/executor/nodeForeignscan.h
+++ b/src/include/executor/nodeForeignscan.h
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepForeignScan(ForeignScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern ForeignScanState *ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags);
 extern void ExecEndForeignScan(ForeignScanState *node);
 extern void ExecReScanForeignScan(ForeignScanState *node);
diff --git a/src/include/executor/nodeFunctionscan.h b/src/include/executor/nodeFunctionscan.h
index 7a598a1d46..8686bb5c09 100644
--- a/src/include/executor/nodeFunctionscan.h
+++ b/src/include/executor/nodeFunctionscan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepFunctionScan(FunctionScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern FunctionScanState *ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags);
 extern void ExecEndFunctionScan(FunctionScanState *node);
 extern void ExecReScanFunctionScan(FunctionScanState *node);
diff --git a/src/include/executor/nodeGather.h b/src/include/executor/nodeGather.h
index 29829ffe9a..206185ffbc 100644
--- a/src/include/executor/nodeGather.h
+++ b/src/include/executor/nodeGather.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepGather(Gather *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern GatherState *ExecInitGather(Gather *node, EState *estate, int eflags);
 extern void ExecEndGather(GatherState *node);
 extern void ExecShutdownGather(GatherState *node);
diff --git a/src/include/executor/nodeGatherMerge.h b/src/include/executor/nodeGatherMerge.h
index d724d5fea4..b124a3fe99 100644
--- a/src/include/executor/nodeGatherMerge.h
+++ b/src/include/executor/nodeGatherMerge.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepGatherMerge(GatherMerge *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern GatherMergeState *ExecInitGatherMerge(GatherMerge *node,
 											 EState *estate,
 											 int eflags);
diff --git a/src/include/executor/nodeGroup.h b/src/include/executor/nodeGroup.h
index 816ed2c099..7e86abab01 100644
--- a/src/include/executor/nodeGroup.h
+++ b/src/include/executor/nodeGroup.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepGroup(Group *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern GroupState *ExecInitGroup(Group *node, EState *estate, int eflags);
 extern void ExecEndGroup(GroupState *node);
 extern void ExecReScanGroup(GroupState *node);
diff --git a/src/include/executor/nodeHash.h b/src/include/executor/nodeHash.h
index e1e0dec24b..1426a6e9a1 100644
--- a/src/include/executor/nodeHash.h
+++ b/src/include/executor/nodeHash.h
@@ -19,6 +19,7 @@
 
 struct SharedHashJoinBatch;
 
+extern void ExecPrepHash(Hash *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern HashState *ExecInitHash(Hash *node, EState *estate, int eflags);
 extern Node *MultiExecHash(HashState *node);
 extern void ExecEndHash(HashState *node);
diff --git a/src/include/executor/nodeHashjoin.h b/src/include/executor/nodeHashjoin.h
index b3b5a2c3f2..6dc88282d4 100644
--- a/src/include/executor/nodeHashjoin.h
+++ b/src/include/executor/nodeHashjoin.h
@@ -18,6 +18,7 @@
 #include "nodes/execnodes.h"
 #include "storage/buffile.h"
 
+extern void ExecPrepHashJoin(HashJoin *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern HashJoinState *ExecInitHashJoin(HashJoin *node, EState *estate, int eflags);
 extern void ExecEndHashJoin(HashJoinState *node);
 extern void ExecReScanHashJoin(HashJoinState *node);
diff --git a/src/include/executor/nodeIncrementalSort.h b/src/include/executor/nodeIncrementalSort.h
index 84cfd96b13..e909cb784b 100644
--- a/src/include/executor/nodeIncrementalSort.h
+++ b/src/include/executor/nodeIncrementalSort.h
@@ -15,6 +15,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepIncrementalSort(IncrementalSort *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern IncrementalSortState *ExecInitIncrementalSort(IncrementalSort *node, EState *estate, int eflags);
 extern void ExecEndIncrementalSort(IncrementalSortState *node);
 extern void ExecReScanIncrementalSort(IncrementalSortState *node);
diff --git a/src/include/executor/nodeIndexonlyscan.h b/src/include/executor/nodeIndexonlyscan.h
index 47b03950ea..d0aca7a303 100644
--- a/src/include/executor/nodeIndexonlyscan.h
+++ b/src/include/executor/nodeIndexonlyscan.h
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepIndexOnlyScan(IndexOnlyScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern IndexOnlyScanState *ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags);
 extern void ExecEndIndexOnlyScan(IndexOnlyScanState *node);
 extern void ExecIndexOnlyMarkPos(IndexOnlyScanState *node);
diff --git a/src/include/executor/nodeIndexscan.h b/src/include/executor/nodeIndexscan.h
index 0a075f9aea..d57c370466 100644
--- a/src/include/executor/nodeIndexscan.h
+++ b/src/include/executor/nodeIndexscan.h
@@ -18,6 +18,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepIndexScan(IndexScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern IndexScanState *ExecInitIndexScan(IndexScan *node, EState *estate, int eflags);
 extern void ExecEndIndexScan(IndexScanState *node);
 extern void ExecIndexMarkPos(IndexScanState *node);
diff --git a/src/include/executor/nodeLimit.h b/src/include/executor/nodeLimit.h
index 6da0c4026c..05d7e4797b 100644
--- a/src/include/executor/nodeLimit.h
+++ b/src/include/executor/nodeLimit.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepLimit(Limit *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern LimitState *ExecInitLimit(Limit *node, EState *estate, int eflags);
 extern void ExecEndLimit(LimitState *node);
 extern void ExecReScanLimit(LimitState *node);
diff --git a/src/include/executor/nodeLockRows.h b/src/include/executor/nodeLockRows.h
index 125a32b608..157d4a7f0e 100644
--- a/src/include/executor/nodeLockRows.h
+++ b/src/include/executor/nodeLockRows.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepLockRows(LockRows *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern LockRowsState *ExecInitLockRows(LockRows *node, EState *estate, int eflags);
 extern void ExecEndLockRows(LockRowsState *node);
 extern void ExecReScanLockRows(LockRowsState *node);
diff --git a/src/include/executor/nodeMaterial.h b/src/include/executor/nodeMaterial.h
index 21a6860a1a..9b70d6e97b 100644
--- a/src/include/executor/nodeMaterial.h
+++ b/src/include/executor/nodeMaterial.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepMaterial(Material *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern MaterialState *ExecInitMaterial(Material *node, EState *estate, int eflags);
 extern void ExecEndMaterial(MaterialState *node);
 extern void ExecMaterialMarkPos(MaterialState *node);
diff --git a/src/include/executor/nodeMemoize.h b/src/include/executor/nodeMemoize.h
index 4643163dc7..53a784f012 100644
--- a/src/include/executor/nodeMemoize.h
+++ b/src/include/executor/nodeMemoize.h
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepMemoize(Memoize *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern MemoizeState *ExecInitMemoize(Memoize *node, EState *estate, int eflags);
 extern void ExecEndMemoize(MemoizeState *node);
 extern void ExecReScanMemoize(MemoizeState *node);
diff --git a/src/include/executor/nodeMergeAppend.h b/src/include/executor/nodeMergeAppend.h
index 97fe3b0665..60a9136de6 100644
--- a/src/include/executor/nodeMergeAppend.h
+++ b/src/include/executor/nodeMergeAppend.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepMergeAppend(MergeAppend *node, ExecPrepContext *context, ExecPrepOutput *result);
 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/nodeMergejoin.h b/src/include/executor/nodeMergejoin.h
index 26ab517508..29553d5dd0 100644
--- a/src/include/executor/nodeMergejoin.h
+++ b/src/include/executor/nodeMergejoin.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepMergeJoin(MergeJoin *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern MergeJoinState *ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags);
 extern void ExecEndMergeJoin(MergeJoinState *node);
 extern void ExecReScanMergeJoin(MergeJoinState *node);
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 1d225bc88d..4b1846f8ff 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 void ExecPrepModifyTable(ModifyTable *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
diff --git a/src/include/executor/nodeNamedtuplestorescan.h b/src/include/executor/nodeNamedtuplestorescan.h
index d595124e54..964afcd816 100644
--- a/src/include/executor/nodeNamedtuplestorescan.h
+++ b/src/include/executor/nodeNamedtuplestorescan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepNamedTuplestoreScan(NamedTuplestoreScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern NamedTuplestoreScanState *ExecInitNamedTuplestoreScan(NamedTuplestoreScan *node, EState *estate, int eflags);
 extern void ExecEndNamedTuplestoreScan(NamedTuplestoreScanState *node);
 extern void ExecReScanNamedTuplestoreScan(NamedTuplestoreScanState *node);
diff --git a/src/include/executor/nodeNestloop.h b/src/include/executor/nodeNestloop.h
index b1411faf57..13ea4cc870 100644
--- a/src/include/executor/nodeNestloop.h
+++ b/src/include/executor/nodeNestloop.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepNestLoop(NestLoop *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern NestLoopState *ExecInitNestLoop(NestLoop *node, EState *estate, int eflags);
 extern void ExecEndNestLoop(NestLoopState *node);
 extern void ExecReScanNestLoop(NestLoopState *node);
diff --git a/src/include/executor/nodeProjectSet.h b/src/include/executor/nodeProjectSet.h
index 2c2b58282c..c9b44356ba 100644
--- a/src/include/executor/nodeProjectSet.h
+++ b/src/include/executor/nodeProjectSet.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepProjectSet(ProjectSet *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern ProjectSetState *ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags);
 extern void ExecEndProjectSet(ProjectSetState *node);
 extern void ExecReScanProjectSet(ProjectSetState *node);
diff --git a/src/include/executor/nodeRecursiveunion.h b/src/include/executor/nodeRecursiveunion.h
index 2d20470da2..7b7585d594 100644
--- a/src/include/executor/nodeRecursiveunion.h
+++ b/src/include/executor/nodeRecursiveunion.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepRecursiveUnion(RecursiveUnion *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern RecursiveUnionState *ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags);
 extern void ExecEndRecursiveUnion(RecursiveUnionState *node);
 extern void ExecReScanRecursiveUnion(RecursiveUnionState *node);
diff --git a/src/include/executor/nodeResult.h b/src/include/executor/nodeResult.h
index ebb131d265..998a50ae27 100644
--- a/src/include/executor/nodeResult.h
+++ b/src/include/executor/nodeResult.h
@@ -16,6 +16,8 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepResult(Result *node, ExecPrepContext *context, ExecPrepOutput *result);
+extern ResultState *ExecInitResult(Result *node, EState *estate, int eflags);
 extern ResultState *ExecInitResult(Result *node, EState *estate, int eflags);
 extern void ExecEndResult(ResultState *node);
 extern void ExecResultMarkPos(ResultState *node);
diff --git a/src/include/executor/nodeSamplescan.h b/src/include/executor/nodeSamplescan.h
index 340b41a427..c0dd45b8bc 100644
--- a/src/include/executor/nodeSamplescan.h
+++ b/src/include/executor/nodeSamplescan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepSampleScan(SampleScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern SampleScanState *ExecInitSampleScan(SampleScan *node, EState *estate, int eflags);
 extern void ExecEndSampleScan(SampleScanState *node);
 extern void ExecReScanSampleScan(SampleScanState *node);
diff --git a/src/include/executor/nodeSeqscan.h b/src/include/executor/nodeSeqscan.h
index c225ba6e04..5452742622 100644
--- a/src/include/executor/nodeSeqscan.h
+++ b/src/include/executor/nodeSeqscan.h
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepSeqScan(SeqScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern SeqScanState *ExecInitSeqScan(SeqScan *node, EState *estate, int eflags);
 extern void ExecEndSeqScan(SeqScanState *node);
 extern void ExecReScanSeqScan(SeqScanState *node);
diff --git a/src/include/executor/nodeSetOp.h b/src/include/executor/nodeSetOp.h
index a504cf8613..bc80011513 100644
--- a/src/include/executor/nodeSetOp.h
+++ b/src/include/executor/nodeSetOp.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepSetOp(SetOp *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern SetOpState *ExecInitSetOp(SetOp *node, EState *estate, int eflags);
 extern void ExecEndSetOp(SetOpState *node);
 extern void ExecReScanSetOp(SetOpState *node);
diff --git a/src/include/executor/nodeSort.h b/src/include/executor/nodeSort.h
index 008e6a6bc6..def930a8bc 100644
--- a/src/include/executor/nodeSort.h
+++ b/src/include/executor/nodeSort.h
@@ -17,6 +17,7 @@
 #include "access/parallel.h"
 #include "nodes/execnodes.h"
 
+extern void ExecPrepSort(Sort *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern SortState *ExecInitSort(Sort *node, EState *estate, int eflags);
 extern void ExecEndSort(SortState *node);
 extern void ExecSortMarkPos(SortState *node);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 75cc6d5104..f6e21007fa 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepSubPlan(SubPlan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern SubPlanState *ExecInitSubPlan(SubPlan *subplan, PlanState *parent);
 
 extern Datum ExecSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull);
diff --git a/src/include/executor/nodeSubqueryscan.h b/src/include/executor/nodeSubqueryscan.h
index a09e2be423..3fbf053e04 100644
--- a/src/include/executor/nodeSubqueryscan.h
+++ b/src/include/executor/nodeSubqueryscan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepSubqueryScan(SubqueryScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern SubqueryScanState *ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags);
 extern void ExecEndSubqueryScan(SubqueryScanState *node);
 extern void ExecReScanSubqueryScan(SubqueryScanState *node);
diff --git a/src/include/executor/nodeTableFuncscan.h b/src/include/executor/nodeTableFuncscan.h
index 2b82e7d7ed..ba2e7774f1 100644
--- a/src/include/executor/nodeTableFuncscan.h
+++ b/src/include/executor/nodeTableFuncscan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepTableFuncScan(TableFuncScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern TableFuncScanState *ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags);
 extern void ExecEndTableFuncScan(TableFuncScanState *node);
 extern void ExecReScanTableFuncScan(TableFuncScanState *node);
diff --git a/src/include/executor/nodeTidrangescan.h b/src/include/executor/nodeTidrangescan.h
index f122e09583..333cfbb5c6 100644
--- a/src/include/executor/nodeTidrangescan.h
+++ b/src/include/executor/nodeTidrangescan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepTidRangeScan(TidRangeScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern TidRangeScanState *ExecInitTidRangeScan(TidRangeScan *node,
 											   EState *estate, int eflags);
 extern void ExecEndTidRangeScan(TidRangeScanState *node);
diff --git a/src/include/executor/nodeTidscan.h b/src/include/executor/nodeTidscan.h
index 91a5f89f42..188f3f3f97 100644
--- a/src/include/executor/nodeTidscan.h
+++ b/src/include/executor/nodeTidscan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepTidScan(TidScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern TidScanState *ExecInitTidScan(TidScan *node, EState *estate, int eflags);
 extern void ExecEndTidScan(TidScanState *node);
 extern void ExecReScanTidScan(TidScanState *node);
diff --git a/src/include/executor/nodeUnique.h b/src/include/executor/nodeUnique.h
index 61f09d9853..970e894681 100644
--- a/src/include/executor/nodeUnique.h
+++ b/src/include/executor/nodeUnique.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepUnique(Unique *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern UniqueState *ExecInitUnique(Unique *node, EState *estate, int eflags);
 extern void ExecEndUnique(UniqueState *node);
 extern void ExecReScanUnique(UniqueState *node);
diff --git a/src/include/executor/nodeValuesscan.h b/src/include/executor/nodeValuesscan.h
index 07c13ef123..f08bb080eb 100644
--- a/src/include/executor/nodeValuesscan.h
+++ b/src/include/executor/nodeValuesscan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepValuesScan(ValuesScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern ValuesScanState *ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags);
 extern void ExecEndValuesScan(ValuesScanState *node);
 extern void ExecReScanValuesScan(ValuesScanState *node);
diff --git a/src/include/executor/nodeWindowAgg.h b/src/include/executor/nodeWindowAgg.h
index 4e62c8936d..a4d8487aba 100644
--- a/src/include/executor/nodeWindowAgg.h
+++ b/src/include/executor/nodeWindowAgg.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepWindowAgg(WindowAgg *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern WindowAggState *ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags);
 extern void ExecEndWindowAgg(WindowAggState *node);
 extern void ExecReScanWindowAgg(WindowAggState *node);
diff --git a/src/include/executor/nodeWorktablescan.h b/src/include/executor/nodeWorktablescan.h
index 17842de576..5f7f76ec85 100644
--- a/src/include/executor/nodeWorktablescan.h
+++ b/src/include/executor/nodeWorktablescan.h
@@ -16,6 +16,7 @@
 
 #include "nodes/execnodes.h"
 
+extern void ExecPrepWorkTableScan(WorkTableScan *node, ExecPrepContext *context, ExecPrepOutput *result);
 extern WorkTableScanState *ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags);
 extern void ExecEndWorkTableScan(WorkTableScanState *node);
 extern void ExecReScanWorkTableScan(WorkTableScanState *node);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index dd95dc40c7..7b03f46966 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -570,6 +570,8 @@ 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 ExecPrepOutput *es_execprep;	/* link to ExecPrepOutput, if one was
+										 * passed to ExecutorStart() */
 	const char *es_sourceText;	/* Source text from QueryDesc */
 
 	JunkFilter *es_junkFilter;	/* top-level junk filter, if any */
@@ -958,6 +960,82 @@ typedef struct DomainConstraintState
  */
 typedef TupleTableSlot *(*ExecProcNodeMtd) (struct PlanState *pstate);
 
+/*----------------
+ * ExecPrepContext
+ *
+ * Context information for performing ExecutorPrep() on a given plan
+ */
+typedef struct ExecPrepContext
+{
+	NodeTag		type;
+
+	PlannedStmt	   *stmt;		/* target plan */
+	ParamListInfo	params;		/* EXTERN parameters to prune with */
+} ExecPrepContext;
+
+/*----------------
+ * ExecPrepOutput
+ *
+ * Result of of performing ExecutorPrep() for a given PlannedStmt
+ */
+typedef struct ExecPrepOutput
+{
+	NodeTag		type;
+
+	Bitmapset  *relationRTIs;		/* RT indexes of RTE_RELATIONs */
+	int			numPlanNodes;		/* PlannedStmt.numPlanNodes */
+
+	/*
+	 * Array of 'numPlanNodes' elements containing PlanPrepOutput nodes
+	 * for each node in the plan tree, indexed using  the node's plan_node_id.
+	 * A NULL value means that the corresponding plan node does not have a
+	 * PlanPrepOutput associated with it.
+	 */
+	struct PlanPrepOutput **planPrepResults;
+} ExecPrepOutput;
+
+#define	ExecPrepStorePlanPrepOutput(execprep, planPrepResult, plannode) \
+	(execprep)->planPrepResults[(plannode)->plan_node_id] = (planPrepResult)
+
+#define	ExecPrepFetchPlanPrepOutput(execprep, plannode) \
+		((execprep) != NULL ? \
+		 (execprep)->planPrepResults[(plannode)->plan_node_id] : NULL)
+
+#ifdef USE_ASSERT_CHECKING
+#define	EXEC_PREP_OUTPUT_SANITY(plannode, estate) \
+	do { \
+		PlanPrepOutput *planPrepOutput = \
+		ExecPrepFetchPlanPrepOutput(estate->es_execprep, node); \
+		Assert(planPrepOutput == NULL || \
+			   (IsA(planPrepOutput, PlanPrepOutput) && \
+				planPrepOutput->plan_node_id == plannode->plan_node_id)); \
+	} while (0);
+#else
+#define	EXEC_PREP_OUTPUT_SANITY(node, estate)
+#endif
+
+/* ---------------
+ * PlanPrepOutput
+ *
+ * ExecutorPrep() creates a node of this type for every node in the Plan tree
+ * that does some "prep" work.
+ */
+typedef struct PlanPrepOutput
+{
+	NodeTag		type;
+
+	int			plan_node_id;		/* associated Plan node */
+
+	/* Information collected by ExecPrepNode subroutine for the node */
+
+	/*
+	 * For nodes that contain a list of prunable subnodes, the following
+	 * contains offsets into that list, of the subnodes that survive initial
+	 * partition pruning.
+	 */
+	Bitmapset  *initially_valid_subnodes;
+} PlanPrepOutput;
+
 /* ----------------
  *		PlanState node
  *
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index 93c60bde66..fca107ad65 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -158,5 +158,8 @@ extern bool raw_expression_tree_walker(Node *node, bool (*walker) (),
 struct PlanState;
 extern bool planstate_tree_walker(struct PlanState *planstate, bool (*walker) (),
 								  void *context);
+struct Plan;
+extern bool plan_tree_walker(struct Plan *plan, bool (*walker) (),
+				 void *context);
 
 #endif							/* NODEFUNCS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index da35f2c272..8db017a138 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -96,6 +96,11 @@ typedef enum NodeTag
 	T_PartitionPruneStepCombine,
 	T_PlanInvalItem,
 
+	/* TAGS FOR EXECUTOR PREP NODES (execnodes.h) */
+	T_ExecPrepContext,
+	T_ExecPrepOutput,
+	T_PlanPrepOutput,
+
 	/*
 	 * TAGS FOR PLAN STATE NODES (execnodes.h)
 	 *
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fe..ffde93ef13 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -101,6 +101,9 @@ typedef struct PlannerGlobal
 
 	List	   *finalrtable;	/* "flat" rangetable for executor */
 
+	Bitmapset  *relationRTIs;	/* Indexes of RTE_RELATION entries in range
+								 * table */
+
 	List	   *finalrowmarks;	/* "flat" list of PlanRowMarks */
 
 	List	   *resultRelations;	/* "flat" list of integer RT indexes */
@@ -129,6 +132,9 @@ typedef struct PlannerGlobal
 
 	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
 
+	bool		usesPreExecPruning;	/* Do some Plan nodes use pre-execution
+									 * partition pruning */
+
 	PartitionDirectory partition_directory; /* partition descriptors */
 } PlannerGlobal;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b518ce6b2..69bc5f918c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -59,12 +59,20 @@ typedef struct PlannedStmt
 
 	bool		parallelModeNeeded; /* parallel mode required to execute? */
 
+	bool		usesPreExecPruning;	/* Do some Plan nodes use pre-execution
+									 * partition pruning */
+
 	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 */
 
+	Bitmapset  *relationRTIs;	/* Indexes of RTE_RELATION entries in range
+								 * table */
+
 	/* rtable indexes of target relations for INSERT/UPDATE/DELETE */
 	List	   *resultRelations;	/* integer list of RT indexes, or NIL */
 
@@ -1172,6 +1180,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.
+ *
+ * contains_init_steps	Does any of the PartitionedRelPruneInfos in
+ *						prune_infos have its initial_pruning_steps set?
+ *
+ * contains_exec_steps	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.
@@ -1180,6 +1195,8 @@ typedef struct PartitionPruneInfo
 {
 	NodeTag		type;
 	List	   *prune_infos;
+	bool		contains_init_steps;
+	bool		contains_exec_steps;
 	Bitmapset  *other_subplans;
 } PartitionPruneInfo;
 
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index ee11b6feae..90684efa25 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -41,6 +41,7 @@ struct RelOptInfo;
  *					subsidiary data, such as the FmgrInfos.
  * planstate		Points to the parent plan node's PlanState when called
  *					during execution; NULL when called from the planner.
+ * exprcontext		ExprContext to use when evaluating pruning expressions
  * exprstates		Array of ExprStates, indexed as per PruneCxtStateIdx; one
  *					for each partition key in each pruning step.  Allocated if
  *					planstate is non-NULL, otherwise NULL.
@@ -56,6 +57,7 @@ typedef struct PartitionPruneContext
 	FmgrInfo   *stepcmpfuncs;
 	MemoryContext ppccontext;
 	PlanState  *planstate;
+	ExprContext *exprcontext;
 	ExprState **exprstates;
 } PartitionPruneContext;
 
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 15a11bc3ff..02124af4ed 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -59,7 +59,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 **stmt_execprep_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..14794972a0 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	   *stmt_execprep_list;	/* list of ExecutorPrepResult 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,8 @@ 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 execprep_context;	/* context containing stmt_execprep_list,
+									 * a child of the above context */
 } CachedPlan;
 
 /*
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index aeddbdafe5..03c39ff97a 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	   *stmt_execpreps;	/* list of ExecutorPrepResults with one element
+								 * for each of 'stmts'; same as
+								 * cplan->stmt_execprep_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 *stmt_execpreps,
 							  CachedPlan *cplan);
 extern PlannedStmt *PortalGetPrimaryStmt(Portal portal);
 extern void PortalCreateHoldStore(Portal portal);
-- 
2.24.1

