Here's a rebased version of this patch (it had a trivial conflict).
No further changes.


-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 82d8140ba2..677b9cdd58 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1951,7 +1951,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	if (plan && plan->operation == CMD_UPDATE &&
 		(resultRelInfo->ri_usesFdwDirectModify ||
 		 resultRelInfo->ri_FdwState) &&
-		resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
+		resultRelInfo > mtstate->resultRelInfos[mtstate->mt_whichplan])
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@@ -2005,7 +2005,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 		 */
 		if (plan && plan->operation == CMD_UPDATE &&
 			resultRelation == plan->rootRelation)
-			resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+			resultRelation = mtstate->resultRelInfos[0]->ri_RangeTableIndex;
 	}
 
 	/* Construct the SQL command string. */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3aeef30b28..2e9954ddda 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2856,7 +2856,7 @@ CopyFrom(CopyState cstate)
 	mtstate->ps.plan = NULL;
 	mtstate->ps.state = estate;
 	mtstate->operation = CMD_INSERT;
-	mtstate->resultRelInfo = estate->es_result_relations;
+	mtstate->resultRelInfos = &estate->es_result_relations;
 
 	if (resultRelInfo->ri_FdwRoutine != NULL &&
 		resultRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 62fb3434a3..682bf615db 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3190,14 +3190,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 	/* Should we explicitly label target relations? */
 	labeltargets = (mtstate->mt_nplans > 1 ||
 					(mtstate->mt_nplans == 1 &&
-					 mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+					 mtstate->resultRelInfos[0]->ri_RangeTableIndex != node->nominalRelation));
 
 	if (labeltargets)
 		ExplainOpenGroup("Target Tables", "Target Tables", false, es);
 
 	for (j = 0; j < mtstate->mt_nplans; j++)
 	{
-		ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
+		ResultRelInfo *resultRelInfo = mtstate->resultRelInfos[j];
 		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
 
 		if (labeltargets)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index d23f292cb0..8d1d961809 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -471,7 +471,7 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 	/* Hash all subplans by their Oid */
 	for (i = 0; i < mtstate->mt_nplans; i++)
 	{
-		ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+		ResultRelInfo *rri = mtstate->resultRelInfos[i];
 		bool		found;
 		Oid			partoid = RelationGetRelid(rri->ri_RelationDesc);
 		SubplanResultRelHashElem *elem;
@@ -508,7 +508,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
 	Relation	rootrel = rootResultRelInfo->ri_RelationDesc,
 				partrel;
-	Relation	firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
+	Relation	firstResultRel = mtstate->resultRelInfos[0]->ri_RelationDesc;
 	ResultRelInfo *leaf_part_rri;
 	MemoryContext oldcxt;
 	AttrNumber *part_attnos = NULL;
@@ -556,7 +556,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		List	   *wcoList;
 		List	   *wcoExprs = NIL;
 		ListCell   *ll;
-		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+		int			firstVarno = mtstate->resultRelInfos[0]->ri_RangeTableIndex;
 
 		/*
 		 * In the case of INSERT on a partitioned table, there is only one
@@ -621,7 +621,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		TupleTableSlot *slot;
 		ExprContext *econtext;
 		List	   *returningList;
-		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+		int			firstVarno = mtstate->resultRelInfos[0]->ri_RangeTableIndex;
 
 		/* See the comment above for WCO lists. */
 		Assert((node->operation == CMD_INSERT &&
@@ -681,7 +681,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 	 */
 	if (node && node->onConflictAction != ONCONFLICT_NONE)
 	{
-		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
+		int			firstVarno = mtstate->resultRelInfos[0]->ri_RangeTableIndex;
 		TupleDesc	partrelDesc = RelationGetDescr(partrel);
 		ExprContext *econtext = mtstate->ps.ps_ExprContext;
 		ListCell   *lc;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c9d024ead5..f35291cb12 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -57,6 +57,9 @@
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
+ /* Special values for mt_whichplan */
+#define WHICHPLAN_CHOOSE_PARTITIONS			-1
+#define WHICHPLAN_NO_MATCHING_PARTITIONS	-2
 
 static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
 								 ResultRelInfo *resultRelInfo,
@@ -1261,7 +1264,7 @@ lreplace:;
 			 * retrieve the one for this resultRel, we need to know the
 			 * position of the resultRel in mtstate->resultRelInfo[].
 			 */
-			map_index = resultRelInfo - mtstate->resultRelInfo;
+			map_index = mtstate->mt_whichplan;
 			Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
 			tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
 			if (tupconv_map != NULL)
@@ -1707,12 +1710,12 @@ static void
 fireBSTriggers(ModifyTableState *node)
 {
 	ModifyTable *plan = (ModifyTable *) node->ps.plan;
-	ResultRelInfo *resultRelInfo = node->resultRelInfo;
+	ResultRelInfo *resultRelInfo = node->resultRelInfos[0];
 
 	/*
 	 * If the node modifies a partitioned table, we must fire its triggers.
-	 * Note that in that case, node->resultRelInfo points to the first leaf
-	 * partition, not the root table.
+	 * Note that in that case, node->resultRelInfos[0] points to the first
+	 * leaf partition, not the root table.
 	 */
 	if (node->rootResultRelInfo != NULL)
 		resultRelInfo = node->rootResultRelInfo;
@@ -1750,13 +1753,14 @@ static ResultRelInfo *
 getTargetResultRelInfo(ModifyTableState *node)
 {
 	/*
-	 * Note that if the node modifies a partitioned table, node->resultRelInfo
-	 * points to the first leaf partition, not the root table.
+	 * Note that if the node modifies a partitioned table,
+	 * node->resultRelInfos[0] points to the first leaf partition, not the
+	 * root table.
 	 */
 	if (node->rootResultRelInfo != NULL)
 		return node->rootResultRelInfo;
 	else
-		return node->resultRelInfo;
+		return node->resultRelInfos[0];
 }
 
 /*
@@ -1935,7 +1939,7 @@ static void
 ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
 {
 	ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
-	ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
+	ResultRelInfo **resultRelInfos = mtstate->resultRelInfos;
 	TupleDesc	outdesc;
 	int			numResultRelInfos = mtstate->mt_nplans;
 	int			i;
@@ -1955,7 +1959,7 @@ ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
 	for (i = 0; i < numResultRelInfos; ++i)
 	{
 		mtstate->mt_per_subplan_tupconv_maps[i] =
-			convert_tuples_by_name(RelationGetDescr(resultRelInfos[i].ri_RelationDesc),
+			convert_tuples_by_name(RelationGetDescr(resultRelInfos[i]->ri_RelationDesc),
 								   outdesc);
 	}
 }
@@ -2031,8 +2035,47 @@ ExecModifyTable(PlanState *pstate)
 		node->fireBSTriggers = false;
 	}
 
+	if (node->mt_whichplan < 0)
+	{
+		/* Handle choosing the valid partitions */
+		if (node->mt_whichplan == WHICHPLAN_CHOOSE_PARTITIONS)
+		{
+			PartitionPruneState *prunestate = node->mt_prune_state;
+
+			/* There should always be at least one */
+			Assert(node->mt_nplans > 0);
+
+			/*
+			 * When partition pruning is enabled and exec params match the
+			 * partition key then determine the minimum set of matching
+			 * subnodes.  Otherwise we match to all subnodes.
+			 */
+			if (prunestate != NULL && prunestate->do_exec_prune)
+			{
+				node->mt_valid_subplans = ExecFindMatchingSubPlans(prunestate);
+				node->mt_whichplan = bms_next_member(node->mt_valid_subplans, -1);
+
+				/* If no subplan matches these params then we're done */
+				if (node->mt_whichplan < 0)
+					goto done;
+			}
+			else
+			{
+				node->mt_valid_subplans = bms_add_range(NULL, 0,
+														node->mt_nplans - 1);
+				node->mt_whichplan = 0;
+			}
+		}
+
+		/* partition pruning determined that no partitions match */
+		else if (node->mt_whichplan == WHICHPLAN_NO_MATCHING_PARTITIONS)
+			goto done;
+		else
+			elog(ERROR, "invalid subplan index: %d", node->mt_whichplan);
+	}
+
 	/* Preload local variables */
-	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+	resultRelInfo = node->resultRelInfos[node->mt_whichplan];
 	subplanstate = node->mt_plans[node->mt_whichplan];
 	junkfilter = resultRelInfo->ri_junkFilter;
 
@@ -2074,10 +2117,12 @@ ExecModifyTable(PlanState *pstate)
 		if (TupIsNull(planSlot))
 		{
 			/* advance to next subplan if any */
-			node->mt_whichplan++;
-			if (node->mt_whichplan < node->mt_nplans)
+			node->mt_whichplan = bms_next_member(node->mt_valid_subplans,
+												 node->mt_whichplan);
+
+			if (node->mt_whichplan >= 0)
 			{
-				resultRelInfo++;
+				resultRelInfo = node->resultRelInfos[node->mt_whichplan];
 				subplanstate = node->mt_plans[node->mt_whichplan];
 				junkfilter = resultRelInfo->ri_junkFilter;
 				estate->es_result_relation_info = resultRelInfo;
@@ -2247,6 +2292,8 @@ ExecModifyTable(PlanState *pstate)
 	/* Restore es_result_relation_info before exiting */
 	estate->es_result_relation_info = saved_resultRelInfo;
 
+done:
+
 	/*
 	 * We're done, but fire AFTER STATEMENT triggers before exiting.
 	 */
@@ -2269,9 +2316,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	int			nplans = list_length(node->plans);
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
+	Bitmapset  *validsubplans;
 	Plan	   *subplan;
 	ListCell   *l;
-	int			i;
+	int			i,
+				j;
 	Relation	rel;
 	bool		update_tuple_routing_needed = node->partColsUpdated;
 
@@ -2289,9 +2338,75 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	mtstate->operation = operation;
 	mtstate->canSetTag = node->canSetTag;
 	mtstate->mt_done = false;
+	mtstate->mt_whichplan = WHICHPLAN_CHOOSE_PARTITIONS;
+
+	/* If run-time partition pruning is enabled, then set that up now */
+	if (node->part_prune_info != NULL)
+	{
+		PartitionPruneState *prunestate;
+
+		ExecAssignExprContext(estate, &mtstate->ps);
+
+		prunestate = ExecCreatePartitionPruneState(&mtstate->ps,
+			node->part_prune_info);
+		mtstate->mt_prune_state = prunestate;
+
+		/* Perform an initial partition prune, if required. */
+		if (prunestate->do_initial_prune)
+		{
+			/* Determine which subplans match the external params */
+			validsubplans = ExecFindInitialMatchingSubPlans(prunestate,
+													list_length(node->plans));
+
+			/*
+			 * The case where no subplans survive pruning must be handled
+			 * specially.  The problem here is that code in explain.c requires
+			 * an Append to have at least one subplan in order for it to
+			 * properly determine the Vars in that subplan's targetlist.  We
+			 * sidestep this issue by just initializing the first subplan and
+			 * setting as_whichplan to NO_MATCHING_SUBPLANS to indicate that
+			 * we don't really need to scan any subnodes.
+			 */
+			if (bms_is_empty(validsubplans))
+			{
+				mtstate->mt_whichplan = WHICHPLAN_NO_MATCHING_PARTITIONS;
+
+				/* Mark the first as valid so that it's initialized below */
+				validsubplans = bms_make_singleton(0);
+			}
+
+			nplans = bms_num_members(validsubplans);
+		}
+		else
+		{
+			/* We'll need to initialize all subplans */
+			nplans = list_length(node->plans);
+			validsubplans = bms_add_range(NULL, 0, nplans - 1);
+		}
+
+		/*
+		 * If no runtime pruning is required, we can fill mt_valid_subplans
+		 * immediately, preventing later calls to ExecFindMatchingSubPlans.
+		 */
+		if (!prunestate->do_exec_prune)
+			mtstate->mt_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
+	}
+	else
+	{
+		nplans = list_length(node->plans);
+
+		/*
+		 * When run-time partition pruning is not enabled we can just mark all
+		 * plans as valid, they must also all be initialized.
+		 */
+		validsubplans = bms_add_range(NULL, 0, nplans - 1);
+		mtstate->mt_prune_state = NULL;
+	}
+
 
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
-	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+	mtstate->resultRelInfos = (ResultRelInfo **)
+		palloc(sizeof(ResultRelInfo *) * nplans);
 	mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
 
 	/* If modifying a partitioned table, initialize the root table info */
@@ -2318,11 +2433,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 */
 	saved_resultRelInfo = estate->es_result_relation_info;
 
-	resultRelInfo = mtstate->resultRelInfo;
-	i = 0;
+	j = i = 0;
 	foreach(l, node->plans)
 	{
+		if (!bms_is_member(i, validsubplans))
+		{
+			i++;
+			continue;
+		}
+
 		subplan = (Plan *) lfirst(l);
+		resultRelInfo = estate->es_result_relations + node->resultRelIndex + i;
+		mtstate->resultRelInfos[j] = resultRelInfo;
 
 		/* Initialize the usesFdwDirectModify flag */
 		resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
@@ -2360,9 +2482,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		/* Now init the plan for this result rel */
 		estate->es_result_relation_info = resultRelInfo;
-		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
-		mtstate->mt_scans[i] =
-			ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
+		mtstate->mt_plans[j] = ExecInitNode(subplan, estate, eflags);
+		mtstate->mt_scans[j] =
+			ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[j]),
 								   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
 
 		/* Also let FDWs init themselves for foreign-table result rels */
@@ -2378,9 +2500,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 															 i,
 															 eflags);
 		}
-
-		resultRelInfo++;
 		i++;
+		j++;
 	}
 
 	estate->es_result_relation_info = saved_resultRelInfo;
@@ -2429,14 +2550,21 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	/*
 	 * Initialize any WITH CHECK OPTION constraints if needed.
 	 */
-	resultRelInfo = mtstate->resultRelInfo;
-	i = 0;
+	j = i = 0;
 	foreach(l, node->withCheckOptionLists)
 	{
-		List	   *wcoList = (List *) lfirst(l);
+		List	   *wcoList;
 		List	   *wcoExprs = NIL;
 		ListCell   *ll;
 
+		if (!bms_is_member(i, validsubplans))
+		{
+			i++;
+			continue;
+		}
+
+		wcoList = (List *) lfirst(l);
+
 		foreach(ll, wcoList)
 		{
 			WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
@@ -2446,9 +2574,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			wcoExprs = lappend(wcoExprs, wcoExpr);
 		}
 
+		resultRelInfo = mtstate->resultRelInfos[j];
 		resultRelInfo->ri_WithCheckOptions = wcoList;
 		resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
-		resultRelInfo++;
+		j++;
 		i++;
 	}
 
@@ -2478,16 +2607,25 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		/*
 		 * Build a projection for each result rel.
 		 */
-		resultRelInfo = mtstate->resultRelInfo;
+		j = i = 0;
 		foreach(l, node->returningLists)
 		{
-			List	   *rlist = (List *) lfirst(l);
+			List	   *rlist;
 
+			if (!bms_is_member(i, validsubplans))
+			{
+				i++;
+				continue;
+			}
+
+			rlist = (List *) lfirst(l);
+
+			resultRelInfo = mtstate->resultRelInfos[j];
 			resultRelInfo->ri_returningList = rlist;
 			resultRelInfo->ri_projectReturning =
 				ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
 										resultRelInfo->ri_RelationDesc->rd_att);
-			resultRelInfo++;
+			j++;
 		}
 	}
 	else
@@ -2498,12 +2636,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		 */
 		mtstate->ps.plan->targetlist = NIL;
 		ExecInitResultTypeTL(&mtstate->ps);
-
-		mtstate->ps.ps_ExprContext = NULL;
 	}
 
 	/* Set the list of arbiter indexes if needed for ON CONFLICT */
-	resultRelInfo = mtstate->resultRelInfo;
+	resultRelInfo = mtstate->resultRelInfos[0];
 	if (node->onConflictAction != ONCONFLICT_NONE)
 		resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
 
@@ -2597,7 +2733,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/* select first subplan */
-	mtstate->mt_whichplan = 0;
 	subplan = (Plan *) linitial(node->plans);
 	EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
 						mtstate->mt_arowmarks[0]);
@@ -2646,12 +2781,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		if (junk_filter_needed)
 		{
-			resultRelInfo = mtstate->resultRelInfo;
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
 				TupleTableSlot *junkresslot;
 
+				resultRelInfo = mtstate->resultRelInfos[i];
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
 					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
@@ -2694,13 +2829,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				}
 
 				resultRelInfo->ri_junkFilter = j;
-				resultRelInfo++;
 			}
 		}
 		else
 		{
 			if (operation == CMD_INSERT)
-				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
+				ExecCheckPlanOutput(mtstate->resultRelInfos[0]->ri_RelationDesc,
 									subplan->targetlist);
 		}
 	}
@@ -2739,7 +2873,7 @@ ExecEndModifyTable(ModifyTableState *node)
 	 */
 	for (i = 0; i < node->mt_nplans; i++)
 	{
-		ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
+		ResultRelInfo *resultRelInfo = node->resultRelInfos[i];
 
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..d3a2fed402 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -215,6 +215,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(part_prune_info);
 	COPY_SCALAR_FIELD(onConflictAction);
 	COPY_NODE_FIELD(arbiterIndexes);
 	COPY_NODE_FIELD(onConflictSet);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b0dcd02ff6..612d41c4fb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -416,6 +416,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(part_prune_info);
 	WRITE_ENUM_FIELD(onConflictAction, OnConflictAction);
 	WRITE_NODE_FIELD(arbiterIndexes);
 	WRITE_NODE_FIELD(onConflictSet);
@@ -2089,6 +2090,7 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
 	WRITE_UINT_FIELD(rootRelation);
 	WRITE_BOOL_FIELD(partColsUpdated);
 	WRITE_NODE_FIELD(resultRelations);
+	WRITE_NODE_FIELD(partitioned_rels);
 	WRITE_NODE_FIELD(subpaths);
 	WRITE_NODE_FIELD(subroots);
 	WRITE_NODE_FIELD(withCheckOptionLists);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 764e3bb90c..571587c114 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1616,6 +1616,7 @@ _readModifyTable(void)
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
+	READ_NODE_FIELD(part_prune_info);
 	READ_ENUM_FIELD(onConflictAction, OnConflictAction);
 	READ_NODE_FIELD(arbiterIndexes);
 	READ_NODE_FIELD(onConflictSet);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 0c036209f0..9262f39673 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -289,7 +289,8 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
 									 bool partColsUpdated,
 									 List *resultRelations, List *subplans, List *subroots,
 									 List *withCheckOptionLists, List *returningLists,
-									 List *rowMarks, OnConflictExpr *onconflict, int epqParam);
+									 List *rowMarks, OnConflictExpr *onconflict, int epqParam,
+									 PartitionPruneInfo *partpruneinfos);
 static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
 											 GatherMergePath *best_path);
 
@@ -1237,6 +1238,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 				make_partition_pruneinfo(root, rel,
 										 best_path->subpaths,
 										 best_path->partitioned_rels,
+										 NIL,
 										 prunequal);
 	}
 
@@ -1402,6 +1404,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
 			partpruneinfo = make_partition_pruneinfo(root, rel,
 													 best_path->subpaths,
 													 best_path->partitioned_rels,
+													 NIL,
 													 prunequal);
 	}
 
@@ -2581,6 +2584,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 	List	   *subplans = NIL;
 	ListCell   *subpaths,
 			   *subroots;
+	PartitionPruneInfo *partpruneinfos = NULL;
 
 	/* Build the plan for each input path */
 	forboth(subpaths, best_path->subpaths,
@@ -2609,6 +2613,30 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 		subplans = lappend(subplans, subplan);
 	}
 
+	if (enable_partition_pruning &&
+		best_path->partitioned_rels != NIL &&
+		!IS_DUMMY_MODIFYTABLE(best_path))
+	{
+		RelOptInfo *rel = best_path->path.parent;
+		List	   *prunequal = NIL;
+
+		prunequal = extract_actual_clauses(rel->baserestrictinfo, false);
+
+		/*
+		 * If any quals exist, then these may be useful to allow us to perform
+		 * further partition pruning during execution.  We'll generate a
+		 * PartitionPruneInfo for each partitioned rel to store these quals
+		 * and allow translation of partition indexes into subpath indexes.
+		 */
+		if (prunequal != NIL)
+			partpruneinfos = make_partition_pruneinfo(root,
+													  best_path->path.parent,
+													  best_path->subpaths,
+													  best_path->partitioned_rels,
+													  best_path->resultRelations,
+													  prunequal);
+	}
+
 	plan = make_modifytable(root,
 							best_path->operation,
 							best_path->canSetTag,
@@ -2622,7 +2650,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->returningLists,
 							best_path->rowMarks,
 							best_path->onconflict,
-							best_path->epqParam);
+							best_path->epqParam,
+							partpruneinfos);
 
 	copy_generic_path_info(&plan->plan, &best_path->path);
 
@@ -6616,7 +6645,8 @@ make_modifytable(PlannerInfo *root,
 				 bool partColsUpdated,
 				 List *resultRelations, List *subplans, List *subroots,
 				 List *withCheckOptionLists, List *returningLists,
-				 List *rowMarks, OnConflictExpr *onconflict, int epqParam)
+				 List *rowMarks, OnConflictExpr *onconflict, int epqParam,
+				 PartitionPruneInfo *partpruneinfos)
 {
 	ModifyTable *node = makeNode(ModifyTable);
 	List	   *fdw_private_list;
@@ -6677,6 +6707,7 @@ make_modifytable(PlannerInfo *root,
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
+	node->part_prune_info = partpruneinfos;
 
 	/*
 	 * For each result relation that is a foreign table, allow the FDW to
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17c5f086fb..bab7175d48 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1236,6 +1236,9 @@ inheritance_planner(PlannerInfo *root)
 	RangeTblEntry *parent_rte;
 	Bitmapset  *parent_relids;
 	Query	  **parent_parses;
+	PlannerInfo *partition_root = NULL;
+	List	   *partitioned_rels = NIL;
+	bool		dummy_update = false;
 
 	/* Should only get here for UPDATE or DELETE */
 	Assert(parse->commandType == CMD_UPDATE ||
@@ -1347,6 +1350,13 @@ inheritance_planner(PlannerInfo *root)
 		 * expand_partitioned_rtentry for the UPDATE target.)
 		 */
 		root->partColsUpdated = subroot->partColsUpdated;
+
+		/*
+		 * Save this for later so that we can enable run-time pruning on
+		 * the partitioned table(s).
+		 */
+		if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
+			partition_root = subroot;
 	}
 
 	/*----------
@@ -1741,6 +1751,9 @@ inheritance_planner(PlannerInfo *root)
 			returningLists = list_make1(parse->returningList);
 		/* Disable tuple routing, too, just to be safe */
 		root->partColsUpdated = false;
+
+		/* Mark that we're performing a dummy update */
+		dummy_update = true;
 	}
 	else
 	{
@@ -1779,6 +1792,69 @@ inheritance_planner(PlannerInfo *root)
 	else
 		rowMarks = root->rowMarks;
 
+
+	/*
+	 * When performing UPDATE/DELETE on a partitioned table, if the query has
+	 * a WHERE clause which supports it, we may be able to perform run-time
+	 * partition pruning.  The following code sets things up to allow this to
+	 * be possible.
+	 */
+	if (partition_root && !dummy_update)
+	{
+		RelOptInfo *parent_rel;
+		int			i;
+
+		/*
+		 * Fetch the target partitioned table from the SELECT version of
+		 * the query which we performed above.  This may have the base quals
+		 * which could allow the run-time pruning to work.
+		 */
+		parent_rel = partition_root->simple_rel_array[top_parentRTindex];
+
+		final_rel->baserestrictinfo = parent_rel->baserestrictinfo;
+
+		/* build a list of partitioned rels */
+		i = -1;
+		while ((i = bms_next_member(parent_relids, i)) > 0)
+			partitioned_rels = lappend_int(partitioned_rels, i);
+
+
+		/*
+		 * In order to build the run-time pruning data we'll need append rels
+		 * any sub-partitioned tables.  If there are some of those and the
+		 * append_rel_array is not already allocated, then do that now.
+		 */
+		if (list_length(partitioned_rels) > 1 &&
+			root->append_rel_array == NULL)
+			root->append_rel_array = palloc0(sizeof(AppendRelInfo *) *
+											 root->simple_rel_array_size);
+
+		/*
+		 * There can only be a single partition hierarchy, so it's fine to
+		 * just make a single element list of the partitioned_rels.
+		 */
+		partitioned_rels = list_make1(partitioned_rels);
+
+		i = -1;
+		while ((i = bms_next_member(parent_relids, i)) >= 0)
+		{
+			Assert(root->simple_rel_array[i] == NULL);
+
+			root->simple_rel_array[i] = partition_root->simple_rel_array[i];
+
+			/*
+			 * The root partition won't have an append rel entry, so we can
+			 * skip that.  We'll need to take the partition_root's version for
+			 * any sub-partitioned table's
+			 */
+			if (i != top_parentRTindex)
+			{
+				Assert(root->append_rel_array[i] == NULL);
+				root->append_rel_array[i] = partition_root->append_rel_array[i];
+			}
+		}
+	}
+
 	/* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
 	add_path(final_rel, (Path *)
 			 create_modifytable_path(root, final_rel,
@@ -1788,6 +1864,7 @@ inheritance_planner(PlannerInfo *root)
 									 rootRelation,
 									 root->partColsUpdated,
 									 resultRelations,
+									 partitioned_rels,
 									 subpaths,
 									 subroots,
 									 withCheckOptionLists,
@@ -2371,6 +2448,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										rootRelation,
 										false,
 										list_make1_int(parse->resultRelation),
+										NIL,
 										list_make1(path),
 										list_make1(root),
 										withCheckOptionLists,
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 34acb732ee..c1ed3abe9a 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3432,6 +3432,9 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'partColsUpdated' is true if any partitioning columns are being updated,
  *		either from the target relation or a descendent partitioned table.
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
+ * 'partitioned_rels' is an integer list of RT indexes of non-leaf tables in
+ *		the partition tree, if this is an UPDATE/DELETE to a partitioned table.
+ *		Otherwise NIL.
  * 'subpaths' is a list of Path(s) producing source data (one per rel)
  * 'subroots' is a list of PlannerInfo structs (one per rel)
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
@@ -3445,8 +3448,8 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						CmdType operation, bool canSetTag,
 						Index nominalRelation, Index rootRelation,
 						bool partColsUpdated,
-						List *resultRelations, List *subpaths,
-						List *subroots,
+						List *resultRelations, List *partitioned_rels,
+						List *subpaths, List *subroots,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						int epqParam)
@@ -3514,6 +3517,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->rootRelation = rootRelation;
 	pathnode->partColsUpdated = partColsUpdated;
 	pathnode->resultRelations = resultRelations;
+	pathnode->partitioned_rels = list_copy(partitioned_rels);
 	pathnode->subpaths = subpaths;
 	pathnode->subroots = subroots;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index 29f1c11b24..97f3145d16 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -214,7 +214,10 @@ static void partkey_datum_from_expr(PartitionPruneContext *context,
  * 'parentrel' is the RelOptInfo for an appendrel, and 'subpaths' is the list
  * of scan paths for its child rels.
  *
- * 'partitioned_rels' is a List containing Lists of relids of partitioned
+ * If 'resultRelations' is non-NIL, then this List of relids is used to build
+ * the mapping structures.  Otherwise the 'subpaths' List is used.
+ *
+  * 'partitioned_rels' is a List containing Lists of relids of partitioned
  * tables (a/k/a non-leaf partitions) that are parents of some of the child
  * rels.  Here we attempt to populate the PartitionPruneInfo by adding a
  * 'prune_infos' item for each sublist in the 'partitioned_rels' list.
@@ -229,6 +232,7 @@ static void partkey_datum_from_expr(PartitionPruneContext *context,
 PartitionPruneInfo *
 make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 						 List *subpaths, List *partitioned_rels,
+						 List *resultRelations,
 						 List *prunequal)
 {
 	PartitionPruneInfo *pruneinfo;
@@ -246,23 +250,36 @@ make_partition_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 	relid_subplan_map = palloc0(sizeof(int) * root->simple_rel_array_size);
 
 	/*
-	 * relid_subplan_map maps relid of a leaf partition to the index in
-	 * 'subpaths' of the scan plan for that partition.
+	 * If 'resultRelations' are present then map these, otherwise we map the
+	 * 'subpaths' List.
 	 */
-	i = 1;
-	foreach(lc, subpaths)
+	if (resultRelations != NIL)
 	{
-		Path	   *path = (Path *) lfirst(lc);
-		RelOptInfo *pathrel = path->parent;
+		i = 1;
+		foreach(lc, resultRelations)
+		{
+			int			resultrel = lfirst_int(lc);
 
-		Assert(IS_SIMPLE_REL(pathrel));
-		Assert(pathrel->relid < root->simple_rel_array_size);
-		/* No duplicates please */
-		Assert(relid_subplan_map[pathrel->relid] == 0);
-
-		relid_subplan_map[pathrel->relid] = i++;
+			Assert(resultrel < root->simple_rel_array_size);
+			relid_subplan_map[resultrel] = i++;
+		}
 	}
+	else
+	{
+		i = 1;
+		foreach(lc, subpaths)
+		{
+			Path	   *path = (Path *)lfirst(lc);
+			RelOptInfo *pathrel = path->parent;
 
+			Assert(IS_SIMPLE_REL(pathrel));
+			Assert(pathrel->relid < root->simple_rel_array_size);
+			/* No duplicates please */
+			Assert(relid_subplan_map[pathrel->relid] == 0);
+
+			relid_subplan_map[pathrel->relid] = i++;
+		}
+	}
 	/* We now build a PartitionedRelPruneInfo for each partitioned rel. */
 	prunerelinfos = NIL;
 	foreach(lc, partitioned_rels)
@@ -404,9 +421,12 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel,
 			 * an adjust_appendrel_attrs step.  But it might not be, and then
 			 * we have to translate.  We update the prunequal parameter here,
 			 * because in later iterations of the loop for child partitions,
-			 * we want to translate from parent to child variables.
+			 * we want to translate from parent to child variables.  We don't
+			 * need to do this when planning a non-SELECT as we're only
+			 * working with a single partition hierarchy in that case.
 			 */
-			if (!bms_equal(parentrel->relids, subpart->relids))
+			if (root->parse->commandType == CMD_SELECT &&
+				!bms_equal(parentrel->relids, subpart->relids))
 			{
 				int			nappinfos;
 				AppendRelInfo **appinfos = find_appinfos_by_relids(root,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b593d22c48..d058716ff8 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1166,7 +1166,7 @@ typedef struct ModifyTableState
 	int			mt_whichplan;	/* which one is being executed (0..n-1) */
 	TupleTableSlot **mt_scans;	/* input tuple corresponding to underlying
 								 * plans */
-	ResultRelInfo *resultRelInfo;	/* per-subplan target relations */
+	ResultRelInfo **resultRelInfos;	/* per-subplan target relations */
 	ResultRelInfo *rootResultRelInfo;	/* root target relation (partitioned
 										 * table root) */
 	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
@@ -1191,6 +1191,14 @@ typedef struct ModifyTableState
 
 	/* Per plan map for tuple conversion from child to root */
 	TupleConversionMap **mt_per_subplan_tupconv_maps;
+
+	/*
+	 * Details required to allow partitions to be eliminated from the scan, or
+	 * NULL if not possible.
+	 */
+	struct PartitionPruneState *mt_prune_state;
+	Bitmapset  *mt_valid_subplans;	/* for runtime pruning, valid mt_plans
+									 * indexes to scan. */
 } ModifyTableState;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 23a06d718e..37e32166de 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1380,6 +1380,10 @@ typedef struct AppendPath
 #define IS_DUMMY_APPEND(p) \
 	(IsA((p), AppendPath) && ((AppendPath *) (p))->subpaths == NIL)
 
+#define IS_DUMMY_MODIFYTABLE(p) \
+	(list_length((p)->subpaths) == 1 && \
+		IS_DUMMY_APPEND(linitial((p)->subpaths)))
+
 /*
  * A relation that's been proven empty will have one path that is dummy
  * (but might have projection paths on top).  For historical reasons,
@@ -1775,6 +1779,8 @@ typedef struct ModifyTablePath
 	Index		rootRelation;	/* Root RT index, if target is partitioned */
 	bool		partColsUpdated;	/* some part key in hierarchy updated */
 	List	   *resultRelations;	/* integer list of RT indexes */
+	/* RT indexes of non-leaf tables in a partition tree */
+	List	   *partitioned_rels;
 	List	   *subpaths;		/* Path(s) producing source data */
 	List	   *subroots;		/* per-target-table PlannerInfos */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 8e6594e355..444ed14e7c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -231,6 +231,8 @@ typedef struct ModifyTable
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	/* Mapping details for run-time subplan pruning, one per partitioned_rels */
+	struct PartitionPruneInfo *part_prune_info;
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
 	List	   *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs  */
 	List	   *onConflictSet;	/* SET for INSERT ON CONFLICT DO UPDATE */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index a12af54971..c8f5a165d6 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -252,10 +252,12 @@ extern LockRowsPath *create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
 extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												RelOptInfo *rel,
 												CmdType operation, bool canSetTag,
-												Index nominalRelation, Index rootRelation,
+												Index nominalRelation,
+												Index rootRelation,
 												bool partColsUpdated,
-												List *resultRelations, List *subpaths,
-												List *subroots,
+												List *resultRelations,
+												List *partitioned_rels,
+												List *subpaths, List *subroots,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												int epqParam);
diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h
index b3e926865a..a98566f004 100644
--- a/src/include/partitioning/partprune.h
+++ b/src/include/partitioning/partprune.h
@@ -72,6 +72,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(struct PlannerInfo *root,
 													struct RelOptInfo *parentrel,
 													List *subpaths,
 													List *partitioned_rels,
+													List *resultRelations,
 													List *prunequal);
 extern Bitmapset *prune_append_rel_partitions(struct RelOptInfo *rel);
 extern Bitmapset *get_matching_partitions(PartitionPruneContext *context,
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 82b68e793d..ccb72d915a 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -3459,6 +3459,111 @@ explain (analyze, costs off, summary off, timing off) select * from ma_test wher
 
 reset enable_seqscan;
 reset enable_sort;
+--
+-- Test run-time pruning of ModifyTable subnodes
+--
+-- Ensure only ma_test_p3 is scanned.
+explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 29);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Delete on ma_test (actual rows=0 loops=1)
+   Delete on ma_test_p1
+   Delete on ma_test_p2
+   Delete on ma_test_p3
+   InitPlan 1 (returns $0)
+     ->  Result (actual rows=1 loops=1)
+   ->  Seq Scan on ma_test_p1 (never executed)
+         Filter: (a = $0)
+   ->  Seq Scan on ma_test_p2 (never executed)
+         Filter: (a = $0)
+   ->  Seq Scan on ma_test_p3 (actual rows=1 loops=1)
+         Filter: (a = $0)
+         Rows Removed by Filter: 9
+(13 rows)
+
+-- Ensure no partitions are scanned.
+explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 30);
+                  QUERY PLAN                   
+-----------------------------------------------
+ Delete on ma_test (actual rows=0 loops=1)
+   Delete on ma_test_p1
+   Delete on ma_test_p2
+   Delete on ma_test_p3
+   InitPlan 1 (returns $0)
+     ->  Result (actual rows=1 loops=1)
+   ->  Seq Scan on ma_test_p1 (never executed)
+         Filter: (a = $0)
+   ->  Seq Scan on ma_test_p2 (never executed)
+         Filter: (a = $0)
+   ->  Seq Scan on ma_test_p3 (never executed)
+         Filter: (a = $0)
+(12 rows)
+
+-- Ensure partition pruning works with an update of the partition key.
+explain (analyze, costs off, summary off, timing off) update ma_test set a = 29 where a = (select 1);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Update on ma_test (actual rows=0 loops=1)
+   Update on ma_test_p1
+   Update on ma_test_p2
+   Update on ma_test_p3
+   InitPlan 1 (returns $0)
+     ->  Result (actual rows=1 loops=1)
+   ->  Seq Scan on ma_test_p1 (actual rows=1 loops=1)
+         Filter: (a = $0)
+         Rows Removed by Filter: 9
+   ->  Seq Scan on ma_test_p2 (never executed)
+         Filter: (a = $0)
+   ->  Seq Scan on ma_test_p3 (never executed)
+         Filter: (a = $0)
+(13 rows)
+
+-- Verify the above command
+select tableoid::regclass,a from ma_test where a = 29;
+  tableoid  | a  
+------------+----
+ ma_test_p3 | 29
+(1 row)
+
+truncate ma_test;
+prepare mt_q1 (int) as
+delete from ma_test where a > $1;
+set plan_cache_mode = force_generic_plan;
+explain (analyze, costs off, summary off, timing off) execute mt_q1(15);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Delete on ma_test (actual rows=0 loops=1)
+   Delete on ma_test_p2
+   Delete on ma_test_p3
+   Subplans Removed: 1
+   ->  Seq Scan on ma_test_p2 (actual rows=0 loops=1)
+         Filter: (a > $1)
+   ->  Seq Scan on ma_test_p3 (actual rows=0 loops=1)
+         Filter: (a > $1)
+(8 rows)
+
+explain (analyze, costs off, summary off, timing off) execute mt_q1(25);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Delete on ma_test (actual rows=0 loops=1)
+   Delete on ma_test_p3
+   Subplans Removed: 2
+   ->  Seq Scan on ma_test_p3 (actual rows=0 loops=1)
+         Filter: (a > $1)
+(5 rows)
+
+-- Ensure ModifyTable behaves correctly when no subplans match exec params
+explain (analyze, costs off, summary off, timing off) execute mt_q1(35);
+                  QUERY PLAN                   
+-----------------------------------------------
+ Delete on ma_test (actual rows=0 loops=1)
+   Delete on ma_test_p1
+   Subplans Removed: 2
+   ->  Seq Scan on ma_test_p1 (never executed)
+         Filter: (a > $1)
+(5 rows)
+
+reset plan_cache_mode;
 drop table ma_test;
 reset enable_indexonlyscan;
 --
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 446af3b2c1..2c2a08e6f6 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -908,6 +908,36 @@ explain (analyze, costs off, summary off, timing off) select * from ma_test wher
 reset enable_seqscan;
 reset enable_sort;
 
+--
+-- Test run-time pruning of ModifyTable subnodes
+--
+
+-- Ensure only ma_test_p3 is scanned.
+explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 29);
+
+-- Ensure no partitions are scanned.
+explain (analyze, costs off, summary off, timing off) delete from ma_test where a = (select 30);
+
+-- Ensure partition pruning works with an update of the partition key.
+explain (analyze, costs off, summary off, timing off) update ma_test set a = 29 where a = (select 1);
+
+-- Verify the above command
+select tableoid::regclass,a from ma_test where a = 29;
+
+truncate ma_test;
+
+prepare mt_q1 (int) as
+delete from ma_test where a > $1;
+
+set plan_cache_mode = force_generic_plan;
+
+explain (analyze, costs off, summary off, timing off) execute mt_q1(15);
+explain (analyze, costs off, summary off, timing off) execute mt_q1(25);
+-- Ensure ModifyTable behaves correctly when no subplans match exec params
+explain (analyze, costs off, summary off, timing off) execute mt_q1(35);
+
+reset plan_cache_mode;
+
 drop table ma_test;
 
 reset enable_indexonlyscan;

Reply via email to