From 3e3e3674fdfd98c72630436564834acbf6e2f23a Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Thu, 2 Jul 2020 10:51:45 +0900
Subject: [PATCH v12 3/3] Initialize result relation information lazily

Currently, all elements of the ModifyTableState.resultRelInfo array
are initialized in ExecInitModifyTable(), possibly wastefully,
because only one or a handful of potentially many result relations
appearing in that array may actually have any rows to update or
delete.

This commit refactors all places that directly access the individual
elements of the array to instead go through a lazy-initialization-on-
access function, such that only the elements corresponding to result
relations that are actually operated on are initialized.

This also delays the initialization of
ModifyTableState.mt_partition_tuple_routing in the UPDATE case to the
first time ExecCrossPartitionUpdate() is called.  That allows us to
get rid of the somewhat convoluted logic used to decide whether
ExecInitModifyTable() should initialize it.  Related to that it the
lazy initialization of ri_ChildToRootMap in the ResultRelInfo of the
source partition of a tuple movement operation.  Note that there is a
regression test output change in update.out resulting from this change
-- whereas previously a partition constraint violation error would be
reported as occurring on a leaf partition, it is now shown as occurring
on the query's target relation, which is valid because it is really
that table's (which is a sub-partitioned table) partition constraint
that is actually violated in the affected test cases.

While at it, also delay the opening of result relation indices,
ExecOpenIndices(), to the first time ExecInsert() or ExecUpdate() is
called.
---
 doc/src/sgml/fdwhandler.sgml           |   9 +-
 src/backend/commands/explain.c         |  12 +-
 src/backend/commands/trigger.c         |   2 +-
 src/backend/executor/execMain.c        |   6 +
 src/backend/executor/execPartition.c   | 106 ++--
 src/backend/executor/execUtils.c       |  21 +
 src/backend/executor/nodeModifyTable.c | 827 +++++++++++++------------
 src/include/executor/executor.h        |   5 +
 src/include/nodes/execnodes.h          |   1 +
 src/test/regress/expected/update.out   |  12 +-
 10 files changed, 540 insertions(+), 461 deletions(-)

diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index b1d7c84002..d2ab4977e9 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -520,10 +520,11 @@ BeginForeignModify(ModifyTableState *mtstate,
                    int eflags);
 </programlisting>
 
-     Begin executing a foreign table modification operation.  This routine is
-     called during executor startup.  It should perform any initialization
-     needed prior to the actual table modifications.  Subsequently,
-     <function>ExecForeignInsert</function>, <function>ExecForeignUpdate</function> or
+     Begin executing a foreign table modification operation. This is called
+     right before executing the subplan to fetch the tuples to be modified.
+     It should perform any initialization needed prior to the actual table
+     modifications.  Subsequently, <function>ExecForeignInsert</function>,
+     <function>ExecForeignUpdate</function> or
      <function>ExecForeignDelete</function> will be called for each tuple to be
      inserted, updated, or deleted.
     </para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 43f9b01e83..ec79557e40 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -18,6 +18,7 @@
 #include "commands/createas.h"
 #include "commands/defrem.h"
 #include "commands/prepare.h"
+#include "executor/executor.h"
 #include "executor/nodeHash.h"
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
@@ -3654,6 +3655,8 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 	int			j;
 	List	   *idxNames = NIL;
 	ListCell   *lst;
+	ResultRelInfo *firstResultRel;
+	Relation	rootTargetDesc;
 
 	switch (node->operation)
 	{
@@ -3675,17 +3678,22 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 			break;
 	}
 
+	Assert(mtstate->rootResultRelInfo != NULL);
+	rootTargetDesc = mtstate->rootResultRelInfo->ri_RelationDesc;
+	firstResultRel = ExecGetResultRelation(mtstate, 0, rootTargetDesc);
+
 	/* Should we explicitly label target relations? */
 	labeltargets = (mtstate->mt_nplans > 1 ||
 					(mtstate->mt_nplans == 1 &&
-					 mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
+					 firstResultRel->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 = ExecGetResultRelation(mtstate, j,
+															 rootTargetDesc);
 		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
 
 		if (labeltargets)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index c336b238aa..af83f42d52 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -5459,7 +5459,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 	if (row_trigger && transition_capture != NULL)
 	{
 		TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple;
-		TupleConversionMap *map = relinfo->ri_ChildToRootMap;
+		TupleConversionMap *map = ExecGetChildToRootMap(relinfo);
 		bool		delete_old_table = transition_capture->tcs_delete_old_table;
 		bool		update_old_table = transition_capture->tcs_update_old_table;
 		bool		update_new_table = transition_capture->tcs_update_new_table;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d133a14f61..5828c01505 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1246,6 +1246,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 													 * ExecInitRoutingInfo */
 	resultRelInfo->ri_PartitionTupleSlot = NULL;	/* ditto */
 	resultRelInfo->ri_ChildToRootMap = NULL;
+	resultRelInfo->ri_ChildToRootMapValid = false;
 	resultRelInfo->ri_CopyMultiInsertBuffer = NULL;
 }
 
@@ -1439,6 +1440,11 @@ ExecCloseResultRelations(EState *estate)
 		ResultRelInfo *resultRelInfo = lfirst(l);
 
 		ExecCloseIndices(resultRelInfo);
+		if (!resultRelInfo->ri_usesFdwDirectModify &&
+			resultRelInfo->ri_FdwRoutine != NULL &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(estate,
+														   resultRelInfo);
 	}
 
 	/* Close any relations that have been opened by ExecGetTriggerResultRel(). */
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 50ed83e4c8..1e4973b42a 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_type.h"
 #include "executor/execPartition.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/fdwapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
@@ -157,10 +158,11 @@ typedef struct PartitionDispatchData
 typedef struct SubplanResultRelHashElem
 {
 	Oid			relid;			/* hash key -- must be first */
-	ResultRelInfo *rri;
+	int			index;
 } SubplanResultRelHashElem;
 
 
+static ResultRelInfo *ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid);
 static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 										   PartitionTupleRouting *proute);
 static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -218,7 +220,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
 							   Relation rel)
 {
 	PartitionTupleRouting *proute;
-	ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
 
 	/*
 	 * Here we attempt to expend as little effort as possible in setting up
@@ -240,17 +241,6 @@ ExecSetupPartitionTupleRouting(EState *estate, ModifyTableState *mtstate,
 	ExecInitPartitionDispatchInfo(estate, proute, RelationGetRelid(rel),
 								  NULL, 0);
 
-	/*
-	 * If performing an UPDATE with tuple routing, we can reuse partition
-	 * sub-plan result rels.  We build a hash table to map the OIDs of
-	 * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
-	 * Every time a tuple is routed to a partition that we've yet to set the
-	 * ResultRelInfo for, before we go to the trouble of making one, we check
-	 * for a pre-made one in the hash table.
-	 */
-	if (node && node->operation == CMD_UPDATE)
-		ExecHashSubPlanResultRelsByOid(mtstate, proute);
-
 	return proute;
 }
 
@@ -350,7 +340,6 @@ ExecFindPartition(ModifyTableState *mtstate,
 		is_leaf = partdesc->is_leaf[partidx];
 		if (is_leaf)
 		{
-
 			/*
 			 * We've reached the leaf -- hurray, we're done.  Look to see if
 			 * we've already got a ResultRelInfo for this partition.
@@ -367,20 +356,19 @@ ExecFindPartition(ModifyTableState *mtstate,
 
 				/*
 				 * We have not yet set up a ResultRelInfo for this partition,
-				 * but if we have a subplan hash table, we might have one
-				 * there.  If not, we'll have to create one.
+				 * but if the partition is also an UPDATE result relation, use
+				 * the one in mtstate->resultRelInfo instead of creating a new
+				 * one with ExecInitPartitionInfo().
 				 */
-				if (proute->subplan_resultrel_htab)
+				if (mtstate->operation == CMD_UPDATE && mtstate->ps.plan)
 				{
 					Oid			partoid = partdesc->oids[partidx];
-					SubplanResultRelHashElem *elem;
 
-					elem = hash_search(proute->subplan_resultrel_htab,
-									   &partoid, HASH_FIND, NULL);
-					if (elem)
+					rri = ExecLookupUpdateResultRelByOid(mtstate, partoid);
+
+					if (rri)
 					{
 						found = true;
-						rri = elem->rri;
 
 						/* Verify this ResultRelInfo allows INSERTs */
 						CheckValidResultRel(rri, CMD_INSERT);
@@ -507,6 +495,33 @@ ExecFindPartition(ModifyTableState *mtstate,
 	return rri;
 }
 
+/*
+ * ExecLookupUpdateResultRelByOid
+ * 		If the table with given OID appears in the list of result relations
+ * 		to be updated by the given ModifyTable node, return its
+ * 		ResultRelInfo, NULL otherwise.
+ */
+static ResultRelInfo *
+ExecLookupUpdateResultRelByOid(ModifyTableState *mtstate, Oid reloid)
+{
+	PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
+	SubplanResultRelHashElem *elem;
+	ResultRelInfo *result = NULL;
+
+	Assert(proute != NULL);
+	if (proute->subplan_resultrel_htab == NULL)
+		ExecHashSubPlanResultRelsByOid(mtstate, proute);
+
+	elem = hash_search(proute->subplan_resultrel_htab, &reloid,
+					   HASH_FIND, NULL);
+
+	if (elem)
+		result = ExecGetResultRelation(mtstate, elem->index,
+									   proute->partition_root);
+
+	return result;
+}
+
 /*
  * ExecHashSubPlanResultRelsByOid
  *		Build a hash table to allow fast lookups of subplan ResultRelInfos by
@@ -517,9 +532,13 @@ static void
 ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 							   PartitionTupleRouting *proute)
 {
+	EState	   *estate = mtstate->ps.state;
+	ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+	ListCell   *l;
 	HASHCTL		ctl;
 	HTAB	   *htab;
 	int			i;
+	MemoryContext oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
 
 	ctl.keysize = sizeof(Oid);
 	ctl.entrysize = sizeof(SubplanResultRelHashElem);
@@ -529,19 +548,26 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 					   &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
 	proute->subplan_resultrel_htab = htab;
 
-	/* Hash all subplans by their Oid */
-	for (i = 0; i < mtstate->mt_nplans; i++)
+	/*
+	 * Map each result relation's OID to its ordinal position in
+	 * plan->resultRelations.
+	 */
+	i = 0;
+	foreach(l, plan->resultRelations)
 	{
-		ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+		Index		rti = lfirst_int(l);
+		RangeTblEntry *rte = exec_rt_fetch(rti, estate);
+		Oid			partoid = rte->relid;
 		bool		found;
-		Oid			partoid = RelationGetRelid(rri->ri_RelationDesc);
 		SubplanResultRelHashElem *elem;
 
 		elem = (SubplanResultRelHashElem *)
 			hash_search(htab, &partoid, HASH_ENTER, &found);
 		Assert(!found);
-		elem->rri = rri;
+		elem->index = i++;
 	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -562,7 +588,8 @@ 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 = NULL;
+	Index		firstVarno = 0;
 	ResultRelInfo *leaf_part_rri;
 	MemoryContext oldcxt;
 	AttrMap    *part_attmap = NULL;
@@ -598,19 +625,27 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 						(node != NULL &&
 						 node->onConflictAction != ONCONFLICT_NONE));
 
+	if (node)
+	{
+		ResultRelInfo *firstResultRelInfo =
+			ExecGetResultRelation(mtstate, 0, proute->partition_root);
+
+		firstResultRel = firstResultRelInfo->ri_RelationDesc;
+		firstVarno = firstResultRelInfo->ri_RangeTableIndex;
+	}
+
 	/*
 	 * Build WITH CHECK OPTION constraints for the partition.  Note that we
 	 * didn't build the withCheckOptionList for partitions within the planner,
 	 * but simple translation of varattnos will suffice.  This only occurs for
 	 * the INSERT case or in the case of UPDATE tuple routing where we didn't
-	 * find a result rel to reuse in ExecSetupPartitionTupleRouting().
+	 * find a result rel to reuse.
 	 */
 	if (node && node->withCheckOptionLists != NIL)
 	{
 		List	   *wcoList;
 		List	   *wcoExprs = NIL;
 		ListCell   *ll;
-		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
 
 		/*
 		 * In the case of INSERT on a partitioned table, there is only one
@@ -674,7 +709,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		TupleTableSlot *slot;
 		ExprContext *econtext;
 		List	   *returningList;
-		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
 
 		/* See the comment above for WCO lists. */
 		Assert((node->operation == CMD_INSERT &&
@@ -733,7 +767,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 	 */
 	if (node && node->onConflictAction != ONCONFLICT_NONE)
 	{
-		int			firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
 		TupleDesc	partrelDesc = RelationGetDescr(partrel);
 		ExprContext *econtext = mtstate->ps.ps_ExprContext;
 		ListCell   *lc;
@@ -903,15 +936,6 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		}
 	}
 
-	/*
-	 * Also, if transition capture is required, store a map to convert tuples
-	 * from partition's rowtype to the root partition table's.
-	 */
-	if (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture)
-		leaf_part_rri->ri_ChildToRootMap =
-			convert_tuples_by_name(RelationGetDescr(leaf_part_rri->ri_RelationDesc),
-								   RelationGetDescr(leaf_part_rri->ri_RootTargetDesc));
-
 	/*
 	 * Since we've just initialized this ResultRelInfo, it's not in any list
 	 * attached to the estate as yet.  Add it, so that it can be found later.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 9819e7ae6f..769362f0e6 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1223,3 +1223,24 @@ ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo)
 
 	return relInfo->ri_ReturningSlot;
 }
+
+/*
+ * Returns the map needed to convert given child relation's tuples to the
+ * root relation's format, possibly initializing if not already done.
+ */
+TupleConversionMap *
+ExecGetChildToRootMap(ResultRelInfo *resultRelInfo)
+{
+	if (!resultRelInfo->ri_ChildToRootMapValid)
+	{
+		Relation	relation = resultRelInfo->ri_RelationDesc;
+		Relation	targetRel = resultRelInfo->ri_RootTargetDesc;
+
+		resultRelInfo->ri_ChildToRootMap =
+			convert_tuples_by_name(RelationGetDescr(relation),
+								   RelationGetDescr(targetRel));
+		resultRelInfo->ri_ChildToRootMapValid = true;
+	}
+
+	return resultRelInfo->ri_ChildToRootMap;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 3e325435ba..277dd4c49a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -179,6 +179,304 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo,
 	return ExecProject(projectReturning);
 }
 
+/*
+ * ExecGetResultRelation
+ *		Returns mtstate->resultRelInfo[whichrel], possibly initializing it
+ *		if being requested for the first time
+ */
+ResultRelInfo *
+ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+					  Relation rootTargetDesc)
+{
+	EState	   *estate = mtstate->ps.state;
+	ModifyTable *plan = (ModifyTable *) mtstate->ps.plan;
+	Index		rti;
+	ResultRelInfo *resultRelInfo = NULL;
+
+	/*
+	 * Initialized result relations are added to es_result_relations, so check
+	 * there first.  Remember that es_result_relations is indexed by RT index,
+	 * so fetch the relation's RT index from the plan.
+	 */
+	Assert(plan != NULL);
+	Assert(whichrel >= 0 && whichrel < mtstate->mt_nplans);
+	rti = list_nth_int(plan->resultRelations, whichrel);
+	if (estate->es_result_relations)
+		resultRelInfo = estate->es_result_relations[rti - 1];
+
+	/* Nope, so initialize. */
+	if (resultRelInfo == NULL)
+	{
+		int		eflags = estate->es_top_eflags;
+		CmdType	operation = mtstate->operation;
+		PlanState *subplanstate = mtstate->mt_plans[whichrel];
+		Plan   *subplan = subplanstate->plan;
+		bool	junk_filter_needed = false;
+		ListCell *l;
+		MemoryContext oldcxt;
+
+		Assert(whichrel >= 0);
+		resultRelInfo = &mtstate->resultRelInfo[whichrel];
+
+		/* Things built here have to last for the query duration. */
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		/*
+		 * Perform InitResultRelInfo() and save the pointer in
+		 * es_result_relations.
+		 */
+		ExecInitResultRelation(estate, resultRelInfo, rti, rootTargetDesc);
+
+		/*
+		 * A few more initializations that are not handled by
+		 * InitResultRelInfo() follow.
+		 */
+
+		/*
+		 * Verify result relation is a valid target for the current operation.
+		 */
+		CheckValidResultRel(resultRelInfo, operation);
+
+		/* Initialize the usesFdwDirectModify flag */
+		resultRelInfo->ri_usesFdwDirectModify = bms_is_member(whichrel,
+															  plan->fdwDirectModifyPlans);
+
+		/* Also let FDWs init themselves for foreign-table result rels */
+		if (!resultRelInfo->ri_usesFdwDirectModify &&
+			resultRelInfo->ri_FdwRoutine != NULL &&
+			resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+		{
+			List	   *fdw_private = (List *) list_nth(plan->fdwPrivLists,
+														whichrel);
+
+			resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+															 resultRelInfo,
+															 fdw_private,
+															 whichrel,
+															 eflags);
+		}
+
+		/* Initilize WITH CHECK OPTIONS expressions. */
+		if (plan->withCheckOptionLists)
+		{
+			List   *wcoList;
+			List   *wcoExprs = NIL;
+			ListCell   *ll;
+
+			wcoList = (List *) list_nth(plan->withCheckOptionLists, whichrel);
+			foreach(ll, wcoList)
+			{
+				WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+				ExprState  *wcoExpr = ExecInitQual((List *) wco->qual,
+												   &mtstate->ps);
+
+				wcoExprs = lappend(wcoExprs, wcoExpr);
+			}
+
+			resultRelInfo->ri_WithCheckOptions = wcoList;
+			resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+		}
+
+		/* Initilize RETURNING expressions. */
+		if (plan->returningLists)
+		{
+			List	*rlist;
+			TupleTableSlot *slot;
+			ExprContext *econtext;
+
+			rlist = (List *) list_nth(plan->returningLists, whichrel);
+			slot = mtstate->ps.ps_ResultTupleSlot;
+			Assert(slot != NULL);
+			econtext = mtstate->ps.ps_ExprContext;
+			Assert(econtext != NULL);
+
+			resultRelInfo->ri_returningList = rlist;
+			resultRelInfo->ri_projectReturning =
+				ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+										resultRelInfo->ri_RelationDesc->rd_att);
+		}
+
+		/* Set the list of arbiter indexes if needed for ON CONFLICT */
+		if (plan->onConflictAction != ONCONFLICT_NONE)
+			resultRelInfo->ri_onConflictArbiterIndexes = plan->arbiterIndexes;
+
+		/*
+		 * If needed, Initialize target list, projection and qual for ON CONFLICT
+		 * DO UPDATE.
+		 */
+		if (plan->onConflictAction == ONCONFLICT_UPDATE)
+		{
+			ExprContext *econtext;
+			TupleDesc	relationDesc;
+			TupleDesc	tupDesc;
+
+			/*
+			 * insert may only have one relation, inheritance is not expanded.
+			 */
+			Assert(mtstate->mt_nplans == 1);
+
+			/* already exists if created by RETURNING processing above */
+			if (mtstate->ps.ps_ExprContext == NULL)
+				ExecAssignExprContext(estate, &mtstate->ps);
+
+			econtext = mtstate->ps.ps_ExprContext;
+			relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+			/* create state for DO UPDATE SET operation */
+			resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
+
+			/* initialize slot for the existing tuple */
+			resultRelInfo->ri_onConflict->oc_Existing =
+				table_slot_create(resultRelInfo->ri_RelationDesc,
+								  &mtstate->ps.state->es_tupleTable);
+
+			/*
+			 * Create the tuple slot for the UPDATE SET projection. We want a
+			 * slot of the table's type here, because the slot will be used to
+			 * insert into the table, and for RETURNING processing - which may
+			 * access system attributes.
+			 */
+			tupDesc = ExecTypeFromTL((List *) plan->onConflictSet);
+			resultRelInfo->ri_onConflict->oc_ProjSlot =
+				ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+									   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+			/* build UPDATE SET projection state */
+			resultRelInfo->ri_onConflict->oc_ProjInfo =
+				ExecBuildProjectionInfo(plan->onConflictSet, econtext,
+										resultRelInfo->ri_onConflict->oc_ProjSlot,
+										&mtstate->ps,
+										relationDesc);
+
+			/* initialize state to evaluate the WHERE clause, if any */
+			if (plan->onConflictWhere)
+			{
+				ExprState  *qualexpr;
+
+				qualexpr = ExecInitQual((List *) plan->onConflictWhere,
+										&mtstate->ps);
+				resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
+			}
+		}
+
+		/*
+		 * Initialize JunkFilter if needed.
+		 *
+		 * INSERT queries need a filter if there are any junk attrs in the
+		 * tlist.  UPDATE and DELETE always need a filter, since there's always
+		 * at least one junk attribute present --- no need to look first.
+		 * Typically, this will be a 'ctid' or 'wholerow' attribute, but in the
+		 * case of a foreign data wrapper it might be a set of junk attributes
+		 * sufficient to identify the remote row.
+		 *
+		 * If there are multiple result relations, each one needs its own junk
+		 * filter.  Note multiple rels are only possible for UPDATE/DELETE, so
+		 * we can't be fooled by some needing a filter and some not.
+		 *
+		 * This is also a convenient place to verify that the output of an
+		 * INSERT or UPDATE matches the target table(s).
+		 */
+		switch (operation)
+		{
+			case CMD_INSERT:
+				foreach(l, subplan->targetlist)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					if (tle->resjunk)
+					{
+						junk_filter_needed = true;
+						break;
+					}
+				}
+				break;
+			case CMD_UPDATE:
+			case CMD_DELETE:
+				junk_filter_needed = true;
+				break;
+			default:
+				elog(ERROR, "unknown operation");
+				break;
+		}
+
+		if (junk_filter_needed)
+		{
+			JunkFilter *j;
+			TupleTableSlot *junkresslot;
+
+			junkresslot =
+				ExecInitExtraTupleSlot(estate, NULL,
+									   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+
+			/*
+			 * For an INSERT or UPDATE, the result tuple must always match
+			 * the target table's descriptor.  For a DELETE, it won't
+			 * (indeed, there's probably no non-junk output columns).
+			 */
+			if (operation == CMD_INSERT || operation == CMD_UPDATE)
+			{
+				ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+									subplan->targetlist);
+				j = ExecInitJunkFilterInsertion(subplan->targetlist,
+												RelationGetDescr(resultRelInfo->ri_RelationDesc),
+												junkresslot);
+			}
+			else
+				j = ExecInitJunkFilter(subplan->targetlist,
+									   junkresslot);
+
+			if (operation == CMD_UPDATE || operation == CMD_DELETE)
+			{
+				/* For UPDATE/DELETE, find the appropriate junk attr now */
+				char		relkind;
+
+				relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+				if (relkind == RELKIND_RELATION ||
+					relkind == RELKIND_MATVIEW ||
+					relkind == RELKIND_PARTITIONED_TABLE)
+				{
+					j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
+					if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						elog(ERROR, "could not find junk ctid column");
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					/*
+					 * When there is a row-level trigger, there should be
+					 * a wholerow attribute.
+					 */
+					j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+				}
+				else
+				{
+					j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
+					if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						elog(ERROR, "could not find junk wholerow column");
+				}
+			}
+
+			resultRelInfo->ri_junkFilter = j;
+		}
+
+		if (operation == CMD_INSERT || operation == CMD_UPDATE)
+			ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+								subplan->targetlist);
+
+		/*
+		 * While at it, also initialize a result relation specific slot that
+		 * will be used to copy the plan's output tuples into.
+		 */
+		Assert(mtstate->mt_scans[whichrel] == NULL);
+		mtstate->mt_scans[whichrel] =
+				ExecInitExtraTupleSlot(mtstate->ps.state,
+									   ExecGetResultType(subplanstate),
+									   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	return resultRelInfo;
+}
+
 /*
  * ExecCheckTupleVisible -- verify tuple is visible
  *
@@ -404,6 +702,9 @@ ExecInsert(ModifyTableState *mtstate,
 		resultRelInfo = partRelInfo;
 	}
 
+	if (resultRelInfo->ri_IndexRelationDescs == NULL)
+		ExecOpenIndices(resultRelInfo, onconflict != ONCONFLICT_NONE);
+
 	ExecMaterializeSlot(slot);
 
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -1080,7 +1381,6 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
 						 TupleTableSlot **inserted_tuple)
 {
 	EState	   *estate = mtstate->ps.state;
-	PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 	TupleConversionMap *tupconv_map;
 	bool		tuple_deleted;
 	TupleTableSlot *epqslot = NULL;
@@ -1099,13 +1399,27 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
 				 errmsg("invalid ON UPDATE specification"),
 				 errdetail("The result tuple would appear in a different partition than the original tuple.")));
 
-	/*
-	 * When an UPDATE is run on a leaf partition, we will not have partition
-	 * tuple routing set up.  In that case, fail with partition constraint
-	 * violation error.
-	 */
-	if (proute == NULL)
-		ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+	/* Initialize tuple routing info if not already done. */
+	if (mtstate->mt_partition_tuple_routing == NULL)
+	{
+		Relation	targetRel = mtstate->rootResultRelInfo->ri_RelationDesc;
+		MemoryContext	oldcxt;
+
+		/* Things built here have to last for the query duration. */
+		oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+
+		mtstate->mt_partition_tuple_routing =
+			ExecSetupPartitionTupleRouting(estate, mtstate, targetRel);
+
+		/*
+		 * Before a partition's tuple can be re-routed, it must first
+		 * be converted to the root's format and we need a slot for
+		 * storing such tuple.
+		 */
+		Assert(mtstate->mt_root_tuple_slot == NULL);
+		mtstate->mt_root_tuple_slot = table_slot_create(targetRel, NULL);
+		MemoryContextSwitchTo(oldcxt);
+	}
 
 	/*
 	 * Row movement, part 1.  Delete the tuple, but skip RETURNING processing.
@@ -1159,7 +1473,7 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
 	 * convert the tuple into root's tuple descriptor if needed, since
 	 * ExecInsert() starts the search from root.
 	 */
-	tupconv_map = resultRelInfo->ri_ChildToRootMap;
+	tupconv_map = ExecGetChildToRootMap(resultRelInfo);
 	if (tupconv_map != NULL)
 		slot = execute_attr_map_slot(tupconv_map->attrMap,
 									 slot,
@@ -1224,6 +1538,9 @@ ExecUpdate(ModifyTableState *mtstate,
 	if (IsBootstrapProcessingMode())
 		elog(ERROR, "cannot UPDATE during bootstrap");
 
+	if (resultRelInfo->ri_IndexRelationDescs == NULL)
+		ExecOpenIndices(resultRelInfo, false);
+
 	ExecMaterializeSlot(slot);
 
 	/* BEFORE ROW UPDATE Triggers */
@@ -1337,6 +1654,13 @@ lreplace:;
 					   *retry_slot;
 			bool		retry;
 
+			/*
+			 * When an UPDATE is run directly on a leaf partition, simply fail
+			 * with partition constraint violation error.
+			 */
+			if (resultRelInfo == mtstate->rootResultRelInfo)
+				ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+
 			/*
 			 * ExecCrossPartitionUpdate will first DELETE the row from the
 			 * partition it's currently in and then insert it back into the
@@ -1927,17 +2251,19 @@ static TupleTableSlot *
 ExecModifyTable(PlanState *pstate)
 {
 	ModifyTableState *node = castNode(ModifyTableState, pstate);
+	ModifyTable *plan = (ModifyTable *) node->ps.plan;
 	EState	   *estate = node->ps.state;
 	CmdType		operation = node->operation;
-	ResultRelInfo *resultRelInfo;
+	ResultRelInfo *resultRelInfo = NULL;
 	PlanState  *subplanstate;
-	JunkFilter *junkfilter;
+	JunkFilter *junkfilter = NULL;
 	TupleTableSlot *slot;
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid;
 	ItemPointerData tuple_ctid;
 	HeapTupleData oldtupdata;
 	HeapTuple	oldtuple;
+	Relation	rootTargetDesc = node->rootResultRelInfo->ri_RelationDesc;
 
 	CHECK_FOR_INTERRUPTS();
 
@@ -1972,9 +2298,7 @@ ExecModifyTable(PlanState *pstate)
 	}
 
 	/* Preload local variables */
-	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
 	subplanstate = node->mt_plans[node->mt_whichplan];
-	junkfilter = resultRelInfo->ri_junkFilter;
 
 	/*
 	 * Fetch rows from subplan(s), and execute the required table modification
@@ -1998,17 +2322,27 @@ ExecModifyTable(PlanState *pstate)
 		if (pstate->ps_ExprContext)
 			ResetExprContext(pstate->ps_ExprContext);
 
+		/*
+		 * FDWs that can push down a modify operation would need to see the
+		 * ResultRelInfo, so fetch one if not already done before executing
+		 * the subplan, potentially opening it for the first time.
+		 */
+		if (resultRelInfo == NULL &&
+			bms_is_member(node->mt_whichplan, plan->fdwDirectModifyPlans))
+			resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+												  rootTargetDesc);
+
 		planSlot = ExecProcNode(subplanstate);
 
 		if (TupIsNull(planSlot))
 		{
-			/* advance to next subplan if any */
+			/* Signal to initialize the next plan's relation. */
+			resultRelInfo = NULL;
+
 			node->mt_whichplan++;
 			if (node->mt_whichplan < node->mt_nplans)
 			{
-				resultRelInfo++;
 				subplanstate = node->mt_plans[node->mt_whichplan];
-				junkfilter = resultRelInfo->ri_junkFilter;
 				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
 									node->mt_arowmarks[node->mt_whichplan]);
 				continue;
@@ -2017,6 +2351,17 @@ ExecModifyTable(PlanState *pstate)
 				break;
 		}
 
+		/*
+		 * Fetch the result relation for the current plan if not already done,
+		 * potentially opening it for the first time.
+		 */
+		if (resultRelInfo == NULL)
+		{
+			resultRelInfo = ExecGetResultRelation(node, node->mt_whichplan,
+												  rootTargetDesc);
+			junkfilter = resultRelInfo->ri_junkFilter;
+		}
+
 		/*
 		 * Ensure input tuple is the right format for the target relation.
 		 */
@@ -2173,13 +2518,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	ModifyTableState *mtstate;
 	CmdType		operation = node->operation;
 	int			nplans = list_length(node->plans);
-	ResultRelInfo *resultRelInfo;
 	Plan	   *subplan;
-	ListCell   *l,
-			   *l1;
+	ListCell   *l;
 	int			i;
 	Relation	rel;
-	bool		update_tuple_routing_needed = node->partColsUpdated;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -2196,11 +2538,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	mtstate->canSetTag = node->canSetTag;
 	mtstate->mt_done = false;
 
+	/*
+	 * call ExecInitNode on each of the plans to be executed and save the
+	 * results into the array "mt_plans".
+	 */
+	mtstate->mt_nplans = nplans;
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
+	i = 0;
+	foreach(l, node->plans)
+	{
+		subplan = (Plan *) lfirst(l);
+
+		mtstate->mt_plans[i++] = ExecInitNode(subplan, estate, eflags);
+	}
+
 	mtstate->resultRelInfo = (ResultRelInfo *)
 		palloc(nplans * sizeof(ResultRelInfo));
 	mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
 
+	/* Initialize some global state for RETURNING projections. */
+	if (node->returningLists)
+	{
+		/*
+		 * Initialize result tuple slot and assign its rowtype using the first
+		 * RETURNING list.  We assume the rest will look the same.
+		 */
+		mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
+
+		/* Set up a slot for the output of the RETURNING projection(s) */
+		ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
+
+		/* Need an econtext too */
+		if (mtstate->ps.ps_ExprContext == NULL)
+			ExecAssignExprContext(estate, &mtstate->ps);
+	}
+	else
+	{
+		/*
+		 * We still must construct a dummy result tuple type, because InitPlan
+		 * expects one (maybe should change that?).
+		 */
+		mtstate->ps.plan->targetlist = NIL;
+		ExecInitResultTypeTL(&mtstate->ps);
+
+		mtstate->ps.ps_ExprContext = NULL;
+	}
+
 	/*----------
 	 * Resolve the target relation. This is the same as:
 	 *
@@ -2210,12 +2593,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 * - the root partitioned table used for tuple routing.
 	 *
 	 * If it's a partitioned table, the root partition doesn't appear
-	 * elsewhere in the plan and its RT index is given explicitly in
-	 * node->rootRelation.  Otherwise (i.e. table inheritance) the target
-	 * relation is the first relation in the node->resultRelations list.
+	 * elsewhere in the plan unless if it's an INSERT and its RT index is
+	 * given explicitly in node->rootRelation.  Otherwise (i.e. table
+	 * inheritance) the target relation is the first relation in the
+	 * node->resultRelations list.
 	 *----------
 	 */
-	if (node->rootRelation > 0)
+	if (node->rootRelation > 0 && operation != CMD_INSERT)
 	{
 		rel = ExecGetRangeTableRelation(estate, node->rootRelation);
 		mtstate->rootResultRelInfo = makeNode(ResultRelInfo);
@@ -2226,14 +2610,18 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		Index	rootRelation = linitial_int(node->resultRelations);
 
+		/*
+		 * Unlike a partitioned target relation, the target relation in this
+		 * case will be actually used by ExecModifyTable(), so use
+		 * ExecGetResultRelation() to get the ResultRelInfo, because it
+		 * initializes some fields that a bare InitResultRelInfo() doesn't.
+		 */
 		rel = ExecGetRangeTableRelation(estate, rootRelation);
-		mtstate->rootResultRelInfo = mtstate->resultRelInfo;
-		ExecInitResultRelation(estate, mtstate->resultRelInfo, rootRelation,
-							   rel);
+		mtstate->rootResultRelInfo = ExecGetResultRelation(mtstate, 0, rel);
+		Assert(mtstate->rootResultRelInfo == mtstate->resultRelInfo);
 	}
 
 	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
-	mtstate->mt_nplans = nplans;
 
 	/* set up epqstate with dummy subplan data for the moment */
 	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
@@ -2247,261 +2635,15 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		ExecSetupTransitionCaptureState(mtstate, estate);
 
 	/*
-	 * call ExecInitNode on each of the plans to be executed and save the
-	 * results into the array "mt_plans".  This is also a convenient place to
-	 * verify that the proposed target relations are valid and open their
-	 * indexes for insertion of new index entries.
-	 */
-	resultRelInfo = mtstate->resultRelInfo;
-	i = 0;
-	forboth(l, node->resultRelations, l1, node->plans)
-	{
-		Index		resultRelation = lfirst_int(l);
-
-		subplan = (Plan *) lfirst(l1);
-
-		/*
-		 * This opens result relation and fills ResultRelInfo. (root relation
-		 * was initialized already.)
-		 */
-		if (resultRelInfo != mtstate->rootResultRelInfo)
-			ExecInitResultRelation(estate, resultRelInfo, resultRelation, rel);
-
-		/* Initialize the usesFdwDirectModify flag */
-		resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
-															  node->fdwDirectModifyPlans);
-
-		/*
-		 * Verify result relation is a valid target for the current operation
-		 */
-		CheckValidResultRel(resultRelInfo, operation);
-
-		/*
-		 * If there are indices on the result relation, open them and save
-		 * descriptors in the result relation info, so that we can add new
-		 * index entries for the tuples we add/update.  We need not do this
-		 * for a DELETE, however, since deletion doesn't affect indexes. Also,
-		 * inside an EvalPlanQual operation, the indexes might be open
-		 * already, since we share the resultrel state with the original
-		 * query.
-		 */
-		if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
-			operation != CMD_DELETE &&
-			resultRelInfo->ri_IndexRelationDescs == NULL)
-			ExecOpenIndices(resultRelInfo,
-							node->onConflictAction != ONCONFLICT_NONE);
-
-		/*
-		 * If this is an UPDATE and a BEFORE UPDATE trigger is present, the
-		 * trigger itself might modify the partition-key values. So arrange
-		 * for tuple routing.
-		 */
-		if (resultRelInfo->ri_TrigDesc &&
-			resultRelInfo->ri_TrigDesc->trig_update_before_row &&
-			operation == CMD_UPDATE)
-			update_tuple_routing_needed = true;
-
-		/* Now init the plan for this result rel */
-		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
-		mtstate->mt_scans[i] =
-			ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
-								   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
-		/* Also let FDWs init themselves for foreign-table result rels */
-		if (!resultRelInfo->ri_usesFdwDirectModify &&
-			resultRelInfo->ri_FdwRoutine != NULL &&
-			resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
-		{
-			List	   *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
-
-			resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
-															 resultRelInfo,
-															 fdw_private,
-															 i,
-															 eflags);
-		}
-
-		/*
-		 * If needed, initialize a map to convert tuples in the child format
-		 * to the format of the table mentioned in the query (root relation).
-		 * It's needed for update tuple routing, because the routing starts
-		 * from the root relation.  It's also needed for capturing transition
-		 * tuples, because the transition tuple store can only store tuples in
-		 * the root table format.
-		 *
-		 * For INSERT, the map is only initialized for a given partition when
-		 * the partition itself is first initialized by ExecFindPartition().
-		 */
-		if (update_tuple_routing_needed ||
-			(mtstate->mt_transition_capture &&
-			 mtstate->operation != CMD_INSERT))
-			resultRelInfo->ri_ChildToRootMap =
-				convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
-									   RelationGetDescr(mtstate->rootResultRelInfo->ri_RelationDesc));
-		resultRelInfo++;
-		i++;
-	}
-
-	/*
-	 * If it's not a partitioned table after all, UPDATE tuple routing should
-	 * not be attempted.
-	 */
-	if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
-		update_tuple_routing_needed = false;
-
-	/*
-	 * Build state for tuple routing if it's an INSERT or if it's an UPDATE of
-	 * partition key.
+	 * Build state for tuple routing if it's an INSERT.  An UPDATE might need
+	 * it too, but it's initialized only when it actually ends up moving
+	 * tuples between partitions; see ExecCrossPartitionUpdate().
 	 */
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-		(operation == CMD_INSERT || update_tuple_routing_needed))
+		operation == CMD_INSERT)
 		mtstate->mt_partition_tuple_routing =
 			ExecSetupPartitionTupleRouting(estate, mtstate, rel);
 
-	/*
-	 * For update row movement we'll need a dedicated slot to store the tuples
-	 * that have been converted from partition format to the root table
-	 * format.
-	 */
-	if (update_tuple_routing_needed)
-		mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
-
-	/*
-	 * Initialize any WITH CHECK OPTION constraints if needed.
-	 */
-	resultRelInfo = mtstate->resultRelInfo;
-	foreach(l, node->withCheckOptionLists)
-	{
-		List	   *wcoList = (List *) lfirst(l);
-		List	   *wcoExprs = NIL;
-		ListCell   *ll;
-
-		foreach(ll, wcoList)
-		{
-			WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
-			ExprState  *wcoExpr = ExecInitQual((List *) wco->qual,
-											   &mtstate->ps);
-
-			wcoExprs = lappend(wcoExprs, wcoExpr);
-		}
-
-		resultRelInfo->ri_WithCheckOptions = wcoList;
-		resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
-		resultRelInfo++;
-	}
-
-	/*
-	 * Initialize RETURNING projections if needed.
-	 */
-	if (node->returningLists)
-	{
-		TupleTableSlot *slot;
-		ExprContext *econtext;
-
-		/*
-		 * Initialize result tuple slot and assign its rowtype using the first
-		 * RETURNING list.  We assume the rest will look the same.
-		 */
-		mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
-
-		/* Set up a slot for the output of the RETURNING projection(s) */
-		ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
-		slot = mtstate->ps.ps_ResultTupleSlot;
-
-		/* Need an econtext too */
-		if (mtstate->ps.ps_ExprContext == NULL)
-			ExecAssignExprContext(estate, &mtstate->ps);
-		econtext = mtstate->ps.ps_ExprContext;
-
-		/*
-		 * Build a projection for each result rel.
-		 */
-		resultRelInfo = mtstate->resultRelInfo;
-		foreach(l, node->returningLists)
-		{
-			List	   *rlist = (List *) lfirst(l);
-
-			resultRelInfo->ri_returningList = rlist;
-			resultRelInfo->ri_projectReturning =
-				ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
-										resultRelInfo->ri_RelationDesc->rd_att);
-			resultRelInfo++;
-		}
-	}
-	else
-	{
-		/*
-		 * We still must construct a dummy result tuple type, because InitPlan
-		 * expects one (maybe should change that?).
-		 */
-		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;
-	if (node->onConflictAction != ONCONFLICT_NONE)
-		resultRelInfo->ri_onConflictArbiterIndexes = node->arbiterIndexes;
-
-	/*
-	 * If needed, Initialize target list, projection and qual for ON CONFLICT
-	 * DO UPDATE.
-	 */
-	if (node->onConflictAction == ONCONFLICT_UPDATE)
-	{
-		ExprContext *econtext;
-		TupleDesc	relationDesc;
-		TupleDesc	tupDesc;
-
-		/* insert may only have one plan, inheritance is not expanded */
-		Assert(nplans == 1);
-
-		/* already exists if created by RETURNING processing above */
-		if (mtstate->ps.ps_ExprContext == NULL)
-			ExecAssignExprContext(estate, &mtstate->ps);
-
-		econtext = mtstate->ps.ps_ExprContext;
-		relationDesc = resultRelInfo->ri_RelationDesc->rd_att;
-
-		/* create state for DO UPDATE SET operation */
-		resultRelInfo->ri_onConflict = makeNode(OnConflictSetState);
-
-		/* initialize slot for the existing tuple */
-		resultRelInfo->ri_onConflict->oc_Existing =
-			table_slot_create(resultRelInfo->ri_RelationDesc,
-							  &mtstate->ps.state->es_tupleTable);
-
-		/*
-		 * Create the tuple slot for the UPDATE SET projection. We want a slot
-		 * of the table's type here, because the slot will be used to insert
-		 * into the table, and for RETURNING processing - which may access
-		 * system attributes.
-		 */
-		tupDesc = ExecTypeFromTL((List *) node->onConflictSet);
-		resultRelInfo->ri_onConflict->oc_ProjSlot =
-			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
-								   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
-		/* build UPDATE SET projection state */
-		resultRelInfo->ri_onConflict->oc_ProjInfo =
-			ExecBuildProjectionInfo(node->onConflictSet, econtext,
-									resultRelInfo->ri_onConflict->oc_ProjSlot,
-									&mtstate->ps,
-									relationDesc);
-
-		/* initialize state to evaluate the WHERE clause, if any */
-		if (node->onConflictWhere)
-		{
-			ExprState  *qualexpr;
-
-			qualexpr = ExecInitQual((List *) node->onConflictWhere,
-									&mtstate->ps);
-			resultRelInfo->ri_onConflict->oc_WhereClause = qualexpr;
-		}
-	}
-
 	/*
 	 * If we have any secondary relations in an UPDATE or DELETE, they need to
 	 * be treated like non-locked relations in SELECT FOR UPDATE, ie, the
@@ -2537,121 +2679,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
 						mtstate->mt_arowmarks[0]);
 
-	/*
-	 * Initialize the junk filter(s) if needed.  INSERT queries need a filter
-	 * if there are any junk attrs in the tlist.  UPDATE and DELETE always
-	 * need a filter, since there's always at least one junk attribute present
-	 * --- no need to look first.  Typically, this will be a 'ctid' or
-	 * 'wholerow' attribute, but in the case of a foreign data wrapper it
-	 * might be a set of junk attributes sufficient to identify the remote
-	 * row.
-	 *
-	 * If there are multiple result relations, each one needs its own junk
-	 * filter.  Note multiple rels are only possible for UPDATE/DELETE, so we
-	 * can't be fooled by some needing a filter and some not.
-	 *
-	 * This section of code is also a convenient place to verify that the
-	 * output of an INSERT or UPDATE matches the target table(s).
-	 */
-	{
-		bool		junk_filter_needed = false;
-
-		switch (operation)
-		{
-			case CMD_INSERT:
-				foreach(l, subplan->targetlist)
-				{
-					TargetEntry *tle = (TargetEntry *) lfirst(l);
-
-					if (tle->resjunk)
-					{
-						junk_filter_needed = true;
-						break;
-					}
-				}
-				break;
-			case CMD_UPDATE:
-			case CMD_DELETE:
-				junk_filter_needed = true;
-				break;
-			default:
-				elog(ERROR, "unknown operation");
-				break;
-		}
-
-		if (junk_filter_needed)
-		{
-			resultRelInfo = mtstate->resultRelInfo;
-			for (i = 0; i < nplans; i++)
-			{
-				JunkFilter *j;
-				TupleTableSlot *junkresslot;
-
-				subplan = mtstate->mt_plans[i]->plan;
-
-				junkresslot =
-					ExecInitExtraTupleSlot(estate, NULL,
-										   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-
-				/*
-				 * For an INSERT or UPDATE, the result tuple must always match
-				 * the target table's descriptor.  For a DELETE, it won't
-				 * (indeed, there's probably no non-junk output columns).
-				 */
-				if (operation == CMD_INSERT || operation == CMD_UPDATE)
-				{
-					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
-										subplan->targetlist);
-					j = ExecInitJunkFilterInsertion(subplan->targetlist,
-													RelationGetDescr(resultRelInfo->ri_RelationDesc),
-													junkresslot);
-				}
-				else
-					j = ExecInitJunkFilter(subplan->targetlist,
-										   junkresslot);
-
-				if (operation == CMD_UPDATE || operation == CMD_DELETE)
-				{
-					/* For UPDATE/DELETE, find the appropriate junk attr now */
-					char		relkind;
-
-					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
-					if (relkind == RELKIND_RELATION ||
-						relkind == RELKIND_MATVIEW ||
-						relkind == RELKIND_PARTITIONED_TABLE)
-					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
-							elog(ERROR, "could not find junk ctid column");
-					}
-					else if (relkind == RELKIND_FOREIGN_TABLE)
-					{
-						/*
-						 * When there is a row-level trigger, there should be
-						 * a wholerow attribute.
-						 */
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-					}
-					else
-					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
-							elog(ERROR, "could not find junk wholerow column");
-					}
-				}
-
-				resultRelInfo->ri_junkFilter = j;
-				resultRelInfo++;
-			}
-		}
-		else
-		{
-			if (operation == CMD_INSERT)
-				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
-									subplan->targetlist);
-		}
-	}
-
 	/*
 	 * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
 	 * to estate->es_auxmodifytables so that it will be run to completion by
@@ -2681,20 +2708,6 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
-	/*
-	 * Allow any FDWs to shut down
-	 */
-	for (i = 0; i < node->mt_nplans; i++)
-	{
-		ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
-
-		if (!resultRelInfo->ri_usesFdwDirectModify &&
-			resultRelInfo->ri_FdwRoutine != NULL &&
-			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
-			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
-														   resultRelInfo);
-	}
-
 	/*
 	 * Close all the partitioned tables, leaf partitions, and their indices
 	 * and release the slot used for tuple routing, if set.
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 2bc349b782..4b79267e1e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -573,6 +573,7 @@ extern int	ExecCleanTargetListLength(List *targetlist);
 extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo);
 extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo);
+extern TupleConversionMap *ExecGetChildToRootMap(ResultRelInfo *resultRelInfo);
 
 /*
  * prototypes from functions in execIndexing.c
@@ -616,4 +617,8 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 									 const char *relname);
 
+/* prototypes from nodeModifyTable.c */
+extern ResultRelInfo *ExecGetResultRelation(ModifyTableState *mtstate, int whichrel,
+					  Relation rootTargetDesc);
+
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e6bdf80555..960a376d11 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -499,6 +499,7 @@ typedef struct ResultRelInfo
 	 * transition tuple capture or update partition row movement is active.
 	 */
 	TupleConversionMap *ri_ChildToRootMap;
+	bool		ri_ChildToRootMapValid;	/* has the map been initialized? */
 
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 8819921d0a..51f42abf2a 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -341,8 +341,8 @@ DETAIL:  Failing row contains (105, 85, null, b, 15).
 -- fail, no partition key update, so no attempt to move tuple,
 -- but "a = 'a'" violates partition constraint enforced by root partition)
 UPDATE part_b_10_b_20 set a = 'a';
-ERROR:  new row for relation "part_c_1_100" violates partition constraint
-DETAIL:  Failing row contains (null, 1, 96, 12, a).
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (null, 96, a, 12, 1).
 -- ok, partition key update, no constraint violation
 UPDATE range_parted set d = d - 10 WHERE d > 10;
 -- ok, no partition key update, no constraint violation
@@ -372,8 +372,8 @@ UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a;
 
 -- fail, row movement happens only within the partition subtree.
 UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *;
-ERROR:  new row for relation "part_d_1_15" violates partition constraint
-DETAIL:  Failing row contains (2, 117, 2, b, 7).
+ERROR:  new row for relation "part_b_10_b_20" violates partition constraint
+DETAIL:  Failing row contains (2, 117, b, 7, 2).
 -- ok, row movement, with subset of rows moved into different partition.
 UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c;
  a | ?column? 
@@ -814,8 +814,8 @@ INSERT into sub_parted VALUES (1,2,10);
 -- Test partition constraint violation when intermediate ancestor is used and
 -- constraint is inherited from upper root.
 UPDATE sub_parted set a = 2 WHERE c = 10;
-ERROR:  new row for relation "sub_part2" violates partition constraint
-DETAIL:  Failing row contains (2, 10, 2).
+ERROR:  new row for relation "sub_parted" violates partition constraint
+DETAIL:  Failing row contains (2, 2, 10).
 -- Test update-partition-key, where the unpruned partitions do not have their
 -- partition keys updated.
 SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1;
-- 
2.24.1

