From df92e509ec16077de8f97e44b743a118dd1323ec Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Fri, 29 May 2020 21:49:56 +0900
Subject: [PATCH v1] Overhaul UPDATE's targetlist processing

Instead of emitting the full tuple matching the target table's tuple
descriptor, make the plan emit only the attributes that are assigned
values in the SET clause, plus row-identity junk attributes as before.
This allows us to avoid making a separate plan for each target
relation in the inheritance case, because the only reason it is so
currently is to account for the fact that each target relations may
have a set of attributes that is different from others.  Having only
one plan suffices, because the set of assigned attributes must be same
in all the result relations.

While the plan will now produce only the assigned attributes and
row-identity junk attributes, other columns' values are filled by
refetching the old tuple. To that end, there will be a targetlist for
each target relation to compute the full tuple, that is, by combining
the values from the plan tuple and the old tuple, but they are passed
separately in the ModifyTable node.

Implementation notes:

* In the inheritance case, as the same plan produces tuples to be
updated from multiple result relations, the tuples now need to also
identity which table they come from, so an additional junk attribute
"tableoid" is present in that case.

* Considering that the inheritance set may contain foreign tables
that require a different (set of) row-identity junk attribute(s), the
plan needs to emit multiple distinct junk attributes.  When transposed
to a child scan node, this targetlist emits a non-NULL value for
the junk attribute that's valid for the child relation and NULL for
others.

* Executor and FDW execution APIs can no longer assume any specific
order in which the result relations will be processed. For each
tuple to be updated/deleted, result relation is selected by looking
it up in a hash table using the "tableoid" value as the key.

* Since the plan does not emit values for all the attributes, FDW
APIs may not assume that the individual column values in the
TupleTableSlot containing the plan tuple are accessible by their
attribute numbers.

TODO:

* Reconsider having only one plan!
* Update FDW handler docs to reflect the API changes
---
 contrib/postgres_fdw/postgres_fdw.c           |  14 +-
 src/backend/commands/explain.c                |   6 +-
 src/backend/commands/trigger.c                |   5 +-
 src/backend/executor/execMain.c               |   1 -
 src/backend/executor/execPartition.c          | 110 +---
 src/backend/executor/nodeModifyTable.c        | 765 +++++++++++++++-----------
 src/backend/nodes/copyfuncs.c                 |   1 +
 src/backend/nodes/outfuncs.c                  |  37 +-
 src/backend/nodes/readfuncs.c                 |   1 +
 src/backend/optimizer/path/allpaths.c         |  22 +-
 src/backend/optimizer/plan/createplan.c       |  42 +-
 src/backend/optimizer/plan/planner.c          | 755 ++++---------------------
 src/backend/optimizer/plan/setrefs.c          |  64 ++-
 src/backend/optimizer/prep/prepjointree.c     |   1 -
 src/backend/optimizer/prep/preptlist.c        | 110 +++-
 src/backend/optimizer/util/appendinfo.c       | 110 ++--
 src/backend/optimizer/util/inherit.c          | 300 +++++++++-
 src/backend/optimizer/util/pathnode.c         |   6 +-
 src/backend/optimizer/util/plancat.c          | 127 ++++-
 src/backend/optimizer/util/relnode.c          |  35 +-
 src/backend/optimizer/util/tlist.c            |  19 +
 src/backend/rewrite/rewriteHandler.c          |  17 +-
 src/include/executor/executor.h               |   9 +
 src/include/nodes/execnodes.h                 |  33 +-
 src/include/nodes/nodes.h                     |   2 +
 src/include/nodes/pathnodes.h                 | 103 +++-
 src/include/nodes/plannodes.h                 |   5 +-
 src/include/optimizer/appendinfo.h            |   3 +
 src/include/optimizer/optimizer.h             |   1 +
 src/include/optimizer/pathnode.h              |   3 +-
 src/include/optimizer/plancat.h               |   6 +-
 src/include/optimizer/prep.h                  |   6 +-
 src/include/optimizer/tlist.h                 |   2 +
 src/test/regress/expected/inherit.out         |  22 +-
 src/test/regress/expected/partition_join.out  |  42 +-
 src/test/regress/expected/partition_prune.out | 199 +++----
 src/test/regress/expected/rowsecurity.out     | 135 ++---
 src/test/regress/expected/updatable_views.out | 173 +++---
 src/test/regress/expected/update.out          |  43 +-
 src/test/regress/expected/with.out            |  56 +-
 40 files changed, 1801 insertions(+), 1590 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9fc53ca..697ec68 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1825,7 +1825,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									rte,
 									resultRelInfo,
 									mtstate->operation,
-									mtstate->mt_plans[subplan_index]->plan,
+									mtstate->mt_plans[0]->plan,
 									query,
 									target_attrs,
 									has_returning,
@@ -1938,8 +1938,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	 */
 	if (plan && plan->operation == CMD_UPDATE &&
 		(resultRelInfo->ri_usesFdwDirectModify ||
-		 resultRelInfo->ri_FdwState) &&
-		resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
+		 resultRelInfo->ri_FdwState))
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@@ -2150,6 +2149,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 	List	   *params_list = NIL;
 	List	   *returningList = NIL;
 	List	   *retrieved_attrs = NIL;
+	ResultRelPlanInfo *resultInfo;
 
 	/*
 	 * Decide whether it is safe to modify a foreign table directly.
@@ -2165,7 +2165,9 @@ postgresPlanDirectModify(PlannerInfo *root,
 	 * It's unsafe to modify a foreign table directly if there are any local
 	 * joins needed.
 	 */
-	subplan = (Plan *) list_nth(plan->plans, subplan_index);
+	subplan = (Plan *) linitial(plan->plans);
+	resultInfo = root->result_rel_array[resultRelation];
+	Assert(resultInfo != NULL);
 	if (!IsA(subplan, ForeignScan))
 		return false;
 	fscan = (ForeignScan *) subplan;
@@ -2211,7 +2213,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 			if (attno <= InvalidAttrNumber) /* shouldn't happen */
 				elog(ERROR, "system-column update is not supported");
 
-			tle = get_tle_by_resno(subplan->targetlist, attno);
+			tle = get_tle_by_resno(resultInfo->subplanTargetList, attno);
 
 			if (!tle)
 				elog(ERROR, "attribute number %d not found in subplan targetlist",
@@ -2271,7 +2273,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 		case CMD_UPDATE:
 			deparseDirectUpdateSql(&sql, root, resultRelation, rel,
 								   foreignrel,
-								   ((Plan *) fscan)->targetlist,
+								   resultInfo->subplanTargetList,
 								   targetAttrs,
 								   remote_exprs, &params_list,
 								   returningList, &retrieved_attrs);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index efd7201..739de52 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3588,14 +3588,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 	}
 
 	/* Should we explicitly label target relations? */
-	labeltargets = (mtstate->mt_nplans > 1 ||
-					(mtstate->mt_nplans == 1 &&
+	labeltargets = (mtstate->mt_nrels > 1 ||
+					(mtstate->mt_nrels == 1 &&
 					 mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
 
 	if (labeltargets)
 		ExplainOpenGroup("Target Tables", "Target Tables", false, es);
 
-	for (j = 0; j < mtstate->mt_nplans; j++)
+	for (j = 0; j < mtstate->mt_nrels; j++)
 	{
 		ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
 		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..58a5111 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2686,7 +2686,10 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 		{
 			TupleTableSlot *epqslot_clean;
 
-			epqslot_clean = ExecFilterJunk(relinfo->ri_junkFilter, epqslot_candidate);
+			Assert(relinfo->ri_projectNew != NULL);
+			Assert(relinfo->ri_oldTupleSlot != NULL);
+			epqslot_clean = ExecGetUpdateNewTuple(relinfo, epqslot_candidate,
+												  tupleid, NULL);
 
 			if (newslot != epqslot_clean)
 				ExecCopySlot(newslot, epqslot_clean);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4fdffad..166486d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1318,7 +1318,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_usesFdwDirectModify = false;
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_GeneratedExprs = NULL;
-	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
 	resultRelInfo->ri_onConflictArbiterIndexes = NIL;
 	resultRelInfo->ri_onConflict = NULL;
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index fb6ce49..96002c1 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"
@@ -94,7 +95,6 @@ struct PartitionTupleRouting
 	ResultRelInfo **partitions;
 	int			num_partitions;
 	int			max_partitions;
-	HTAB	   *subplan_resultrel_htab;
 	MemoryContext memcxt;
 };
 
@@ -147,16 +147,7 @@ typedef struct PartitionDispatchData
 	int			indexes[FLEXIBLE_ARRAY_MEMBER];
 }			PartitionDispatchData;
 
-/* struct to hold result relations coming from UPDATE subplans */
-typedef struct SubplanResultRelHashElem
-{
-	Oid			relid;			/* hash key -- must be first */
-	ResultRelInfo *rri;
-} SubplanResultRelHashElem;
-
 
-static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
-										   PartitionTupleRouting *proute);
 static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
 											EState *estate, PartitionTupleRouting *proute,
 											PartitionDispatch dispatch,
@@ -212,7 +203,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
@@ -234,17 +224,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;
 }
 
@@ -352,7 +331,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 
 		if (partdesc->is_leaf[partidx])
 		{
-			ResultRelInfo *rri;
+			ResultRelInfo *rri = NULL;
 
 			/*
 			 * Look to see if we've already got a ResultRelInfo for this
@@ -366,28 +345,32 @@ ExecFindPartition(ModifyTableState *mtstate,
 			}
 			else
 			{
-				bool		found = false;
-
 				/*
 				 * 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 we might be able to find one in the mtstate's
+				 * result relations array.
 				 */
-				if (proute->subplan_resultrel_htab)
+				if (mtstate && mtstate->mt_subplan_resultrel_hash)
 				{
 					Oid			partoid = partdesc->oids[partidx];
-					SubplanResultRelHashElem *elem;
+					int			whichrel;	/* unused */
 
-					elem = hash_search(proute->subplan_resultrel_htab,
-									   &partoid, HASH_FIND, NULL);
-					if (elem)
+					rri = ExecLookupResultRelByOid(mtstate, partoid,
+												   &whichrel);
+					if (rri)
 					{
-						found = true;
-						rri = elem->rri;
-
 						/* Verify this ResultRelInfo allows INSERTs */
 						CheckValidResultRel(rri, CMD_INSERT);
 
+						/*
+						 * This is required in order to convert the
+						 * partition's tuple to be compatible with the root
+						 * partitioned table's tuple descriptor. When
+						 * generating the per-subplan result rels, this would
+						 * not have been set.
+						 */
+						rri->ri_PartitionRoot = proute->partition_root;
+
 						/* Set up the PartitionRoutingInfo for it */
 						ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
 											rri, partidx);
@@ -395,7 +378,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 				}
 
 				/* We need to create a new one. */
-				if (!found)
+				if (rri == NULL)
 					rri = ExecInitPartitionInfo(mtstate, estate, proute,
 												dispatch,
 												rootResultRelInfo, partidx);
@@ -447,51 +430,6 @@ ExecFindPartition(ModifyTableState *mtstate,
 }
 
 /*
- * ExecHashSubPlanResultRelsByOid
- *		Build a hash table to allow fast lookups of subplan ResultRelInfos by
- *		partition Oid.  We also populate the subplan ResultRelInfo with an
- *		ri_PartitionRoot.
- */
-static void
-ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
-							   PartitionTupleRouting *proute)
-{
-	HASHCTL		ctl;
-	HTAB	   *htab;
-	int			i;
-
-	memset(&ctl, 0, sizeof(ctl));
-	ctl.keysize = sizeof(Oid);
-	ctl.entrysize = sizeof(SubplanResultRelHashElem);
-	ctl.hcxt = CurrentMemoryContext;
-
-	htab = hash_create("PartitionTupleRouting table", mtstate->mt_nplans,
-					   &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++)
-	{
-		ResultRelInfo *rri = &mtstate->resultRelInfo[i];
-		bool		found;
-		Oid			partoid = RelationGetRelid(rri->ri_RelationDesc);
-		SubplanResultRelHashElem *elem;
-
-		elem = (SubplanResultRelHashElem *)
-			hash_search(htab, &partoid, HASH_ENTER, &found);
-		Assert(!found);
-		elem->rri = rri;
-
-		/*
-		 * This is required in order to convert the partition's tuple to be
-		 * compatible with the root partitioned table's tuple descriptor. When
-		 * generating the per-subplan result rels, this was not set.
-		 */
-		rri->ri_PartitionRoot = proute->partition_root;
-	}
-}
-
-/*
  * ExecInitPartitionInfo
  *		Lock the partition and initialize ResultRelInfo.  Also setup other
  *		information for the partition and store it in the next empty slot in
@@ -566,10 +504,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		Assert((node->operation == CMD_INSERT &&
 				list_length(node->withCheckOptionLists) == 1 &&
-				list_length(node->plans) == 1) ||
+				list_length(node->resultRelations) == 1) ||
 			   (node->operation == CMD_UPDATE &&
 				list_length(node->withCheckOptionLists) ==
-				list_length(node->plans)));
+				list_length(node->resultRelations)));
 
 		/*
 		 * Use the WCO list of the first plan as a reference to calculate
@@ -626,10 +564,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		/* See the comment above for WCO lists. */
 		Assert((node->operation == CMD_INSERT &&
 				list_length(node->returningLists) == 1 &&
-				list_length(node->plans) == 1) ||
+				list_length(node->resultRelations) == 1) ||
 			   (node->operation == CMD_UPDATE &&
 				list_length(node->returningLists) ==
-				list_length(node->plans)));
+				list_length(node->resultRelations)));
 
 		/*
 		 * Use the RETURNING list of the first plan as a reference to
@@ -1096,7 +1034,7 @@ void
 ExecCleanupTupleRouting(ModifyTableState *mtstate,
 						PartitionTupleRouting *proute)
 {
-	HTAB	   *htab = proute->subplan_resultrel_htab;
+	HTAB	   *htab = mtstate->mt_subplan_resultrel_hash;
 	int			i;
 
 	/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..c95fd05 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -75,6 +75,8 @@ static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
 static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
 static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
 												   int whichplan);
+static TupleTableSlot *ExecGetNewInsertTuple(ResultRelInfo *relinfo,
+					  TupleTableSlot *planSlot);
 
 /*
  * Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1043,6 +1045,130 @@ ldelete:;
 	return NULL;
 }
 
+/* struct to hold info about UPDATE/DELETE result relations */
+typedef struct SubplanResultRelHashElem
+{
+	Oid			relid;			/* hash key -- must be first */
+	int			whichrel;		/* index into mtstate->resultRelInfo */
+} SubplanResultRelHashElem;
+
+/*
+ * ExecHashSubPlanResultRelsByOid
+ *		Build a hash table to allow fast lookups of subplan ResultRelInfos by
+ *		result relation OIDs.
+ */
+static void
+ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate)
+{
+	HASHCTL		ctl;
+	HTAB	   *htab;
+	int			i;
+
+	memset(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(SubplanResultRelHashElem);
+	ctl.hcxt = CurrentMemoryContext;
+
+	Assert(mtstate->mt_subplan_resultrel_hash == NULL);
+
+	htab = hash_create("ModifyTable result relations", mtstate->mt_nrels,
+					   &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+	mtstate->mt_subplan_resultrel_hash = htab;
+
+	/* Hash all subplans by their Oid */
+	for (i = 0; i < mtstate->mt_nrels; i++)
+	{
+		ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+		bool		found;
+		Oid			partoid = RelationGetRelid(rri->ri_RelationDesc);
+		SubplanResultRelHashElem *elem;
+
+		elem = (SubplanResultRelHashElem *)
+			hash_search(htab, &partoid, HASH_ENTER, &found);
+		Assert(!found);
+		elem->whichrel = i;
+	}
+}
+
+ResultRelInfo *
+ExecLookupResultRelByOid(ModifyTableState *mtstate, Oid reloid,
+						 int *whichrel)
+{
+	SubplanResultRelHashElem *elem;
+
+	Assert(mtstate->mt_subplan_resultrel_hash != NULL);
+	elem = hash_search(mtstate->mt_subplan_resultrel_hash, &reloid,
+					   HASH_FIND, NULL);
+
+	*whichrel = -1;
+	if (elem)
+	{
+		*whichrel = elem->whichrel;
+		return mtstate->resultRelInfo + elem->whichrel;
+	}
+
+	return NULL;
+}
+
+/*
+ * ExecProjectNewInsertTuple
+ *		This prepares a "new" tuple ready to be put into a result relation
+ *		by removing any junk columns of the tuple produced by a plan.
+ */
+static TupleTableSlot *
+ExecGetNewInsertTuple(ResultRelInfo *relinfo,
+					  TupleTableSlot *planSlot)
+{
+	ProjectionInfo *newProj = relinfo->ri_projectNew;
+	ExprContext   *econtext;
+
+	if (newProj == NULL)
+		return planSlot;
+
+	econtext = newProj->pi_exprContext;
+	econtext->ecxt_outertuple = planSlot;
+	return ExecProject(newProj);
+}
+
+
+/*
+ * ExecProjectNewUpdateTuple
+ *		This prepares a "new" tuple ready to be put into a result relation
+ *		by combining the "plan" tuple and the "old" tuple.
+ *
+ * The "plan" tuple contains values for only those columns that were specified
+ * in the update statement's SET clause and some junk columns which must be
+ * filtered out.  Values for the rest of the columns are taken from the "old"
+ * tuple.
+ *
+ * The caller may pass either tupleid of the old tuple or its HeapTuple.
+ */
+TupleTableSlot *
+ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+					  TupleTableSlot *planSlot,
+					  ItemPointerData *tupleid,
+					  HeapTupleData *oldtuple)
+{
+	ProjectionInfo *newProj = relinfo->ri_projectNew;
+	ExprContext   *econtext;
+	TupleTableSlot *oldSlot = relinfo->ri_oldTupleSlot;
+	Relation		relation = relinfo->ri_RelationDesc;
+
+	Assert(newProj != NULL);
+	Assert(oldSlot != NULL);
+	Assert(tupleid != NULL || oldtuple != NULL);
+	ExecClearTuple(oldSlot);
+	if (tupleid != NULL)
+		table_tuple_fetch_row_version(relation, tupleid, SnapshotAny,
+									  oldSlot);
+	else
+		ExecForceStoreHeapTuple(oldtuple, oldSlot, false);
+	econtext = newProj->pi_exprContext;
+	econtext->ecxt_scantuple = oldSlot;
+	econtext->ecxt_outertuple = planSlot;
+	return ExecProject(newProj);
+}
+
 /* ----------------------------------------------------------------
  *		ExecUpdate
  *
@@ -1269,7 +1395,8 @@ lreplace:;
 					return NULL;
 				else
 				{
-					slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+					slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
+												 tupleid, oldtuple);
 					goto lreplace;
 				}
 			}
@@ -1293,7 +1420,7 @@ lreplace:;
 			 * position of the resultRel in mtstate->resultRelInfo[].
 			 */
 			map_index = resultRelInfo - mtstate->resultRelInfo;
-			Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
+			Assert(map_index >= 0 && map_index < mtstate->mt_nrels);
 			tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
 			if (tupconv_map != NULL)
 				slot = execute_attr_map_slot(tupconv_map->attrMap,
@@ -1423,7 +1550,9 @@ lreplace:;
 								/* Tuple not passing quals anymore, exiting... */
 								return NULL;
 
-							slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+							slot = ExecGetUpdateNewTuple(resultRelInfo,
+														 epqslot, tupleid,
+														 oldtuple);
 							goto lreplace;
 
 						case TM_Deleted:
@@ -1968,7 +2097,7 @@ ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
 	ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
 	ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
 	TupleDesc	outdesc;
-	int			numResultRelInfos = mtstate->mt_nplans;
+	int			numResultRelInfos = mtstate->mt_nrels;
 	int			i;
 
 	/*
@@ -2001,7 +2130,7 @@ tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
 	if (mtstate->mt_per_subplan_tupconv_maps == NULL)
 		ExecSetupChildParentMapForSubplan(mtstate);
 
-	Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
+	Assert(whichplan >= 0 && whichplan < mtstate->mt_nrels);
 	return mtstate->mt_per_subplan_tupconv_maps[whichplan];
 }
 
@@ -2022,7 +2151,6 @@ ExecModifyTable(PlanState *pstate)
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
 	PlanState  *subplanstate;
-	JunkFilter *junkfilter;
 	TupleTableSlot *slot;
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid;
@@ -2063,9 +2191,8 @@ ExecModifyTable(PlanState *pstate)
 	}
 
 	/* Preload local variables */
-	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+	resultRelInfo = node->resultRelInfo + node->mt_whichrel;
 	subplanstate = node->mt_plans[node->mt_whichplan];
-	junkfilter = resultRelInfo->ri_junkFilter;
 
 	/*
 	 * es_result_relation_info must point to the currently active result
@@ -2102,42 +2229,53 @@ ExecModifyTable(PlanState *pstate)
 
 		planSlot = ExecProcNode(subplanstate);
 
+		/* No more tuples to process? */
 		if (TupIsNull(planSlot))
+			break;
+
+		/* Select the result relation to operate on. */
+		if (node->mt_nrels > 1)
 		{
-			/* advance to next subplan if any */
-			node->mt_whichplan++;
-			if (node->mt_whichplan < node->mt_nplans)
+			bool	isNull;
+			Datum	datum;
+			Oid		tupleRel;
+
+			/* Table OID must be present in the plan's targetlist. */
+			Assert(AttributeNumberIsValid(node->mt_tableOidAttno));
+			datum = ExecGetJunkAttribute(planSlot, node->mt_tableOidAttno,
+										 &isNull);
+			if (isNull)
+				elog(ERROR, "tableoid is NULL");
+			tupleRel = DatumGetObjectId(datum);
+			Assert(OidIsValid(tupleRel));
+
+			/* Table OID -> ResultRelInfo. */
+			resultRelInfo = ExecLookupResultRelByOid(node, tupleRel,
+													 &node->mt_whichrel);
+			Assert(node->mt_whichrel >= 0 &&
+				   node->mt_whichrel < node->mt_nrels);
+			estate->es_result_relation_info = resultRelInfo;
+
+			/* Prepare to convert transition tuples from this child. */
+			if (node->mt_transition_capture != NULL)
 			{
-				resultRelInfo++;
-				subplanstate = node->mt_plans[node->mt_whichplan];
-				junkfilter = resultRelInfo->ri_junkFilter;
-				estate->es_result_relation_info = resultRelInfo;
-				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
-									node->mt_arowmarks[node->mt_whichplan]);
-				/* Prepare to convert transition tuples from this child. */
-				if (node->mt_transition_capture != NULL)
-				{
-					node->mt_transition_capture->tcs_map =
-						tupconv_map_for_subplan(node, node->mt_whichplan);
-				}
-				if (node->mt_oc_transition_capture != NULL)
-				{
-					node->mt_oc_transition_capture->tcs_map =
-						tupconv_map_for_subplan(node, node->mt_whichplan);
-				}
-				continue;
+				node->mt_transition_capture->tcs_map =
+				tupconv_map_for_subplan(node, node->mt_whichrel);
+			}
+			if (node->mt_oc_transition_capture != NULL)
+			{
+				node->mt_oc_transition_capture->tcs_map =
+				tupconv_map_for_subplan(node, node->mt_whichrel);
 			}
-			else
-				break;
 		}
 
 		/*
 		 * Ensure input tuple is the right format for the target relation.
 		 */
-		if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
+		if (node->mt_scans[node->mt_whichrel]->tts_ops != planSlot->tts_ops)
 		{
-			ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
-			planSlot = node->mt_scans[node->mt_whichplan];
+			ExecCopySlot(node->mt_scans[node->mt_whichrel], planSlot);
+			planSlot = node->mt_scans[node->mt_whichrel];
 		}
 
 		/*
@@ -2165,80 +2303,78 @@ ExecModifyTable(PlanState *pstate)
 
 		tupleid = NULL;
 		oldtuple = NULL;
-		if (junkfilter != NULL)
+		/*
+		 * extract the 'ctid' or 'wholerow' junk attribute.
+		 */
+		if (operation == CMD_UPDATE || operation == CMD_DELETE)
 		{
-			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
-			 */
-			if (operation == CMD_UPDATE || operation == CMD_DELETE)
-			{
-				char		relkind;
-				Datum		datum;
-				bool		isNull;
-
-				relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
-				if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
-				{
-					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
-												 &isNull);
-					/* shouldn't ever get a null result... */
-					if (isNull)
-						elog(ERROR, "ctid is NULL");
-
-					tupleid = (ItemPointer) DatumGetPointer(datum);
-					tuple_ctid = *tupleid;	/* be sure we don't free ctid!! */
-					tupleid = &tuple_ctid;
-				}
-
-				/*
-				 * Use the wholerow attribute, when available, to reconstruct
-				 * the old relation tuple.
-				 *
-				 * Foreign table updates have a wholerow attribute when the
-				 * relation has a row-level trigger.  Note that the wholerow
-				 * attribute does not carry system columns.  Foreign table
-				 * triggers miss seeing those, except that we know enough here
-				 * to set t_tableOid.  Quite separately from this, the FDW may
-				 * fetch its own junk attrs to identify the row.
-				 *
-				 * Other relevant relkinds, currently limited to views, always
-				 * have a wholerow attribute.
-				 */
-				else if (AttributeNumberIsValid(junkfilter->jf_junkAttNo))
-				{
-					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
-												 &isNull);
-					/* shouldn't ever get a null result... */
-					if (isNull)
-						elog(ERROR, "wholerow is NULL");
-
-					oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
-					oldtupdata.t_len =
-						HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
-					ItemPointerSetInvalid(&(oldtupdata.t_self));
-					/* Historically, view triggers see invalid t_tableOid. */
-					oldtupdata.t_tableOid =
-						(relkind == RELKIND_VIEW) ? InvalidOid :
-						RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			char		relkind;
+			Datum		datum;
+			bool		isNull;
 
-					oldtuple = &oldtupdata;
-				}
-				else
-					Assert(relkind == RELKIND_FOREIGN_TABLE);
+			relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+			if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
+			{
+				Assert(resultRelInfo->ri_junkAttno > 0);
+				datum = ExecGetJunkAttribute(slot,
+											 resultRelInfo->ri_junkAttno,
+											 &isNull);
+				/* shouldn't ever get a null result... */
+				if (isNull)
+					elog(ERROR, "ctid is NULL");
+
+				tupleid = (ItemPointer) DatumGetPointer(datum);
+				tuple_ctid = *tupleid;	/* be sure we don't free ctid!! */
+				tupleid = &tuple_ctid;
 			}
 
 			/*
-			 * apply the junkfilter if needed.
+			 * Use the wholerow attribute, when available, to reconstruct
+			 * the old relation tuple.  The old tuple serves one of two
+			 * purposes or both: 1) it serves as the OLD tuple for any row
+			 * triggers on foreign tables, 2) it provides values for any
+			 * missing columns for the NEW tuple of the UPDATEs targeting
+			 * foreign tables, because the plan itself does not produce all
+			 * the columns of the target table; see the "oldtuple" being
+			 * passed to ExecGetUpdateNewTuple() below.
+			 *
+			 * Note that the wholerow attribute does not carry system columns,
+			 * so foreign table triggers miss seeing those, except that we
+			 * know enough here to set t_tableOid. Quite separately from this,
+			 * the FDW may fetch its own junk attrs to identify the row.
+			 *
+			 * Other relevant relkinds, currently limited to views, always
+			 * have a wholerow attribute.
 			 */
-			if (operation != CMD_DELETE)
-				slot = ExecFilterJunk(junkfilter, slot);
+			else if (AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+			{
+				datum = ExecGetJunkAttribute(slot,
+											 resultRelInfo->ri_junkAttno,
+											 &isNull);
+				/* shouldn't ever get a null result... */
+				if (isNull)
+					elog(ERROR, "wholerow is NULL");
+
+				oldtupdata.t_data = DatumGetHeapTupleHeader(datum);
+				oldtupdata.t_len =
+					HeapTupleHeaderGetDatumLength(oldtupdata.t_data);
+				ItemPointerSetInvalid(&(oldtupdata.t_self));
+				/* Historically, view triggers see invalid t_tableOid. */
+				oldtupdata.t_tableOid =
+					(relkind == RELKIND_VIEW) ? InvalidOid :
+					RelationGetRelid(resultRelInfo->ri_RelationDesc);
+					oldtuple = &oldtupdata;
+			}
+			else
+				Assert(relkind == RELKIND_FOREIGN_TABLE);
 		}
 
+		estate->es_result_relation_info = resultRelInfo;
+
 		switch (operation)
 		{
 			case CMD_INSERT:
+				slot = ExecGetNewInsertTuple(resultRelInfo, planSlot);
 				/* Prepare for tuple routing if needed. */
 				if (proute)
 					slot = ExecPrepareTupleRouting(node, estate, proute,
@@ -2250,6 +2386,10 @@ ExecModifyTable(PlanState *pstate)
 					estate->es_result_relation_info = resultRelInfo;
 				break;
 			case CMD_UPDATE:
+				Assert(resultRelInfo->ri_projectNew);
+				Assert(resultRelInfo->ri_oldTupleSlot);
+				slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot, tupleid,
+											 oldtuple);
 				slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
 								  &node->mt_epqstate, estate, node->canSetTag);
 				break;
@@ -2298,9 +2438,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	ModifyTableState *mtstate;
 	CmdType		operation = node->operation;
 	int			nplans = list_length(node->plans);
+	int			nrels = list_length(node->resultRelations);
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
 	Plan	   *subplan;
+	TupleDesc	plan_result_type;
 	ListCell   *l;
 	int			i;
 	Relation	rel;
@@ -2323,7 +2465,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
-	mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+	mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nrels);
 
 	/* If modifying a partitioned table, initialize the root table info */
 	if (node->rootResultRelIndex >= 0)
@@ -2337,23 +2479,73 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
 	mtstate->fireBSTriggers = true;
 
+	mtstate->mt_nrels = nrels;
+	mtstate->mt_whichrel = 0;
+
 	/*
-	 * 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.  Note we *must* set
-	 * estate->es_result_relation_info correctly while we initialize each
-	 * sub-plan; external modules such as FDWs may depend on that (see
-	 * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one
-	 * example).
+	 * Call ExecInitNode on the only plan to be executed and save the result
+	 * into the array "mt_plans".
+	 *
+	 * Note we *must* set es_result_relation_info correctly while we
+	 * initialize the plan; external modules such as FDWs may depend on that
+	 * (see contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify()
+	 * as one example).
 	 */
 	saved_resultRelInfo = estate->es_result_relation_info;
+	estate->es_result_relation_info = mtstate->resultRelInfo;
+	subplan = linitial(node->plans);
+	mtstate->mt_plans[0] = ExecInitNode(subplan, estate, eflags);
+	estate->es_result_relation_info = saved_resultRelInfo;
+	plan_result_type = ExecGetResultType(mtstate->mt_plans[0]);
+
+	if (mtstate->mt_nrels > 1)
+	{
+		/*
+		 * There must be "tableoid" junk attribute present if there are
+		 * multiple result relations to update or delete from.
+		 */
+		mtstate->mt_tableOidAttno =
+			ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
+		Assert(AttributeNumberIsValid(mtstate->mt_tableOidAttno));
+	}
+
+	/* 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 = 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;
+	}
+
+	/*
+	 * Per result relation initializations.
+	 * TODO: do this lazily.
+	 */
 	resultRelInfo = mtstate->resultRelInfo;
-	i = 0;
-	foreach(l, node->plans)
+	for (i = 0; i < nrels; i++)
 	{
-		subplan = (Plan *) lfirst(l);
+		List   *resultTargetList = NIL;
+		bool	need_projection = false;
 
 		/* Initialize the usesFdwDirectModify flag */
 		resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
@@ -2389,13 +2581,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			operation == CMD_UPDATE)
 			update_tuple_routing_needed = true;
 
-		/* 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]),
+			ExecInitExtraTupleSlot(mtstate->ps.state, plan_result_type,
 								   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
 
+		/* Now init the plan for this result rel */
+		estate->es_result_relation_info = resultRelInfo;
+
 		/* Also let FDWs init themselves for foreign-table result rels */
 		if (!resultRelInfo->ri_usesFdwDirectModify &&
 			resultRelInfo->ri_FdwRoutine != NULL &&
@@ -2410,12 +2602,151 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 															 eflags);
 		}
 
+		/*
+		 * Initialize any WITH CHECK OPTION constraints if needed.
+		 */
+		if (node->withCheckOptionLists)
+		{
+			List   *wcoList = (List *) list_nth(node->withCheckOptionLists, i);
+			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;
+		}
+		if (node->returningLists)
+		{
+			List	*rlist = (List *) list_nth(node->returningLists, i);
+			TupleTableSlot *slot;
+			ExprContext *econtext;
+
+			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);
+		}
+
+		/*
+		 * Prepare to generate tuples suitable for the target relation.
+		 */
+		if (operation == CMD_INSERT || operation == CMD_UPDATE)
+		{
+			if (operation == CMD_INSERT)
+			{
+				foreach(l, subplan->targetlist)
+				{
+					TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+					if (!tle->resjunk)
+						resultTargetList = lappend(resultTargetList, tle);
+					else
+						need_projection = true;
+				}
+			}
+			else
+			{
+				resultTargetList = (List *) list_nth(node->updateTargetLists,
+													 i);
+				need_projection = true;
+			}
+
+			/*
+			 * The clean list must produce a tuple suitable for the result
+			 * relation.
+			 */
+			ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+								resultTargetList);
+		}
+
+		if (need_projection)
+		{
+			TupleDesc	relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
+
+			/*
+			 * For UPDATE, we use the old tuple to fill up missing values in
+			 * the tuple produced by the plan to get the new tuple.
+			 */
+			if (operation == CMD_UPDATE)
+				resultRelInfo->ri_oldTupleSlot =
+					table_slot_create(resultRelInfo->ri_RelationDesc,
+									  &mtstate->ps.state->es_tupleTable);
+			resultRelInfo->ri_newTupleSlot =
+				table_slot_create(resultRelInfo->ri_RelationDesc,
+								  &mtstate->ps.state->es_tupleTable);
+
+			/* need an expression context to do the projection */
+			if (mtstate->ps.ps_ExprContext == NULL)
+				ExecAssignExprContext(estate, &mtstate->ps);
+			resultRelInfo->ri_projectNew =
+				ExecBuildProjectionInfo(resultTargetList,
+										mtstate->ps.ps_ExprContext,
+										resultRelInfo->ri_newTupleSlot,
+										&mtstate->ps,
+										relDesc);
+		}
+
+		/*
+		 * For UPDATE/DELETE, find the appropriate junk attr now.
+		 */
+		if (operation == CMD_UPDATE || operation == CMD_DELETE)
+		{
+			char	relkind;
+
+			relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+			if (relkind == RELKIND_RELATION ||
+				relkind == RELKIND_MATVIEW ||
+				relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				resultRelInfo->ri_junkAttno =
+					ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
+				if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+					elog(ERROR, "could not find junk ctid column");
+			}
+			else if (relkind == RELKIND_FOREIGN_TABLE)
+			{
+				TriggerDesc *trigdesc = resultRelInfo->ri_TrigDesc;
+				bool	need_wholerow = (operation == CMD_UPDATE ||
+									 (trigdesc &&
+									  (trigdesc->trig_delete_after_row ||
+									   trigdesc->trig_delete_before_row)));
+
+				/*
+				 * There should be a wholerow attribute if this is an UPDATE
+				 * of if there are any row level DELETE triggers.
+				 */
+				resultRelInfo->ri_junkAttno =
+					ExecFindJunkAttributeInTlist(subplan->targetlist,
+												 "wholerow");
+				if (need_wholerow &&
+					!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+					elog(ERROR, "could not find junk wholerow column");
+			}
+			else
+			{
+				resultRelInfo->ri_junkAttno =
+					ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow");
+				if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno))
+					elog(ERROR, "could not find junk wholerow column");
+			}
+		}
+
 		resultRelInfo++;
-		i++;
 	}
 
-	estate->es_result_relation_info = saved_resultRelInfo;
-
 	/* Get the target relation */
 	rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc;
 
@@ -2457,82 +2788,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_root_tuple_slot = table_slot_create(rel, NULL);
 	}
 
-	/*
-	 * Initialize any WITH CHECK OPTION constraints if needed.
-	 */
-	resultRelInfo = mtstate->resultRelInfo;
-	i = 0;
-	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++;
-		i++;
-	}
-
-	/*
-	 * 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)
@@ -2548,8 +2803,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		TupleDesc	relationDesc;
 		TupleDesc	tupDesc;
 
-		/* insert may only have one plan, inheritance is not expanded */
-		Assert(nplans == 1);
+		/* insert may only have one relation, inheritance is not expanded */
+		Assert(nrels == 1);
 
 		/* already exists if created by RETURNING processing above */
 		if (mtstate->ps.ps_ExprContext == NULL)
@@ -2605,133 +2860,29 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		PlanRowMark *rc = lfirst_node(PlanRowMark, l);
 		ExecRowMark *erm;
+		ExecAuxRowMark *aerm;
 
 		/* ignore "parent" rowmarks; they are irrelevant at runtime */
 		if (rc->isParent)
 			continue;
 
-		/* find ExecRowMark (same for all subplans) */
+		/* Find ExecRowMark and build ExecAuxRowMark */
 		erm = ExecFindRowMark(estate, rc->rti, false);
-
-		/* build ExecAuxRowMark for each subplan */
-		for (i = 0; i < nplans; i++)
-		{
-			ExecAuxRowMark *aerm;
-
-			subplan = mtstate->mt_plans[i]->plan;
-			aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
-			mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm);
-		}
+		aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
+		mtstate->mt_arowmarks[0] = lappend(mtstate->mt_arowmarks[0], aerm);
 	}
 
 	/* select first subplan */
 	mtstate->mt_whichplan = 0;
-	subplan = (Plan *) linitial(node->plans);
 	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).
+	 * Initialize a hash table to look up UPDATE/DELETE result relations by
+	 * OID if there is more than one.
 	 */
-	{
-		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;
-				if (operation == CMD_INSERT || operation == CMD_UPDATE)
-					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
-										subplan->targetlist);
-
-				junkresslot =
-					ExecInitExtraTupleSlot(estate, NULL,
-										   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-				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);
-		}
-	}
+	if (mtstate->operation != CMD_INSERT && mtstate->mt_nrels > 1)
+		ExecHashSubPlanResultRelsByOid(mtstate);
 
 	/*
 	 * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
@@ -2765,7 +2916,7 @@ ExecEndModifyTable(ModifyTableState *node)
 	/*
 	 * Allow any FDWs to shut down
 	 */
-	for (i = 0; i < node->mt_nplans; i++)
+	for (i = 0; i < node->mt_nrels; i++)
 	{
 		ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d8cf87e..c988c96 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -212,6 +212,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(plans);
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
+	COPY_NODE_FIELD(updateTargetLists);
 	COPY_NODE_FIELD(fdwPrivLists);
 	COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	COPY_NODE_FIELD(rowMarks);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f1775..d168ecc 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -413,6 +413,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(plans);
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
+	WRITE_NODE_FIELD(updateTargetLists);
 	WRITE_NODE_FIELD(fdwPrivLists);
 	WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	WRITE_NODE_FIELD(rowMarks);
@@ -2120,6 +2121,7 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
 	WRITE_NODE_FIELD(subroots);
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
+	WRITE_NODE_FIELD(updateTargetLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(onconflict);
 	WRITE_INT_FIELD(epqParam);
@@ -2235,6 +2237,8 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_NODE_FIELD(full_join_clauses);
 	WRITE_NODE_FIELD(join_info_list);
 	WRITE_NODE_FIELD(append_rel_list);
+	WRITE_NODE_FIELD(result_rel_list);
+	WRITE_NODE_FIELD(specialJunkVars);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(placeholder_list);
 	WRITE_NODE_FIELD(fkey_list);
@@ -2249,7 +2253,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
 	WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
 	WRITE_UINT_FIELD(qual_security_level);
-	WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind);
 	WRITE_BOOL_FIELD(hasJoinRTEs);
 	WRITE_BOOL_FIELD(hasLateralRTEs);
 	WRITE_BOOL_FIELD(hasHavingQual);
@@ -2546,6 +2549,32 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 }
 
 static void
+_outResultRelPlanInfo(StringInfo str, const ResultRelPlanInfo *node)
+{
+	WRITE_NODE_TYPE("RESULTRELPLANINFO");
+
+	WRITE_UINT_FIELD(resultRelation);
+	WRITE_NODE_FIELD(subplanTargetList);
+	WRITE_NODE_FIELD(updateTargetList);
+	WRITE_NODE_FIELD(withCheckOptions);
+	WRITE_NODE_FIELD(returningList);
+}
+
+static void
+_outSpecialJunkVarInfo(StringInfo str, const SpecialJunkVarInfo *node)
+{
+	WRITE_NODE_TYPE("SPECIALJUNKVARINFO");
+
+	WRITE_STRING_FIELD(attrname);
+	WRITE_OID_FIELD(vartype);
+	WRITE_INT_FIELD(vartypmod);
+	WRITE_OID_FIELD(varcollid);
+	WRITE_INT_FIELD(varattno);
+	WRITE_INT_FIELD(special_attno);
+	/* child_relids not printed. */
+}
+
+static void
 _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
 {
 	WRITE_NODE_TYPE("PLACEHOLDERINFO");
@@ -4148,6 +4177,12 @@ outNode(StringInfo str, const void *obj)
 			case T_AppendRelInfo:
 				_outAppendRelInfo(str, obj);
 				break;
+			case T_ResultRelPlanInfo:
+				_outResultRelPlanInfo(str, obj);
+				break;
+			case T_SpecialJunkVarInfo:
+				_outSpecialJunkVarInfo(str, obj);
+				break;
 			case T_PlaceHolderInfo:
 				_outPlaceHolderInfo(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab..92bb7ad 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1644,6 +1644,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(plans);
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
+	READ_NODE_FIELD(updateTargetLists);
 	READ_NODE_FIELD(fdwPrivLists);
 	READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
 	READ_NODE_FIELD(rowMarks);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index d984da2..7cf1ee5 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -946,6 +946,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	double	   *parent_attrsizes;
 	int			nattrs;
 	ListCell   *l;
+	ResultRelPlanInfo *resultInfo = root->result_rel_array ?
+							root->result_rel_array[rti] : NULL;
 
 	/* Guard against stack overflow due to overly deep inheritance tree. */
 	check_stack_depth();
@@ -1053,10 +1055,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			adjust_appendrel_attrs(root,
 								   (Node *) rel->joininfo,
 								   1, &appinfo);
-		childrel->reltarget->exprs = (List *)
-			adjust_appendrel_attrs(root,
-								   (Node *) rel->reltarget->exprs,
-								   1, &appinfo);
+		/* Process a child result relation. */
+		if (resultInfo)
+			/*
+			 * Using adjust_target_appendrel_attrs() to prevent child's
+			 * wholerow Vars from being converted back to the parent's
+			 * reltype.
+			 */
+			childrel->reltarget->exprs = (List *)
+				adjust_target_appendrel_attrs(root,
+											  (Node *) rel->reltarget->exprs,
+											  appinfo);
+		else
+			childrel->reltarget->exprs = (List *)
+				adjust_appendrel_attrs(root,
+									   (Node *) rel->reltarget->exprs,
+									   1, &appinfo);
 
 		/*
 		 * We have to make child entries in the EquivalenceClass data
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 744eed1..e823172 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -297,6 +297,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
 									 bool partColsUpdated,
 									 List *resultRelations, List *subplans, List *subroots,
 									 List *withCheckOptionLists, List *returningLists,
+									 List *updateTargetLists,
 									 List *rowMarks, OnConflictExpr *onconflict, int epqParam);
 static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
 											 GatherMergePath *best_path);
@@ -2264,7 +2265,6 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
 	 * create_modifytable_plan).  Fortunately we can't be because there would
 	 * never be grouping in an UPDATE/DELETE; but let's Assert that.
 	 */
-	Assert(root->inhTargetKind == INHKIND_NONE);
 	Assert(root->grouping_map == NULL);
 	root->grouping_map = grouping_map;
 
@@ -2426,12 +2426,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
 	 * with InitPlan output params.  (We can't just do that locally in the
 	 * MinMaxAgg node, because path nodes above here may have Agg references
 	 * as well.)  Save the mmaggregates list to tell setrefs.c to do that.
-	 *
-	 * This doesn't work if we're in an inheritance subtree (see notes in
-	 * create_modifytable_plan).  Fortunately we can't be because there would
-	 * never be aggregates in an UPDATE/DELETE; but let's Assert that.
 	 */
-	Assert(root->inhTargetKind == INHKIND_NONE);
 	Assert(root->minmax_aggs == NIL);
 	root->minmax_aggs = best_path->mmaggregates;
 
@@ -2661,6 +2656,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 		Plan	   *subplan;
 
 		/*
+		 * FIXME: there are no longer per-child paths.
+		 *
 		 * In an inherited UPDATE/DELETE, reference the per-child modified
 		 * subroot while creating Plans from Paths for the child rel.  This is
 		 * a kluge, but otherwise it's too hard to ensure that Plan creation
@@ -2673,8 +2670,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 		 */
 		subplan = create_plan_recurse(subroot, subpath, CP_EXACT_TLIST);
 
-		/* Transfer resname/resjunk labeling, too, to keep executor happy */
-		apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist);
+		/* Transfer resname/resjunk labeling, too, to keep executor happy. */
+		apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
 
 		subplans = lappend(subplans, subplan);
 	}
@@ -2690,6 +2687,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->subroots,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
+							best_path->updateTargetLists,
 							best_path->rowMarks,
 							best_path->onconflict,
 							best_path->epqParam);
@@ -6798,21 +6796,22 @@ make_modifytable(PlannerInfo *root,
 				 bool partColsUpdated,
 				 List *resultRelations, List *subplans, List *subroots,
 				 List *withCheckOptionLists, List *returningLists,
+				 List *updateTargetLists,
 				 List *rowMarks, OnConflictExpr *onconflict, int epqParam)
 {
 	ModifyTable *node = makeNode(ModifyTable);
 	List	   *fdw_private_list;
 	Bitmapset  *direct_modify_plans;
 	ListCell   *lc;
-	ListCell   *lc2;
 	int			i;
 
-	Assert(list_length(resultRelations) == list_length(subplans));
-	Assert(list_length(resultRelations) == list_length(subroots));
 	Assert(withCheckOptionLists == NIL ||
 		   list_length(resultRelations) == list_length(withCheckOptionLists));
 	Assert(returningLists == NIL ||
 		   list_length(resultRelations) == list_length(returningLists));
+	Assert(operation != CMD_UPDATE ||
+		   (updateTargetLists != NIL &&
+			list_length(resultRelations) == list_length(updateTargetLists)));
 
 	node->plan.lefttree = NULL;
 	node->plan.righttree = NULL;
@@ -6857,6 +6856,7 @@ make_modifytable(PlannerInfo *root,
 	}
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
+	node->updateTargetLists = updateTargetLists;
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
@@ -6867,10 +6867,9 @@ make_modifytable(PlannerInfo *root,
 	fdw_private_list = NIL;
 	direct_modify_plans = NULL;
 	i = 0;
-	forboth(lc, resultRelations, lc2, subroots)
+	foreach(lc, resultRelations)
 	{
 		Index		rti = lfirst_int(lc);
-		PlannerInfo *subroot = lfirst_node(PlannerInfo, lc2);
 		FdwRoutine *fdwroutine;
 		List	   *fdw_private;
 		bool		direct_modify;
@@ -6882,16 +6881,16 @@ make_modifytable(PlannerInfo *root,
 		 * so it's not a baserel; and there are also corner cases for
 		 * updatable views where the target rel isn't a baserel.)
 		 */
-		if (rti < subroot->simple_rel_array_size &&
-			subroot->simple_rel_array[rti] != NULL)
+		if (rti < root->simple_rel_array_size &&
+			root->simple_rel_array[rti] != NULL)
 		{
-			RelOptInfo *resultRel = subroot->simple_rel_array[rti];
+			RelOptInfo *resultRel = root->simple_rel_array[rti];
 
 			fdwroutine = resultRel->fdwroutine;
 		}
 		else
 		{
-			RangeTblEntry *rte = planner_rt_fetch(rti, subroot);
+			RangeTblEntry *rte = planner_rt_fetch(rti, root);
 
 			Assert(rte->rtekind == RTE_RELATION);
 			if (rte->relkind == RELKIND_FOREIGN_TABLE)
@@ -6914,16 +6913,17 @@ make_modifytable(PlannerInfo *root,
 			fdwroutine->IterateDirectModify != NULL &&
 			fdwroutine->EndDirectModify != NULL &&
 			withCheckOptionLists == NIL &&
-			!has_row_triggers(subroot, rti, operation) &&
-			!has_stored_generated_columns(subroot, rti))
-			direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
+			!has_row_triggers(root, rti, operation) &&
+			!has_stored_generated_columns(root, rti) &&
+			i == 0) /* XXX - child tables not allowed too! */
+			direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
 		if (direct_modify)
 			direct_modify_plans = bms_add_member(direct_modify_plans, i);
 
 		if (!direct_modify &&
 			fdwroutine != NULL &&
 			fdwroutine->PlanForeignModify != NULL)
-			fdw_private = fdwroutine->PlanForeignModify(subroot, node, rti, i);
+			fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
 		else
 			fdw_private = NIL;
 		fdw_private_list = lappend(fdw_private_list, fdw_private);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3578506..3410842 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -129,9 +129,7 @@ typedef struct
 /* Local functions */
 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
-static void inheritance_planner(PlannerInfo *root);
-static void grouping_planner(PlannerInfo *root, bool inheritance_update,
-							 double tuple_fraction);
+static void grouping_planner(PlannerInfo *root, double tuple_fraction);
 static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root);
 static List *remap_to_groupclause_idx(List *groupClause, List *gsets,
 									  int *tleref_to_colnum_map);
@@ -628,7 +626,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->grouping_map = NULL;
 	root->minmax_aggs = NIL;
 	root->qual_security_level = 0;
-	root->inhTargetKind = INHKIND_NONE;
 	root->hasRecursion = hasRecursion;
 	if (hasRecursion)
 		root->wt_param_id = assign_special_exec_param(root);
@@ -636,6 +633,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		root->wt_param_id = -1;
 	root->non_recursive_path = NULL;
 	root->partColsUpdated = false;
+	root->specialJunkVars = NIL;
+	root->result_rel_list = NIL;
 
 	/*
 	 * If there is a WITH list, process each WITH query and either convert it
@@ -1004,15 +1003,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	if (hasResultRTEs)
 		remove_useless_result_rtes(root);
 
-	/*
-	 * Do the main planning.  If we have an inherited target relation, that
-	 * needs special processing, else go straight to grouping_planner.
-	 */
-	if (parse->resultRelation &&
-		rt_fetch(parse->resultRelation, parse->rtable)->inh)
-		inheritance_planner(root);
-	else
-		grouping_planner(root, false, tuple_fraction);
+	/* Do the main planning. */
+	grouping_planner(root, tuple_fraction);
 
 	/*
 	 * Capture the set of outer-level param IDs we have access to, for use in
@@ -1186,621 +1178,6 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
 	return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV);
 }
 
-/*
- * inheritance_planner
- *	  Generate Paths in the case where the result relation is an
- *	  inheritance set.
- *
- * We have to handle this case differently from cases where a source relation
- * is an inheritance set. Source inheritance is expanded at the bottom of the
- * plan tree (see allpaths.c), but target inheritance has to be expanded at
- * the top.  The reason is that for UPDATE, each target relation needs a
- * different targetlist matching its own column set.  Fortunately,
- * the UPDATE/DELETE target can never be the nullable side of an outer join,
- * so it's OK to generate the plan this way.
- *
- * Returns nothing; the useful output is in the Paths we attach to
- * the (UPPERREL_FINAL, NULL) upperrel stored in *root.
- *
- * Note that we have not done set_cheapest() on the final rel; it's convenient
- * to leave this to the caller.
- */
-static void
-inheritance_planner(PlannerInfo *root)
-{
-	Query	   *parse = root->parse;
-	int			top_parentRTindex = parse->resultRelation;
-	List	   *select_rtable;
-	List	   *select_appinfos;
-	List	   *child_appinfos;
-	List	   *old_child_rtis;
-	List	   *new_child_rtis;
-	Bitmapset  *subqueryRTindexes;
-	Index		next_subquery_rti;
-	int			nominalRelation = -1;
-	Index		rootRelation = 0;
-	List	   *final_rtable = NIL;
-	List	   *final_rowmarks = NIL;
-	List	   *final_appendrels = NIL;
-	int			save_rel_array_size = 0;
-	RelOptInfo **save_rel_array = NULL;
-	AppendRelInfo **save_append_rel_array = NULL;
-	List	   *subpaths = NIL;
-	List	   *subroots = NIL;
-	List	   *resultRelations = NIL;
-	List	   *withCheckOptionLists = NIL;
-	List	   *returningLists = NIL;
-	List	   *rowMarks;
-	RelOptInfo *final_rel;
-	ListCell   *lc;
-	ListCell   *lc2;
-	Index		rti;
-	RangeTblEntry *parent_rte;
-	Bitmapset  *parent_relids;
-	Query	  **parent_parses;
-
-	/* Should only get here for UPDATE or DELETE */
-	Assert(parse->commandType == CMD_UPDATE ||
-		   parse->commandType == CMD_DELETE);
-
-	/*
-	 * We generate a modified instance of the original Query for each target
-	 * relation, plan that, and put all the plans into a list that will be
-	 * controlled by a single ModifyTable node.  All the instances share the
-	 * same rangetable, but each instance must have its own set of subquery
-	 * RTEs within the finished rangetable because (1) they are likely to get
-	 * scribbled on during planning, and (2) it's not inconceivable that
-	 * subqueries could get planned differently in different cases.  We need
-	 * not create duplicate copies of other RTE kinds, in particular not the
-	 * target relations, because they don't have either of those issues.  Not
-	 * having to duplicate the target relations is important because doing so
-	 * (1) would result in a rangetable of length O(N^2) for N targets, with
-	 * at least O(N^3) work expended here; and (2) would greatly complicate
-	 * management of the rowMarks list.
-	 *
-	 * To begin with, generate a bitmapset of the relids of the subquery RTEs.
-	 */
-	subqueryRTindexes = NULL;
-	rti = 1;
-	foreach(lc, parse->rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		if (rte->rtekind == RTE_SUBQUERY)
-			subqueryRTindexes = bms_add_member(subqueryRTindexes, rti);
-		rti++;
-	}
-
-	/*
-	 * If the parent RTE is a partitioned table, we should use that as the
-	 * nominal target relation, because the RTEs added for partitioned tables
-	 * (including the root parent) as child members of the inheritance set do
-	 * not appear anywhere else in the plan, so the confusion explained below
-	 * for non-partitioning inheritance cases is not possible.
-	 */
-	parent_rte = rt_fetch(top_parentRTindex, parse->rtable);
-	Assert(parent_rte->inh);
-	if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		nominalRelation = top_parentRTindex;
-		rootRelation = top_parentRTindex;
-	}
-
-	/*
-	 * Before generating the real per-child-relation plans, do a cycle of
-	 * planning as though the query were a SELECT.  The objective here is to
-	 * find out which child relations need to be processed, using the same
-	 * expansion and pruning logic as for a SELECT.  We'll then pull out the
-	 * RangeTblEntry-s generated for the child rels, and make use of the
-	 * AppendRelInfo entries for them to guide the real planning.  (This is
-	 * rather inefficient; we could perhaps stop short of making a full Path
-	 * tree.  But this whole function is inefficient and slated for
-	 * destruction, so let's not contort query_planner for that.)
-	 */
-	{
-		PlannerInfo *subroot;
-
-		/*
-		 * Flat-copy the PlannerInfo to prevent modification of the original.
-		 */
-		subroot = makeNode(PlannerInfo);
-		memcpy(subroot, root, sizeof(PlannerInfo));
-
-		/*
-		 * Make a deep copy of the parsetree for this planning cycle to mess
-		 * around with, and change it to look like a SELECT.  (Hack alert: the
-		 * target RTE still has updatedCols set if this is an UPDATE, so that
-		 * expand_partitioned_rtentry will correctly update
-		 * subroot->partColsUpdated.)
-		 */
-		subroot->parse = copyObject(root->parse);
-
-		subroot->parse->commandType = CMD_SELECT;
-		subroot->parse->resultRelation = 0;
-
-		/*
-		 * Ensure the subroot has its own copy of the original
-		 * append_rel_list, since it'll be scribbled on.  (Note that at this
-		 * point, the list only contains AppendRelInfos for flattened UNION
-		 * ALL subqueries.)
-		 */
-		subroot->append_rel_list = copyObject(root->append_rel_list);
-
-		/*
-		 * Better make a private copy of the rowMarks, too.
-		 */
-		subroot->rowMarks = copyObject(root->rowMarks);
-
-		/* There shouldn't be any OJ info to translate, as yet */
-		Assert(subroot->join_info_list == NIL);
-		/* and we haven't created PlaceHolderInfos, either */
-		Assert(subroot->placeholder_list == NIL);
-
-		/* Generate Path(s) for accessing this result relation */
-		grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
-		/* Extract the info we need. */
-		select_rtable = subroot->parse->rtable;
-		select_appinfos = subroot->append_rel_list;
-
-		/*
-		 * We need to propagate partColsUpdated back, too.  (The later
-		 * planning cycles will not set this because they won't run
-		 * expand_partitioned_rtentry for the UPDATE target.)
-		 */
-		root->partColsUpdated = subroot->partColsUpdated;
-	}
-
-	/*----------
-	 * Since only one rangetable can exist in the final plan, we need to make
-	 * sure that it contains all the RTEs needed for any child plan.  This is
-	 * complicated by the need to use separate subquery RTEs for each child.
-	 * We arrange the final rtable as follows:
-	 * 1. All original rtable entries (with their original RT indexes).
-	 * 2. All the relation RTEs generated for children of the target table.
-	 * 3. Subquery RTEs for children after the first.  We need N * (K - 1)
-	 *    RT slots for this, if there are N subqueries and K child tables.
-	 * 4. Additional RTEs generated during the child planning runs, such as
-	 *    children of inheritable RTEs other than the target table.
-	 * We assume that each child planning run will create an identical set
-	 * of type-4 RTEs.
-	 *
-	 * So the next thing to do is append the type-2 RTEs (the target table's
-	 * children) to the original rtable.  We look through select_appinfos
-	 * to find them.
-	 *
-	 * To identify which AppendRelInfos are relevant as we thumb through
-	 * select_appinfos, we need to look for both direct and indirect children
-	 * of top_parentRTindex, so we use a bitmap of known parent relids.
-	 * expand_inherited_rtentry() always processes a parent before any of that
-	 * parent's children, so we should see an intermediate parent before its
-	 * children.
-	 *----------
-	 */
-	child_appinfos = NIL;
-	old_child_rtis = NIL;
-	new_child_rtis = NIL;
-	parent_relids = bms_make_singleton(top_parentRTindex);
-	foreach(lc, select_appinfos)
-	{
-		AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
-		RangeTblEntry *child_rte;
-
-		/* append_rel_list contains all append rels; ignore others */
-		if (!bms_is_member(appinfo->parent_relid, parent_relids))
-			continue;
-
-		/* remember relevant AppendRelInfos for use below */
-		child_appinfos = lappend(child_appinfos, appinfo);
-
-		/* extract RTE for this child rel */
-		child_rte = rt_fetch(appinfo->child_relid, select_rtable);
-
-		/* and append it to the original rtable */
-		parse->rtable = lappend(parse->rtable, child_rte);
-
-		/* remember child's index in the SELECT rtable */
-		old_child_rtis = lappend_int(old_child_rtis, appinfo->child_relid);
-
-		/* and its new index in the final rtable */
-		new_child_rtis = lappend_int(new_child_rtis, list_length(parse->rtable));
-
-		/* if child is itself partitioned, update parent_relids */
-		if (child_rte->inh)
-		{
-			Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
-			parent_relids = bms_add_member(parent_relids, appinfo->child_relid);
-		}
-	}
-
-	/*
-	 * It's possible that the RTIs we just assigned for the child rels in the
-	 * final rtable are different from what they were in the SELECT query.
-	 * Adjust the AppendRelInfos so that they will correctly map RT indexes to
-	 * the final indexes.  We can do this left-to-right since no child rel's
-	 * final RT index could be greater than what it had in the SELECT query.
-	 */
-	forboth(lc, old_child_rtis, lc2, new_child_rtis)
-	{
-		int			old_child_rti = lfirst_int(lc);
-		int			new_child_rti = lfirst_int(lc2);
-
-		if (old_child_rti == new_child_rti)
-			continue;			/* nothing to do */
-
-		Assert(old_child_rti > new_child_rti);
-
-		ChangeVarNodes((Node *) child_appinfos,
-					   old_child_rti, new_child_rti, 0);
-	}
-
-	/*
-	 * Now set up rangetable entries for subqueries for additional children
-	 * (the first child will just use the original ones).  These all have to
-	 * look more or less real, or EXPLAIN will get unhappy; so we just make
-	 * them all clones of the original subqueries.
-	 */
-	next_subquery_rti = list_length(parse->rtable) + 1;
-	if (subqueryRTindexes != NULL)
-	{
-		int			n_children = list_length(child_appinfos);
-
-		while (n_children-- > 1)
-		{
-			int			oldrti = -1;
-
-			while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
-			{
-				RangeTblEntry *subqrte;
-
-				subqrte = rt_fetch(oldrti, parse->rtable);
-				parse->rtable = lappend(parse->rtable, copyObject(subqrte));
-			}
-		}
-	}
-
-	/*
-	 * The query for each child is obtained by translating the query for its
-	 * immediate parent, since the AppendRelInfo data we have shows deltas
-	 * between parents and children.  We use the parent_parses array to
-	 * remember the appropriate query trees.  This is indexed by parent relid.
-	 * Since the maximum number of parents is limited by the number of RTEs in
-	 * the SELECT query, we use that number to allocate the array.  An extra
-	 * entry is needed since relids start from 1.
-	 */
-	parent_parses = (Query **) palloc0((list_length(select_rtable) + 1) *
-									   sizeof(Query *));
-	parent_parses[top_parentRTindex] = parse;
-
-	/*
-	 * And now we can get on with generating a plan for each child table.
-	 */
-	foreach(lc, child_appinfos)
-	{
-		AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
-		Index		this_subquery_rti = next_subquery_rti;
-		Query	   *parent_parse;
-		PlannerInfo *subroot;
-		RangeTblEntry *child_rte;
-		RelOptInfo *sub_final_rel;
-		Path	   *subpath;
-
-		/*
-		 * expand_inherited_rtentry() always processes a parent before any of
-		 * that parent's children, so the parent query for this relation
-		 * should already be available.
-		 */
-		parent_parse = parent_parses[appinfo->parent_relid];
-		Assert(parent_parse != NULL);
-
-		/*
-		 * We need a working copy of the PlannerInfo so that we can control
-		 * propagation of information back to the main copy.
-		 */
-		subroot = makeNode(PlannerInfo);
-		memcpy(subroot, root, sizeof(PlannerInfo));
-
-		/*
-		 * Generate modified query with this rel as target.  We first apply
-		 * adjust_appendrel_attrs, which copies the Query and changes
-		 * references to the parent RTE to refer to the current child RTE,
-		 * then fool around with subquery RTEs.
-		 */
-		subroot->parse = (Query *)
-			adjust_appendrel_attrs(subroot,
-								   (Node *) parent_parse,
-								   1, &appinfo);
-
-		/*
-		 * If there are securityQuals attached to the parent, move them to the
-		 * child rel (they've already been transformed properly for that).
-		 */
-		parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable);
-		child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
-		child_rte->securityQuals = parent_rte->securityQuals;
-		parent_rte->securityQuals = NIL;
-
-		/*
-		 * HACK: setting this to a value other than INHKIND_NONE signals to
-		 * relation_excluded_by_constraints() to treat the result relation as
-		 * being an appendrel member.
-		 */
-		subroot->inhTargetKind =
-			(rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED;
-
-		/*
-		 * If this child is further partitioned, remember it as a parent.
-		 * Since a partitioned table does not have any data, we don't need to
-		 * create a plan for it, and we can stop processing it here.  We do,
-		 * however, need to remember its modified PlannerInfo for use when
-		 * processing its children, since we'll update their varnos based on
-		 * the delta from immediate parent to child, not from top to child.
-		 *
-		 * Note: a very non-obvious point is that we have not yet added
-		 * duplicate subquery RTEs to the subroot's rtable.  We mustn't,
-		 * because then its children would have two sets of duplicates,
-		 * confusing matters.
-		 */
-		if (child_rte->inh)
-		{
-			Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
-			parent_parses[appinfo->child_relid] = subroot->parse;
-			continue;
-		}
-
-		/*
-		 * Set the nominal target relation of the ModifyTable node if not
-		 * already done.  If the target is a partitioned table, we already set
-		 * nominalRelation to refer to the partition root, above.  For
-		 * non-partitioned inheritance cases, we'll use the first child
-		 * relation (even if it's excluded) as the nominal target relation.
-		 * Because of the way expand_inherited_rtentry works, that should be
-		 * the RTE representing the parent table in its role as a simple
-		 * member of the inheritance set.
-		 *
-		 * It would be logically cleaner to *always* use the inheritance
-		 * parent RTE as the nominal relation; but that RTE is not otherwise
-		 * referenced in the plan in the non-partitioned inheritance case.
-		 * Instead the duplicate child RTE created by expand_inherited_rtentry
-		 * is used elsewhere in the plan, so using the original parent RTE
-		 * would give rise to confusing use of multiple aliases in EXPLAIN
-		 * output for what the user will think is the "same" table.  OTOH,
-		 * it's not a problem in the partitioned inheritance case, because
-		 * there is no duplicate RTE for the parent.
-		 */
-		if (nominalRelation < 0)
-			nominalRelation = appinfo->child_relid;
-
-		/*
-		 * As above, each child plan run needs its own append_rel_list and
-		 * rowmarks, which should start out as pristine copies of the
-		 * originals.  There can't be any references to UPDATE/DELETE target
-		 * rels in them; but there could be subquery references, which we'll
-		 * fix up in a moment.
-		 */
-		subroot->append_rel_list = copyObject(root->append_rel_list);
-		subroot->rowMarks = copyObject(root->rowMarks);
-
-		/*
-		 * If this isn't the first child Query, adjust Vars and jointree
-		 * entries to reference the appropriate set of subquery RTEs.
-		 */
-		if (final_rtable != NIL && subqueryRTindexes != NULL)
-		{
-			int			oldrti = -1;
-
-			while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
-			{
-				Index		newrti = next_subquery_rti++;
-
-				ChangeVarNodes((Node *) subroot->parse, oldrti, newrti, 0);
-				ChangeVarNodes((Node *) subroot->append_rel_list,
-							   oldrti, newrti, 0);
-				ChangeVarNodes((Node *) subroot->rowMarks, oldrti, newrti, 0);
-			}
-		}
-
-		/* There shouldn't be any OJ info to translate, as yet */
-		Assert(subroot->join_info_list == NIL);
-		/* and we haven't created PlaceHolderInfos, either */
-		Assert(subroot->placeholder_list == NIL);
-
-		/* Generate Path(s) for accessing this result relation */
-		grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
-		/*
-		 * Select cheapest path in case there's more than one.  We always run
-		 * modification queries to conclusion, so we care only for the
-		 * cheapest-total path.
-		 */
-		sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
-		set_cheapest(sub_final_rel);
-		subpath = sub_final_rel->cheapest_total_path;
-
-		/*
-		 * If this child rel was excluded by constraint exclusion, exclude it
-		 * from the result plan.
-		 */
-		if (IS_DUMMY_REL(sub_final_rel))
-			continue;
-
-		/*
-		 * If this is the first non-excluded child, its post-planning rtable
-		 * becomes the initial contents of final_rtable; otherwise, copy its
-		 * modified subquery RTEs into final_rtable, to ensure we have sane
-		 * copies of those.  Also save the first non-excluded child's version
-		 * of the rowmarks list; we assume all children will end up with
-		 * equivalent versions of that.  Likewise for append_rel_list.
-		 */
-		if (final_rtable == NIL)
-		{
-			final_rtable = subroot->parse->rtable;
-			final_rowmarks = subroot->rowMarks;
-			final_appendrels = subroot->append_rel_list;
-		}
-		else
-		{
-			Assert(list_length(final_rtable) ==
-				   list_length(subroot->parse->rtable));
-			if (subqueryRTindexes != NULL)
-			{
-				int			oldrti = -1;
-
-				while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
-				{
-					Index		newrti = this_subquery_rti++;
-					RangeTblEntry *subqrte;
-					ListCell   *newrticell;
-
-					subqrte = rt_fetch(newrti, subroot->parse->rtable);
-					newrticell = list_nth_cell(final_rtable, newrti - 1);
-					lfirst(newrticell) = subqrte;
-				}
-			}
-		}
-
-		/*
-		 * We need to collect all the RelOptInfos from all child plans into
-		 * the main PlannerInfo, since setrefs.c will need them.  We use the
-		 * last child's simple_rel_array, so we have to propagate forward the
-		 * RelOptInfos that were already built in previous children.
-		 */
-		Assert(subroot->simple_rel_array_size >= save_rel_array_size);
-		for (rti = 1; rti < save_rel_array_size; rti++)
-		{
-			RelOptInfo *brel = save_rel_array[rti];
-
-			if (brel)
-				subroot->simple_rel_array[rti] = brel;
-		}
-		save_rel_array_size = subroot->simple_rel_array_size;
-		save_rel_array = subroot->simple_rel_array;
-		save_append_rel_array = subroot->append_rel_array;
-
-		/*
-		 * Make sure any initplans from this rel get into the outer list. Note
-		 * we're effectively assuming all children generate the same
-		 * init_plans.
-		 */
-		root->init_plans = subroot->init_plans;
-
-		/* Build list of sub-paths */
-		subpaths = lappend(subpaths, subpath);
-
-		/* Build list of modified subroots, too */
-		subroots = lappend(subroots, subroot);
-
-		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
-
-		/* Build lists of per-relation WCO and RETURNING targetlists */
-		if (parse->withCheckOptions)
-			withCheckOptionLists = lappend(withCheckOptionLists,
-										   subroot->parse->withCheckOptions);
-		if (parse->returningList)
-			returningLists = lappend(returningLists,
-									 subroot->parse->returningList);
-
-		Assert(!parse->onConflict);
-	}
-
-	/* Result path must go into outer query's FINAL upperrel */
-	final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
-
-	/*
-	 * We don't currently worry about setting final_rel's consider_parallel
-	 * flag in this case, nor about allowing FDWs or create_upper_paths_hook
-	 * to get control here.
-	 */
-
-	if (subpaths == NIL)
-	{
-		/*
-		 * We managed to exclude every child rel, so generate a dummy path
-		 * representing the empty set.  Although it's clear that no data will
-		 * be updated or deleted, we will still need to have a ModifyTable
-		 * node so that any statement triggers are executed.  (This could be
-		 * cleaner if we fixed nodeModifyTable.c to support zero child nodes,
-		 * but that probably wouldn't be a net win.)
-		 */
-		Path	   *dummy_path;
-
-		/* tlist processing never got done, either */
-		root->processed_tlist = preprocess_targetlist(root);
-		final_rel->reltarget = create_pathtarget(root, root->processed_tlist);
-
-		/* Make a dummy path, cf set_dummy_rel_pathlist() */
-		dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
-												 NIL, NULL, 0, false,
-												 NIL, -1);
-
-		/* These lists must be nonempty to make a valid ModifyTable node */
-		subpaths = list_make1(dummy_path);
-		subroots = list_make1(root);
-		resultRelations = list_make1_int(parse->resultRelation);
-		if (parse->withCheckOptions)
-			withCheckOptionLists = list_make1(parse->withCheckOptions);
-		if (parse->returningList)
-			returningLists = list_make1(parse->returningList);
-		/* Disable tuple routing, too, just to be safe */
-		root->partColsUpdated = false;
-	}
-	else
-	{
-		/*
-		 * Put back the final adjusted rtable into the master copy of the
-		 * Query.  (We mustn't do this if we found no non-excluded children,
-		 * since we never saved an adjusted rtable at all.)
-		 */
-		parse->rtable = final_rtable;
-		root->simple_rel_array_size = save_rel_array_size;
-		root->simple_rel_array = save_rel_array;
-		root->append_rel_array = save_append_rel_array;
-
-		/* Must reconstruct master's simple_rte_array, too */
-		root->simple_rte_array = (RangeTblEntry **)
-			palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *));
-		rti = 1;
-		foreach(lc, final_rtable)
-		{
-			RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-			root->simple_rte_array[rti++] = rte;
-		}
-
-		/* Put back adjusted rowmarks and appendrels, too */
-		root->rowMarks = final_rowmarks;
-		root->append_rel_list = final_appendrels;
-	}
-
-	/*
-	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
-	 * have dealt with fetching non-locked marked rows, else we need to have
-	 * ModifyTable do that.
-	 */
-	if (parse->rowMarks)
-		rowMarks = NIL;
-	else
-		rowMarks = root->rowMarks;
-
-	/* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
-	add_path(final_rel, (Path *)
-			 create_modifytable_path(root, final_rel,
-									 parse->commandType,
-									 parse->canSetTag,
-									 nominalRelation,
-									 rootRelation,
-									 root->partColsUpdated,
-									 resultRelations,
-									 subpaths,
-									 subroots,
-									 withCheckOptionLists,
-									 returningLists,
-									 rowMarks,
-									 NULL,
-									 assign_special_exec_param(root)));
-}
-
 /*--------------------
  * grouping_planner
  *	  Perform planning steps related to grouping, aggregation, etc.
@@ -1808,11 +1185,6 @@ inheritance_planner(PlannerInfo *root)
  * This function adds all required top-level processing to the scan/join
  * Path(s) produced by query_planner.
  *
- * If inheritance_update is true, we're being called from inheritance_planner
- * and should not include a ModifyTable step in the resulting Path(s).
- * (inheritance_planner will create a single ModifyTable node covering all the
- * target tables.)
- *
  * tuple_fraction is the fraction of tuples we expect will be retrieved.
  * tuple_fraction is interpreted as follows:
  *	  0: expect all tuples to be retrieved (normal case)
@@ -1830,8 +1202,7 @@ inheritance_planner(PlannerInfo *root)
  *--------------------
  */
 static void
-grouping_planner(PlannerInfo *root, bool inheritance_update,
-				 double tuple_fraction)
+grouping_planner(PlannerInfo *root, double tuple_fraction)
 {
 	Query	   *parse = root->parse;
 	int64		offset_est = 0;
@@ -2323,16 +1694,83 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 											  offset_est, count_est);
 		}
 
-		/*
-		 * If this is an INSERT/UPDATE/DELETE, and we're not being called from
-		 * inheritance_planner, add the ModifyTable node.
-		 */
-		if (parse->commandType != CMD_SELECT && !inheritance_update)
+		/* If this is an INSERT/UPDATE/DELETE, add the ModifyTable node. */
+		if (parse->commandType != CMD_SELECT)
 		{
 			Index		rootRelation;
-			List	   *withCheckOptionLists;
-			List	   *returningLists;
+			List	   *resultRelations = NIL;
+			List	   *updateTargetLists = NIL;
+			List	   *withCheckOptionLists = NIL;
+			List	   *returningLists = NIL;
 			List	   *rowMarks;
+			AttrNumber	resno;
+			ListCell *l;
+
+			if (list_length(root->result_rel_list) > 1)
+			{
+				/* Inherited UPDATE/DELETE, can't be an INSERT. */
+				foreach(l, root->result_rel_list)
+				{
+					ResultRelPlanInfo *resultInfo = lfirst(l);
+					Index	resultRelation = resultInfo->resultRelation;
+
+					/* Add only leaf children to ModifyTable. */
+					if (planner_rt_fetch(resultInfo->resultRelation,
+										 root)->inh)
+						continue;
+
+					/* Also, check that the leaf rel has not turned dummy. */
+					if (IS_DUMMY_REL(find_base_rel(root, resultRelation)))
+						continue;
+
+					resultRelations = lappend_int(resultRelations,
+												  resultInfo->resultRelation);
+					if (resultInfo->updateTargetList)
+						updateTargetLists = lappend(updateTargetLists,
+													resultInfo->updateTargetList);
+					if (resultInfo->withCheckOptions)
+						withCheckOptionLists = lappend(withCheckOptionLists,
+													   resultInfo->withCheckOptions);
+					if (resultInfo->returningList)
+						returningLists = lappend(returningLists,
+												 resultInfo->returningList);
+				}
+
+				/* Add the root relation itself if all leaves were dummy. */
+				if (resultRelations == NIL)
+				{
+					ResultRelPlanInfo *resultInfo = linitial(root->result_rel_list);
+
+					resultRelations = list_make1_int(parse->resultRelation);
+					updateTargetLists = list_make1(resultInfo->updateTargetList);
+					if (resultInfo->withCheckOptions)
+						withCheckOptionLists = list_make1(resultInfo->withCheckOptions);
+					if (resultInfo->returningList)
+						returningLists = list_make1(resultInfo->returningList);
+				}
+			}
+			else if (list_length(root->result_rel_list) == 1)
+			{
+				/* Non-inherited UPDATE/DELETE. */
+				ResultRelPlanInfo *resultInfo = linitial(root->result_rel_list);
+
+				resultRelations = list_make1_int(resultInfo->resultRelation);
+				if (resultInfo->updateTargetList)
+					updateTargetLists = list_make1(resultInfo->updateTargetList);
+				if (resultInfo->withCheckOptions)
+					withCheckOptionLists = list_make1(resultInfo->withCheckOptions);
+				if (resultInfo->returningList)
+					returningLists = list_make1(resultInfo->returningList);
+			}
+			else
+			{
+				/* INSERT. */
+				resultRelations = list_make1_int(parse->resultRelation);
+				if (parse->withCheckOptions)
+					withCheckOptionLists = list_make1(parse->withCheckOptions);
+				if (parse->returningList)
+					returningLists = list_make1(parse->returningList);
+			}
 
 			/*
 			 * If target is a partition root table, we need to mark the
@@ -2345,20 +1783,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 				rootRelation = 0;
 
 			/*
-			 * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
-			 * needed.
-			 */
-			if (parse->withCheckOptions)
-				withCheckOptionLists = list_make1(parse->withCheckOptions);
-			else
-				withCheckOptionLists = NIL;
-
-			if (parse->returningList)
-				returningLists = list_make1(parse->returningList);
-			else
-				returningLists = NIL;
-
-			/*
 			 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node
 			 * will have dealt with fetching non-locked marked rows, else we
 			 * need to have ModifyTable do that.
@@ -2368,16 +1792,31 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 			else
 				rowMarks = root->rowMarks;
 
+			/*
+			 * While we are here, renumber the top-level targetlist so that
+			 * resnos match those in the top-level plan's targetlist.
+			 * XXX - really, this is to prevent apply_tlist_labeling() from
+			 * crashing.
+			 */
+			resno = 1;
+			foreach(lc, root->processed_tlist)
+			{
+				TargetEntry *tle = lfirst(lc);
+
+				tle->resno = resno++;
+			}
+
 			path = (Path *)
 				create_modifytable_path(root, final_rel,
 										parse->commandType,
 										parse->canSetTag,
 										parse->resultRelation,
 										rootRelation,
-										false,
-										list_make1_int(parse->resultRelation),
+										root->partColsUpdated,
+										resultRelations,
 										list_make1(path),
 										list_make1(root),
+										updateTargetLists,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index baefe0e..6a52c6c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -148,6 +148,9 @@ static List *set_returning_clause_references(PlannerInfo *root,
 											 Plan *topplan,
 											 Index resultRelation,
 											 int rtoffset);
+static void set_update_tlist_references(PlannerInfo *root,
+										ModifyTable *splan,
+										int rtoffset);
 
 
 /*****************************************************************************
@@ -808,10 +811,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 		case T_ModifyTable:
 			{
 				ModifyTable *splan = (ModifyTable *) plan;
+				Plan		*subplan = linitial(splan->plans);
 
 				Assert(splan->plan.targetlist == NIL);
 				Assert(splan->plan.qual == NIL);
 
+				if (splan->operation == CMD_UPDATE)
+					set_update_tlist_references(root, splan, rtoffset);
+
 				splan->withCheckOptionLists =
 					fix_scan_list(root, splan->withCheckOptionLists, rtoffset);
 
@@ -819,22 +826,17 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				{
 					List	   *newRL = NIL;
 					ListCell   *lcrl,
-							   *lcrr,
-							   *lcp;
+							   *lcrr;
 
 					/*
-					 * Pass each per-subplan returningList through
+					 * Pass each per-resultrel returningList through
 					 * set_returning_clause_references().
 					 */
 					Assert(list_length(splan->returningLists) == list_length(splan->resultRelations));
-					Assert(list_length(splan->returningLists) == list_length(splan->plans));
-					forthree(lcrl, splan->returningLists,
-							 lcrr, splan->resultRelations,
-							 lcp, splan->plans)
+					forboth(lcrl, splan->returningLists, lcrr, splan->resultRelations)
 					{
 						List	   *rlist = (List *) lfirst(lcrl);
 						Index		resultrel = lfirst_int(lcrr);
-						Plan	   *subplan = (Plan *) lfirst(lcp);
 
 						rlist = set_returning_clause_references(root,
 																rlist,
@@ -2694,6 +2696,52 @@ set_returning_clause_references(PlannerInfo *root,
 	return rlist;
 }
 
+/*
+ * Update splan->updateTargetLists to refer to the subplan's output where
+ * applicable.
+ */
+static void
+set_update_tlist_references(PlannerInfo *root,
+							ModifyTable *splan,
+							int rtoffset)
+{
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, splan->resultRelations, lc2, splan->updateTargetLists)
+	{
+		Index	resultRel = lfirst_int(lc1);
+		List   *updateTargetList = lfirst(lc2);
+		ResultRelPlanInfo *resultInfo = root->result_rel_array[resultRel];
+		AttrNumber	resno;
+		ListCell *lc;
+		indexed_tlist *itlist;
+
+		Assert(resultInfo);
+
+		/*
+		 * Make resnos of subplan tlist TLEs match their ordinal position in
+		 * the list.  It's okay to scribble on them now.
+		 */
+		resno = 1;
+		foreach(lc, resultInfo->subplanTargetList)
+		{
+			TargetEntry *tle = lfirst(lc);
+
+			tle->resno = resno++;
+		}
+
+		itlist = build_tlist_index(resultInfo->subplanTargetList);
+		lfirst(lc2) = fix_join_expr(root,
+									updateTargetList,
+									itlist,
+									NULL,
+									resultRel,
+									rtoffset);
+
+	}
+}
+
 
 /*****************************************************************************
  *					QUERY DEPENDENCY MANAGEMENT
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1452172..d24ac34 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -926,7 +926,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->grouping_map = NULL;
 	subroot->minmax_aggs = NIL;
 	subroot->qual_security_level = 0;
-	subroot->inhTargetKind = INHKIND_NONE;
 	subroot->hasRecursion = false;
 	subroot->wt_param_id = -1;
 	subroot->non_recursive_path = NULL;
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index d56d8c6..b92e3ed 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_coerce.h"
@@ -111,13 +112,27 @@ preprocess_targetlist(PlannerInfo *root)
 	 * for heap_form_tuple to work, the targetlist must match the exact order
 	 * of the attributes. We also need to fill in any missing attributes. -ay
 	 * 10/94
+	 *
+	 * For UPDATE, we don't expand the plan's targetlist.  Instead, another
+	 * targetlist to be computed separately from the plan's targetlist is
+	 * built and passed to the executor in the ModifyTable node; see
+	 * make_update_targetlist().
 	 */
 	tlist = parse->targetList;
-	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+	if (command_type == CMD_INSERT)
 		tlist = expand_targetlist(tlist, command_type,
 								  result_relation, target_relation);
 
 	/*
+	 * Make ResultRelPlanInfo for a UPDATE/DELETE result relation.  If the
+	 * relation has inheritance children, they will get one too when
+	 * query_planner() adds them.
+	 */
+	if (target_relation && command_type != CMD_INSERT)
+		make_result_relation_info(root, result_relation, target_relation,
+								  NULL);
+
+	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
 	 * for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
 	 * rechecking.  See comments for PlanRowMark in plannodes.h.  If you
@@ -415,6 +430,99 @@ expand_targetlist(List *tlist, int command_type,
 	return new_tlist;
 }
 
+/*
+ * make_update_targetlist
+ *		Makes UPDATE targetlist such that "plan" TLEs are used for the
+ *		attributes for which they are present.  For the rest, Vars are
+ *		added to fetch the existing values in the "old" tuple of the
+ *		attributes that are not dropped, and NULL constants otherwise.
+ */
+List *
+make_update_targetlist(PlannerInfo *root,
+					   Index rti, Relation relation,
+					   List *plan_tlist)
+{
+	List	   *new_tlist = NIL;
+	ListCell   *plan_item;
+	int			attrno,
+				numattrs;
+
+	plan_item = list_head(plan_tlist);
+
+	numattrs = RelationGetNumberOfAttributes(relation);
+	for (attrno = 1; attrno <= numattrs; attrno++)
+	{
+		Form_pg_attribute att = TupleDescAttr(relation->rd_att, attrno - 1);
+		TargetEntry *new_tle = NULL;
+		Node	   *new_expr;
+
+		if (plan_item != NULL)
+		{
+			TargetEntry *plan_tle = (TargetEntry *) lfirst(plan_item);
+
+			if (!plan_tle->resjunk && plan_tle->resno == attrno)
+			{
+				/* Don't use the plan TLE as is; it gets changed later. */
+				new_tle = copyObject(plan_tle);
+				plan_item = lnext(plan_tlist, plan_item);
+			}
+		}
+
+
+		/*
+		 * If not plan TLE present, generate a Var reference to the existing
+		 * value of the attribute, so that it gets copied to the new tuple. But
+		 * generate a NULL for dropped columns (we want to drop any old values).
+		 */
+		if (new_tle == NULL)
+		{
+			if (!att->attisdropped)
+			{
+				new_expr = (Node *) makeVar(rti,
+											attrno,
+											att->atttypid,
+											att->atttypmod,
+											att->attcollation,
+											0);
+			}
+			else
+			{
+				/* Insert NULL for dropped column */
+				new_expr = (Node *) makeConst(INT4OID,
+											  -1,
+											  InvalidOid,
+											  sizeof(int32),
+											  (Datum) 0,
+											  true, /* isnull */
+											  true /* byval */ );
+			}
+			new_tle = makeTargetEntry((Expr *) new_expr,
+									  attrno,
+									  pstrdup(NameStr(att->attname)),
+									  false);
+			new_tlist = lappend(new_tlist, new_tle);
+		}
+		else
+			new_tlist = lappend(new_tlist, new_tle);
+	}
+
+
+	/*
+	 * The remaining tlist entries should be resjunk.  We don't need them
+	 * for the update target list, but still check that they really are
+	 * junk.
+	 */
+	while (plan_item)
+	{
+		TargetEntry *plan_tle = (TargetEntry *) lfirst(plan_item);
+
+		if (!plan_tle->resjunk)
+			elog(ERROR, "targetlist is not sorted correctly");
+		plan_item = lnext(plan_tlist, plan_item);
+	}
+
+	return new_tlist;
+}
 
 /*
  * Locate PlanRowMark for given RT index, or return NULL if none
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063..c949544 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -18,6 +18,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
+#include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
@@ -29,6 +30,7 @@ typedef struct
 	PlannerInfo *root;
 	int			nappinfos;
 	AppendRelInfo **appinfos;
+	bool		need_parent_wholerow;
 } adjust_appendrel_attrs_context;
 
 static void make_inh_translation_list(Relation oldrelation,
@@ -37,8 +39,6 @@ static void make_inh_translation_list(Relation oldrelation,
 									  AppendRelInfo *appinfo);
 static Node *adjust_appendrel_attrs_mutator(Node *node,
 											adjust_appendrel_attrs_context *context);
-static List *adjust_inherited_tlist(List *tlist,
-									AppendRelInfo *context);
 
 
 /*
@@ -200,42 +200,42 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
 	context.root = root;
 	context.nappinfos = nappinfos;
 	context.appinfos = appinfos;
+	context.need_parent_wholerow = true;
 
 	/* If there's nothing to adjust, don't call this function. */
 	Assert(nappinfos >= 1 && appinfos != NULL);
 
-	/*
-	 * Must be prepared to start with a Query or a bare expression tree.
-	 */
-	if (node && IsA(node, Query))
-	{
-		Query	   *newnode;
-		int			cnt;
+	/* Should never be translating a Query tree. */
+	Assert (node == NULL || !IsA(node, Query));
+	result = adjust_appendrel_attrs_mutator(node, &context);
 
-		newnode = query_tree_mutator((Query *) node,
-									 adjust_appendrel_attrs_mutator,
-									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
-		for (cnt = 0; cnt < nappinfos; cnt++)
-		{
-			AppendRelInfo *appinfo = appinfos[cnt];
+	return result;
+}
 
-			if (newnode->resultRelation == appinfo->parent_relid)
-			{
-				newnode->resultRelation = appinfo->child_relid;
-				/* Fix tlist resnos too, if it's inherited UPDATE */
-				if (newnode->commandType == CMD_UPDATE)
-					newnode->targetList =
-						adjust_inherited_tlist(newnode->targetList,
-											   appinfo);
-				break;
-			}
-		}
+/*
+ * adjust_target_appendrel_attrs
+ *		like adjust_appendrel_attrs, but treats wholerow Vars a bit
+ *		differently in that it doesn't convert any child table
+ *		wholerows contained in 'node' back to the parent reltype.
+ */
+Node *
+adjust_target_appendrel_attrs(PlannerInfo *root, Node *node,
+							  AppendRelInfo *appinfo)
+{
+	Node	   *result;
+	adjust_appendrel_attrs_context context;
 
-		result = (Node *) newnode;
-	}
-	else
-		result = adjust_appendrel_attrs_mutator(node, &context);
+	context.root = root;
+	context.nappinfos = 1;
+	context.appinfos = &appinfo;
+	context.need_parent_wholerow = false;
+
+	/* If there's nothing to adjust, don't call this function. */
+	Assert(appinfo != NULL);
+
+	/* Should never be translating a Query tree. */
+	Assert (node == NULL || !IsA(node, Query));
+	result = adjust_appendrel_attrs_mutator(node, &context);
 
 	return result;
 }
@@ -277,9 +277,25 @@ adjust_appendrel_attrs_mutator(Node *node,
 			{
 				Node	   *newnode;
 
+				/*
+				 * If this Var appears to have a unusual attno assigned,
+				 * it must be one of the "special" Vars assigned to parent
+				 * target table.
+				 */
 				if (var->varattno > list_length(appinfo->translated_vars))
-					elog(ERROR, "attribute %d of relation \"%s\" does not exist",
-						 var->varattno, get_rel_name(appinfo->parent_reloid));
+				{
+					SpecialJunkVarInfo *sjv =
+						get_special_junk_var(context->root, var->varattno);
+
+					if (sjv == NULL)
+						elog(ERROR, "attribute %d of relation \"%s\" does not exist",
+							 var->varattno, get_rel_name(appinfo->parent_reloid));
+					if (!bms_is_member(appinfo->child_relid, sjv->child_relids))
+						return (Node *) makeNullConst(var->vartype,
+													  var->vartypmod,
+													  var->varcollid);
+					var->varattno = sjv->varattno;
+				}
 				newnode = copyObject(list_nth(appinfo->translated_vars,
 											  var->varattno - 1));
 				if (newnode == NULL)
@@ -298,7 +314,10 @@ adjust_appendrel_attrs_mutator(Node *node,
 				if (OidIsValid(appinfo->child_reltype))
 				{
 					Assert(var->vartype == appinfo->parent_reltype);
-					if (appinfo->parent_reltype != appinfo->child_reltype)
+					/* Make sure the Var node has the right type ID, too */
+					var->vartype = appinfo->child_reltype;
+					if (appinfo->parent_reltype != appinfo->child_reltype &&
+						context->need_parent_wholerow)
 					{
 						ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
 
@@ -306,8 +325,6 @@ adjust_appendrel_attrs_mutator(Node *node,
 						r->resulttype = appinfo->parent_reltype;
 						r->convertformat = COERCE_IMPLICIT_CAST;
 						r->location = -1;
-						/* Make sure the Var node has the right type ID, too */
-						var->vartype = appinfo->child_reltype;
 						return (Node *) r;
 					}
 				}
@@ -627,16 +644,13 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
  * the target resnos match the child table (they may not, in the case of
  * a column that was added after-the-fact by ALTER TABLE).  In some cases
  * this can force us to re-order the tlist to preserve resno ordering.
- * (We do all this work in special cases so that preptlist.c is fast for
- * the typical case.)
  *
- * The given tlist has already been through expression_tree_mutator;
- * therefore the TargetEntry nodes are fresh copies that it's okay to
- * scribble on.
+ * Note that the input tlist is scribbled on, so the caller must have made
+ * a copy of it first.
  *
  * Note that this is not needed for INSERT because INSERT isn't inheritable.
  */
-static List *
+List *
 adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
 {
 	bool		changed_it = false;
@@ -663,9 +677,11 @@ adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
 			elog(ERROR, "attribute %d of relation \"%s\" does not exist",
 				 tle->resno, get_rel_name(context->parent_reloid));
 		childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1);
-		if (childvar == NULL || !IsA(childvar, Var))
-			elog(ERROR, "attribute %d of relation \"%s\" does not exist",
-				 tle->resno, get_rel_name(context->parent_reloid));
+		/* Ignore dummy TLEs for the parent's dropped columns. */
+		Assert(childvar != NULL ||
+			   (IsA(tle->expr, Const) && ((Const *) tle->expr)->constisnull));
+		if (childvar == NULL)
+			continue;
 
 		if (tle->resno != childvar->varattno)
 		{
@@ -695,6 +711,10 @@ adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
 			if (tle->resjunk)
 				continue;		/* ignore junk items */
 
+			/* Also the dummy TLEs for the parent's dropped columns. */
+			if (IsA(tle->expr, Const) && ((Const *) tle->expr)->constisnull)
+				continue;
+
 			if (tle->resno == attrno)
 				new_tlist = lappend(new_tlist, tle);
 			else if (tle->resno > attrno)
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd3..a79b77d 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -19,8 +19,10 @@
 #include "catalog/partition.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_type.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
 #include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
@@ -29,16 +31,19 @@
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
+#include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 
 
 static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 									   RangeTblEntry *parentrte,
 									   Index parentRTindex, Relation parentrel,
-									   PlanRowMark *top_parentrc, LOCKMODE lockmode);
+									   PlanRowMark *top_parentrc, LOCKMODE lockmode,
+									   RelOptInfo *rootrelinfo);
 static void expand_single_inheritance_child(PlannerInfo *root,
 											RangeTblEntry *parentrte,
 											Index parentRTindex, Relation parentrel,
@@ -49,6 +54,15 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 									  List *translated_vars);
 static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
 									  RangeTblEntry *rte, Index rti);
+static void add_child_junk_vars(PlannerInfo *root, RelOptInfo *childrel,
+					Relation childrelation, RelOptInfo *rootrel);
+static bool need_special_junk_var(PlannerInfo *root,
+					  TargetEntry *tle,
+					  Index child_relid);
+static void add_special_junk_var(PlannerInfo *root,
+					 TargetEntry *tle,
+					 Index child_relid,
+					 RelOptInfo *rootrel);
 
 
 /*
@@ -85,6 +99,8 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	PlanRowMark *oldrc;
 	bool		old_isParent = false;
 	int			old_allMarkTypes = 0;
+	List	   *newvars = NIL;
+	ListCell   *l;
 
 	Assert(rte->inh);			/* else caller error */
 
@@ -141,7 +157,8 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * extract the partition key columns of all the partitioned tables.
 		 */
 		expand_partitioned_rtentry(root, rel, rte, rti,
-								   oldrelation, oldrc, lockmode);
+								   oldrelation, oldrc, lockmode,
+								   rel);
 	}
 	else
 	{
@@ -151,7 +168,6 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * children, so it's not possible for both cases to apply.)
 		 */
 		List	   *inhOIDs;
-		ListCell   *l;
 
 		/* Scan for all members of inheritance set, acquire needed locks */
 		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
@@ -180,6 +196,7 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 			Relation	newrelation;
 			RangeTblEntry *childrte;
 			Index		childRTindex;
+			RelOptInfo *childrel;
 
 			/* Open rel if needed; we already have required locks */
 			if (childOID != parentOID)
@@ -205,7 +222,14 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 											&childrte, &childRTindex);
 
 			/* Create the otherrel RelOptInfo too. */
-			(void) build_simple_rel(root, childRTindex, rel);
+			childrel = build_simple_rel(root, childRTindex, rel);
+
+			if (rti == root->parse->resultRelation)
+			{
+				Assert(root->parse->commandType == CMD_UPDATE ||
+					   root->parse->commandType == CMD_DELETE);
+				add_child_junk_vars(root, childrel, newrelation, rel);
+			}
 
 			/* Close child relations, but keep locks */
 			if (childOID != parentOID)
@@ -226,7 +250,6 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		Var		   *var;
 		TargetEntry *tle;
 		char		resname[32];
-		List	   *newvars = NIL;
 
 		/* The old PlanRowMark should already have necessitated adding TID */
 		Assert(old_allMarkTypes & ~(1 << ROW_MARK_COPY));
@@ -265,14 +288,56 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 			root->processed_tlist = lappend(root->processed_tlist, tle);
 			newvars = lappend(newvars, var);
 		}
+	}
+
+	/*
+	 * If parent is a result relation, add necessary junk columns to the
+	 * top level targetlist and parent's reltarget.
+	 */
+	if (rti == root->parse->resultRelation)
+	{
+		int		resno = list_length(root->processed_tlist) + 1;
+		TargetEntry *tle;
+		Var	   *var;
 
 		/*
-		 * Add the newly added Vars to parent's reltarget.  We needn't worry
-		 * about the children's reltargets, they'll be made later.
+		 * First, we will need a "tableoid" for the executor to identify
+		 * the result relation to update or delete for a given tuple.
 		 */
-		add_vars_to_targetlist(root, newvars, bms_make_singleton(0), false);
+		var= makeVar(rti, TableOidAttributeNumber, OIDOID, -1, InvalidOid, 0);
+		tle = makeTargetEntry((Expr *) var,
+							  resno,
+							  pstrdup("tableoid"),
+							  true);
+		++resno;
+
+		root->processed_tlist = lappend(root->processed_tlist, tle);
+		newvars = lappend(newvars, var);
+
+		/* Now add "special" child junk Vars. */
+		foreach(l, root->specialJunkVars)
+		{
+			SpecialJunkVarInfo *sjv = lfirst(l);
+			Oid		vartype = sjv->special_attno == 0 ?
+									oldrelation->rd_rel->reltype :
+									sjv->vartype;
+
+			var = makeVar(rti, sjv->special_attno, vartype,
+						  sjv->vartypmod, sjv->varcollid, 0);
+			tle = makeTargetEntry((Expr *) var, resno, sjv->attrname, true);
+			root->processed_tlist = lappend(root->processed_tlist, tle);
+			newvars = lappend(newvars, var);
+			++resno;
+		}
 	}
 
+	/*
+	 * Add the newly added Vars to parent's reltarget.  We needn't worry
+	 * about the children's reltargets, they'll be made later.
+	 */
+	if (newvars != NIL)
+		add_vars_to_targetlist(root, newvars, bms_make_singleton(0), false);
+
 	table_close(oldrelation, NoLock);
 }
 
@@ -284,7 +349,8 @@ static void
 expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 						   RangeTblEntry *parentrte,
 						   Index parentRTindex, Relation parentrel,
-						   PlanRowMark *top_parentrc, LOCKMODE lockmode)
+						   PlanRowMark *top_parentrc, LOCKMODE lockmode,
+						   RelOptInfo *rootrelinfo)
 {
 	PartitionDesc partdesc;
 	Bitmapset  *live_parts;
@@ -379,11 +445,19 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 		relinfo->all_partrels = bms_add_members(relinfo->all_partrels,
 												childrelinfo->relids);
 
+		if (rootrelinfo->relid == root->parse->resultRelation)
+		{
+			Assert(root->parse->commandType == CMD_UPDATE ||
+				   root->parse->commandType == CMD_DELETE);
+			add_child_junk_vars(root, childrelinfo, childrel, rootrelinfo);
+		}
+
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
-									   childrel, top_parentrc, lockmode);
+									   childrel, top_parentrc, lockmode,
+									   rootrelinfo);
 
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
@@ -391,6 +465,212 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 }
 
 /*
+ * add_child_junk_vars
+ *		Check if the row-identifying junk column(s) required by this child
+ *		table is present in the top-level targetlist and if not, add.  The
+ *		junk column's properties (name, type, or Var attribute number) may
+ *		require us to add "special" junk Vars to parent's targetlist which it
+ *		or any of its other children may not be able to produce themselves.
+ *
+ * Note: this only concerns foreign child tables
+ */
+static void
+add_child_junk_vars(PlannerInfo *root, RelOptInfo *childrel,
+					Relation childrelation,
+					RelOptInfo *rootrel)
+{
+	RangeTblEntry *childrte = root->simple_rte_array[childrel->relid];
+	Query	parsetree;
+	List   *child_junk_vars;
+	ListCell *lc;
+
+	/* Currently, only foreign children may require such junk columns. */
+	if (childrel->fdwroutine == NULL ||
+		childrel->fdwroutine->AddForeignUpdateTargets == NULL)
+		return;
+
+	/*
+	 * Ask the table's FDW what junk Vars it needs.
+	 */
+	memcpy(&parsetree, root->parse, sizeof(Query));
+	parsetree.resultRelation = childrel->relid;
+	parsetree.targetList = NIL;
+	childrel->fdwroutine->AddForeignUpdateTargets(&parsetree,
+												  childrte,
+												  childrelation);
+	child_junk_vars = parsetree.targetList;
+
+	/*
+	 * We need the "old" tuple to fill up the values for unassigned-to
+	 * attributes in the case of UPDATE.  We will need it also if there
+	 * are any DELETE row triggers.  This matches what
+	 * rewriteTargetListUD() does.
+	 */
+	if (parsetree.commandType == CMD_UPDATE ||
+		(childrelation->trigdesc &&
+		 (childrelation->trigdesc->trig_delete_after_row ||
+		  childrelation->trigdesc->trig_delete_before_row)))
+	{
+		Var *var;
+		TargetEntry *tle;
+
+		var = makeWholeRowVar(childrte,
+							  parsetree.resultRelation,
+							  0,
+							  false);
+
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(child_junk_vars) + 1,
+							  "wholerow",
+							  true);
+		child_junk_vars = lappend(child_junk_vars, tle);
+	}
+
+	/* Check if any of the child junk Vars need any special attention. */
+	foreach(lc, child_junk_vars)
+	{
+		TargetEntry *tle = lfirst(lc);
+
+		Assert(tle->resjunk);
+
+		if (!IsA(tle->expr, Var))
+			elog(ERROR, "UPDATE junk expression added by foreign table %u not suppported",
+				 childrte->relid);
+
+		if (need_special_junk_var(root, tle, childrel->relid))
+			add_special_junk_var(root, tle, childrel->relid, rootrel);
+	}
+}
+
+/*
+ * Check if we need to consider the junk TLE required by the given child
+ * target relation a "special" junk var, so as to create a new
+ * SpecialJunkVarInfo for it.
+ *
+ * Side-effect warning: If the TLE is found to match the properties of some
+ * SpecialJunkVarInfo already present in root->specialJunkVars, this adds
+ * child_relid into SpecialJunkVarInfo.child_relids.
+ */
+static bool
+need_special_junk_var(PlannerInfo *root,
+					  TargetEntry *tle,
+					  Index child_relid)
+{
+	Var   *var = (Var *) tle->expr;
+	ListCell *lc;
+
+	Assert(IsA(var, Var));
+
+	/* Maybe a similar looking TLE was already considered one? */
+	foreach(lc, root->specialJunkVars)
+	{
+		SpecialJunkVarInfo *sjv = lfirst(lc);
+
+		if (strcmp(tle->resname, sjv->attrname) == 0 &&
+			var->vartype == sjv->vartype &&
+			var->varattno == sjv->varattno)
+		{
+			sjv->child_relids = bms_add_member(sjv->child_relids, child_relid);
+			return false;
+		}
+	}
+
+	/* It's special if the top-level targetlist doesn't have it. */
+	foreach(lc, root->parse->targetList)
+	{
+		TargetEntry *known_tle = lfirst(lc);
+		Var	   *known_var = (Var *) known_tle->expr;
+		Oid		known_vartype = exprType((Node *) known_var);
+		AttrNumber	known_varattno = known_var->varattno;
+
+		/* Ignore non-junk columns. */
+		if (!known_tle->resjunk)
+			continue;
+
+		/* Ignore RETURNING expressions too, if any. */
+		if (known_tle->resname == NULL)
+			continue;
+
+		Assert(IsA(known_var, Var));
+		if (strcmp(tle->resname, known_tle->resname) == 0 &&
+			var->vartype == known_vartype &&
+			var->varattno == known_varattno)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Makes a SpecialJunkVarInfo for a child junk Var that was unlike
+ * any other seen so far.
+ */
+static void
+add_special_junk_var(PlannerInfo *root,
+					 TargetEntry *tle,
+					 Index child_relid,
+					 RelOptInfo *rootrel)
+{
+	SpecialJunkVarInfo *sjv;
+	Var	   *var = (Var *) tle->expr;
+	Oid		vartype = exprType((Node *) var);
+	AttrNumber		special_attno;
+	AppendRelInfo  *appinfo = root->append_rel_array[child_relid];
+
+	Assert(IsA(var, Var));
+
+	/*
+	 * If root parent can emit this var, no need to assign "special" attribute
+	 * numbers.  Unfortunately, this results in all the children needlessly
+	 * emitting the column, even if only this child needs it.
+	 */
+	Assert(appinfo != NULL && var->varattno < appinfo->num_child_cols);
+
+	/* Wholerow ok. */
+	if (var->varattno == 0)
+		special_attno = 0;
+	/* A column that is also present in the parent. */
+	else if (appinfo->parent_colnos[var->varattno] > 0)
+		special_attno = appinfo->parent_colnos[var->varattno];
+	/* Nope, use a "special" attribute number. */
+	else
+		special_attno = ++(rootrel->max_attr);
+
+	sjv = makeNode(SpecialJunkVarInfo);
+	sjv->attrname = pstrdup(tle->resname);
+	sjv->vartype = vartype;
+	sjv->vartypmod = exprTypmod((Node *) var);
+	sjv->varcollid = exprCollation((Node *) var);
+	/*
+	 * Parent Var with a "special" attribute number is mapped to a child Var
+	 * with this attribute number, provided the child's RT index is in
+	 * child_relids.  For other children, it's mapped to a NULL constant.
+	 * See adjust_appendrel_attrs_mutator().
+	 */
+	sjv->varattno = var->varattno;
+	sjv->special_attno = special_attno;
+	sjv->child_relids = bms_add_member(NULL, child_relid);
+
+	root->specialJunkVars = lappend(root->specialJunkVars, sjv);
+}
+
+SpecialJunkVarInfo *
+get_special_junk_var(PlannerInfo *root, Index special_attno)
+{
+	ListCell *l;
+
+	foreach(l, root->specialJunkVars)
+	{
+		SpecialJunkVarInfo *sjv = lfirst(l);
+
+		if (sjv->special_attno == special_attno)
+			return sjv;
+	}
+
+	return NULL;
+}
+
+/*
  * expand_single_inheritance_child
  *		Build a RangeTblEntry and an AppendRelInfo, plus maybe a PlanRowMark.
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e845a4b..3f977ba 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3490,6 +3490,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
  * 'resultRelations' is an integer list of actual RT indexes of target rel(s)
  * 'subpaths' is a list of Path(s) producing source data (one per rel)
  * 'subroots' is a list of PlannerInfo structs (one per rel)
+ * 'updateTargetLists' is a list of UPDATE NEW tuple targetlists.
  * 'withCheckOptionLists' is a list of WCO lists (one per rel)
  * 'returningLists' is a list of RETURNING tlists (one per rel)
  * 'rowMarks' is a list of PlanRowMarks (non-locking only)
@@ -3502,7 +3503,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						Index nominalRelation, Index rootRelation,
 						bool partColsUpdated,
 						List *resultRelations, List *subpaths,
-						List *subroots,
+						List *subroots, List *updateTargetLists,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
 						int epqParam)
@@ -3511,8 +3512,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	double		total_size;
 	ListCell   *lc;
 
-	Assert(list_length(resultRelations) == list_length(subpaths));
-	Assert(list_length(resultRelations) == list_length(subroots));
 	Assert(withCheckOptionLists == NIL ||
 		   list_length(resultRelations) == list_length(withCheckOptionLists));
 	Assert(returningLists == NIL ||
@@ -3572,6 +3571,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->resultRelations = resultRelations;
 	pathnode->subpaths = subpaths;
 	pathnode->subroots = subroots;
+	pathnode->updateTargetLists = updateTargetLists;
 	pathnode->withCheckOptionLists = withCheckOptionLists;
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2554502..c6b83db 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -35,6 +35,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/supportnodes.h"
+#include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
@@ -111,7 +112,7 @@ static void set_baserel_partition_constraint(Relation relation,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  RelOptInfo *rel, RelOptInfo *parent)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -463,6 +464,16 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		set_relation_partition_info(root, rel, relation);
 
+	/*
+	 * If this appears to be a child of an UPDATE/DELETE parent relation, make
+	 * its ResultRelPlanInfo now while we have the relation open.
+	 */
+	if (root->parse->resultRelation &&
+		root->parse->commandType != CMD_INSERT &&
+		parent && root->result_rel_array[parent->relid])
+		make_result_relation_info(root, varno, relation,
+								  root->result_rel_array[parent->relid]);
+
 	table_close(relation, NoLock);
 
 	/*
@@ -1438,18 +1449,11 @@ relation_excluded_by_constraints(PlannerInfo *root,
 
 			/*
 			 * When constraint_exclusion is set to 'partition' we only handle
-			 * appendrel members.  Normally, they are RELOPT_OTHER_MEMBER_REL
-			 * relations, but we also consider inherited target relations as
-			 * appendrel members for the purposes of constraint exclusion
-			 * (since, indeed, they were appendrel members earlier in
-			 * inheritance_planner).
-			 *
-			 * In both cases, partition pruning was already applied, so there
-			 * is no need to consider the rel's partition constraints here.
+			 * appendrel members.  Partition pruning has already been applied,
+			 * so there is no need to consider the rel's partition constraints
+			 * here.
 			 */
-			if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
-				(rel->relid == root->parse->resultRelation &&
-				 root->inhTargetKind != INHKIND_NONE))
+			if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
 				break;			/* appendrel member, so process it */
 			return false;
 
@@ -1462,9 +1466,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
 			 * its partition constraints haven't been considered yet, so
 			 * include them in the processing here.
 			 */
-			if (rel->reloptkind == RELOPT_BASEREL &&
-				!(rel->relid == root->parse->resultRelation &&
-				  root->inhTargetKind != INHKIND_NONE))
+			if (rel->reloptkind == RELOPT_BASEREL)
 				include_partition = true;
 			break;				/* always try to exclude */
 	}
@@ -2345,3 +2347,98 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * make_result_relation_info
+ *		Adds information to the PlannerInfo about a UPDATE/DELETE result
+ *		relation that will be made part of the ModifyTable node in the
+ *		final plan.
+ *
+ * For UPDATE, this adds the targetlist (called 'updateTargetList') that is
+ * computed separately from the plan's top-level targetlist to get the tuples
+ * suitable for this result relation.  Some entries are copied directly from
+ * the plan's top-level targetlist while others are simply Vars of the
+ * relation.  In fact, each TLE copied from the top-level targetlist is later
+ * changed such that its expression is an OUTER Var referring to the
+ * appropriate column in the plan's output; see setrefs.c:
+ * set_update_tlist_references().
+ */
+void
+make_result_relation_info(PlannerInfo *root,
+						  Index rti, Relation relation,
+						  ResultRelPlanInfo *parentInfo)
+{
+	Query			  *parse = root->parse;
+	ResultRelPlanInfo *resultInfo = makeNode(ResultRelPlanInfo);
+	AppendRelInfo	  *appinfo = parentInfo ? root->append_rel_array[rti] :
+								NULL;
+	List   *subplanTargetList;
+	List   *returningList = NIL;
+	List   *updateTargetList = NIL;
+	List   *withCheckOptions = NIL;
+
+	Assert(appinfo != NULL || parentInfo == NULL);
+	if (parentInfo)
+	{
+		subplanTargetList = (List *)
+			adjust_appendrel_attrs(root, (Node *) parentInfo->subplanTargetList,
+								   1, &appinfo);
+		Assert(appinfo != NULL);
+		if (parentInfo->withCheckOptions)
+			withCheckOptions = (List *)
+				adjust_appendrel_attrs(root,
+									   (Node *) parentInfo->withCheckOptions,
+									   1, &appinfo);
+		if (parentInfo->returningList)
+			returningList = (List *)
+				adjust_appendrel_attrs(root, (Node *) parentInfo->returningList,
+									   1, &appinfo);
+	}
+	else
+	{
+		/* parse->targetList resnos get updated later, so make a copy. */
+		subplanTargetList = copyObject(parse->targetList);
+		withCheckOptions = parse->withCheckOptions;
+		returningList = parse->returningList;
+	}
+
+	if (parse->commandType == CMD_UPDATE)
+	{
+		List	   *planTargetList = subplanTargetList;
+
+		/* resnos must match the child attributes numbers. */
+		if (appinfo)
+			planTargetList = adjust_inherited_tlist(planTargetList, appinfo);
+
+		/* Fix to use plan TLEs over the existing ones. */
+		updateTargetList = make_update_targetlist(root, rti, relation,
+												  planTargetList);
+	}
+
+	/*
+	 * Of the following, everything except subplanTargetList goes into the
+	 * ModifyaTable plan.  subplanTargetListis is only kept around because
+	 * set_update_tlist_references() must use result relation specific
+	 * version of the original targetlist when matching the entries in
+	 * their updateTargetLists.
+	 */
+	resultInfo->resultRelation = rti;
+	resultInfo->subplanTargetList = subplanTargetList;
+	resultInfo->updateTargetList = updateTargetList;
+	resultInfo->withCheckOptions = withCheckOptions;
+	resultInfo->returningList = returningList;
+
+	root->result_rel_list = lappend(root->result_rel_list, resultInfo);
+
+	/*
+	 * We can get here before query_planner() is called, so the array may
+	 * not have been created yet.  Worry not, because once it's created,
+	 * anything present in result_rel_list at that point will be put into
+	 * the array.
+	 */
+	if (root->result_rel_array)
+	{
+		Assert(root->result_rel_array[rti] == NULL);
+		root->result_rel_array[rti] = resultInfo;
+	}
+}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index a203e6f..7eb138f 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -16,18 +16,23 @@
 
 #include <limits.h>
 
+#include "access/table.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/inherit.h"
+#include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/plancat.h"
+#include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
+#include "parser/parsetree.h"
+#include "partitioning/partbounds.h"
 #include "utils/hsearch.h"
 #include "utils/lsyscache.h"
 
@@ -108,6 +113,19 @@ setup_simple_rel_arrays(PlannerInfo *root)
 		root->simple_rte_array[rti++] = rte;
 	}
 
+	/* For UPDATE/DELETE result relations.  Filled later. */
+	if (root->parse->resultRelation && root->parse->commandType != CMD_INSERT)
+	{
+		root->result_rel_array = (ResultRelPlanInfo **)
+		palloc0(root->simple_rel_array_size * sizeof(ResultRelPlanInfo *));
+		foreach(lc, root->result_rel_list)
+		{
+			ResultRelPlanInfo *resultInfo = lfirst(lc);
+
+			root->result_rel_array[resultInfo->resultRelation] = resultInfo;
+		}
+	}
+
 	/* append_rel_array is not needed if there are no AppendRelInfos */
 	if (root->append_rel_list == NIL)
 	{
@@ -183,6 +201,21 @@ expand_planner_arrays(PlannerInfo *root, int add_size)
 			palloc0(sizeof(AppendRelInfo *) * new_size);
 	}
 
+	if (root->result_rel_array)
+	{
+		root->result_rel_array = (ResultRelPlanInfo **)
+			repalloc(root->result_rel_array,
+					 sizeof(ResultRelPlanInfo *) * new_size);
+		MemSet(root->result_rel_array + root->simple_rel_array_size,
+			   0, sizeof(ResultRelPlanInfo *) * add_size);
+	}
+	else if (root->parse->resultRelation &&
+			 root->parse->commandType != CMD_INSERT)
+	{
+		root->result_rel_array = (ResultRelPlanInfo **)
+			palloc0(sizeof(ResultRelPlanInfo *) * new_size);
+	}
+
 	root->simple_rel_array_size = new_size;
 }
 
@@ -304,7 +337,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, rel, parent);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 02a3c6b..4c1677f 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -220,6 +220,25 @@ count_nonjunk_tlist_entries(List *tlist)
 	return len;
 }
 
+/*
+ * filter_junk_tlist_entries
+ *		What it says ...
+ */
+List *
+filter_junk_tlist_entries(List *tlist)
+{
+	List	   *result = NIL;
+	ListCell   *l;
+
+	foreach(l, tlist)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+		if (!tle->resjunk)
+			result = lappend(result, tle);
+	}
+	return result;
+}
 
 /*
  * tlist_same_exprs
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index fe777c3..9177eb5 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1480,17 +1480,14 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 												target_relation);
 
 		/*
-		 * If we have a row-level trigger corresponding to the operation, emit
-		 * a whole-row Var so that executor will have the "old" row to pass to
-		 * the trigger.  Alas, this misses system columns.
+		 * We need the "old" tuple to fill up the values for unassigned-to
+		 * attributes in the case of UPDATE.  We will need it also if there
+		 * are any DELETE row triggers.
 		 */
-		if (target_relation->trigdesc &&
-			((parsetree->commandType == CMD_UPDATE &&
-			  (target_relation->trigdesc->trig_update_after_row ||
-			   target_relation->trigdesc->trig_update_before_row)) ||
-			 (parsetree->commandType == CMD_DELETE &&
-			  (target_relation->trigdesc->trig_delete_after_row ||
-			   target_relation->trigdesc->trig_delete_before_row))))
+		if (parsetree->commandType == CMD_UPDATE ||
+			(target_relation->trigdesc &&
+			 (target_relation->trigdesc->trig_delete_after_row ||
+			  target_relation->trigdesc->trig_delete_before_row)))
 		{
 			var = makeWholeRowVar(target_rte,
 								  parsetree->resultRelation,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c7deeac..903b110 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -603,4 +603,13 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 									 const char *relname);
 
+/* prototypes from functions in nodeModifyTable.c */
+extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *mtstate, Oid reloid,
+						 int *whichrel);
+/* needed by trigger.c */
+extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+						  TupleTableSlot *planSlot,
+						  ItemPointerData *tupleid,
+						  HeapTupleData *oldtuple);
+
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 98e0072..ae2cbf0 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -462,9 +462,6 @@ typedef struct ResultRelInfo
 	/* number of stored generated columns we need to compute */
 	int			ri_NumGeneratedNeeded;
 
-	/* for removing junk attributes from tuples */
-	JunkFilter *ri_junkFilter;
-
 	/* list of RETURNING expressions */
 	List	   *ri_returningList;
 
@@ -491,6 +488,12 @@ typedef struct ResultRelInfo
 
 	/* For use by copy.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
+
+	/* Stuff for generating and holding "clean" tuples of this relation */
+	int				ri_junkAttno;
+	TupleTableSlot *ri_oldTupleSlot;
+	TupleTableSlot *ri_newTupleSlot;
+	ProjectionInfo *ri_projectNew;
 } ResultRelInfo;
 
 /* ----------------
@@ -1166,15 +1169,25 @@ typedef struct ModifyTableState
 	CmdType		operation;		/* INSERT, UPDATE, or DELETE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
 	bool		mt_done;		/* are we done? */
-	PlanState **mt_plans;		/* subplans (one per target rel) */
-	int			mt_nplans;		/* number of plans in the array */
-	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 */
+	PlanState **mt_plans;		/* subplans (actually only 1!) */
+	int			mt_nplans;		/* number of plans in mt_plans (only 1!) */
+	int			mt_whichplan;	/* which one is being executed (always 0th!) */
+	int			mt_nrels;		/* number of result rels in the arrays */
+	int			mt_whichrel;	/* Array index of target rel being targeted */
+	TupleTableSlot **mt_scans;  /* input tuple for each result relation */
+
+	/*
+	 * For UPDATE and DELETE, resno of the TargetEntry corresponding to
+	 * the "tableoid" junk attribute present in the subplan's targetlist.
+	 */
+	int			mt_tableOidAttno;
+
+	ResultRelInfo *resultRelInfo;	/* Target relations */
+	HTAB	   *mt_subplan_resultrel_hash;	/* hash table to look up result
+											 * relation by OID. */
 	ResultRelInfo *rootResultRelInfo;	/* root target relation (partitioned
 										 * table root) */
-	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
+	List	  **mt_arowmarks;	/* ExecAuxRowMark lists (actually only 1!) */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b..e7d99c8 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -269,6 +269,8 @@ typedef enum NodeTag
 	T_PlaceHolderVar,
 	T_SpecialJoinInfo,
 	T_AppendRelInfo,
+	T_ResultRelPlanInfo,
+	T_SpecialJunkVarInfo,
 	T_PlaceHolderInfo,
 	T_MinMaxAggInfo,
 	T_PlannerParamItem,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 485d1b0..a0ca24b 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -81,18 +81,6 @@ typedef enum UpperRelationKind
 	/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
 } UpperRelationKind;
 
-/*
- * This enum identifies which type of relation is being planned through the
- * inheritance planner.  INHKIND_NONE indicates the inheritance planner
- * was not used.
- */
-typedef enum InheritanceKind
-{
-	INHKIND_NONE,
-	INHKIND_INHERITED,
-	INHKIND_PARTITIONED
-} InheritanceKind;
-
 /*----------
  * PlannerGlobal
  *		Global information for planning/optimization
@@ -218,6 +206,12 @@ struct PlannerInfo
 	 */
 	struct AppendRelInfo **append_rel_array;
 
+	/* Valid for UPDATE/DELETE. */
+	List	   *result_rel_list;	/* List of ResultRelPlanInfo */
+	/* Same length as other "simple" rel arrays. */
+	struct ResultRelPlanInfo **result_rel_array;
+	List	   *specialJunkVars;	/* List of SpecialJunkVarInfo */
+
 	/*
 	 * all_baserels is a Relids set of all base relids (but not "other"
 	 * relids) in the query; that is, the Relids identifier of the final join
@@ -339,9 +333,6 @@ struct PlannerInfo
 	Index		qual_security_level;	/* minimum security_level for quals */
 	/* Note: qual_security_level is zero if there are no securityQuals */
 
-	InheritanceKind inhTargetKind;	/* indicates if the target relation is an
-									 * inheritance child or partition or a
-									 * partitioned table */
 	bool		hasJoinRTEs;	/* true if any RTEs are RTE_JOIN kind */
 	bool		hasLateralRTEs; /* true if any RTEs are marked LATERAL */
 	bool		hasHavingQual;	/* true if havingQual was non-null */
@@ -1817,7 +1808,8 @@ typedef struct ModifyTablePath
 	bool		partColsUpdated;	/* some part key in hierarchy updated */
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *subpaths;		/* Path(s) producing source data */
-	List	   *subroots;		/* per-target-table PlannerInfos */
+	List	   *subroots;		/* per-path PlannerInfos */
+	List	   *updateTargetLists; /* per-target-table tlists */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
@@ -2274,6 +2266,85 @@ typedef struct AppendRelInfo
 } AppendRelInfo;
 
 /*
+ * ResultRelPlanInfo
+ *		Information about UPDATE/DELETE result relations
+ *
+ * For the original result relation, these fields point to the information
+ * in the original Query, except the target lists are different.
+ * subplanTargetList is set to a copy of PlannerInfo.processed_tlist after all
+ * the necessary junk columns have been added and is also what the top-level
+ * plan produces.  updateTargetList is obtained by applying expand_targetlist()
+ * to subplanTargetList, followed by filtering out junk columns, so that its
+ * output is a tuple ready to be put into the result relation.
+ *
+ * For child result relations, relevant fields are obtained by translating Vars
+ * and for updateTargetList also adjusting the resnos to match the child
+ * attributes numbers.
+ *
+ * Everything execpt subplanTargetList goes into the ModifyTable node.
+ *
+ * See set_result_relation_info().
+ */
+typedef struct ResultRelPlanInfo
+{
+	NodeTag		type;
+
+	Index		resultRelation;
+	List	   *subplanTargetList;
+	List	   *updateTargetList;
+	List	   *withCheckOptions;
+	List	   *returningList;
+} ResultRelPlanInfo;
+
+/*
+ * SpecialJunkVarInfo
+ *
+ * This contains mapping information about certain junk Vars contained in the
+ * top-level targetlist.
+ *
+ * When updating (or deleting) inheritance hierarchies, some child tables may
+ * require row-identifying junk Vars that are not same as the one installed by
+ * the table being modified (the inheritance root).  For each such group of
+ * child junk vars (typically coming from the same FDW), we add a TargetEntry
+ * containing a "special" Var to the top-level target list, while also adding
+ * adding the Var to the root parent's reltarget, and the information to map
+ * the special Var back to the child Var is stored in this node.  A list of
+ * these nodes is present in PlannerInfo.specialJunkVars.
+ *
+ * The Var is special, because the parent may not actually be able to produce
+ * a value for it by itself.  That is not a problem in practice, because the
+ * actual value for it comes from the child table that introduced such a Var
+ * in the first place.  adjust_appendrel_attrs_mutator() which maps the
+ * parent's Vars to a given child's refers to the mapping information here
+ * to convert a special parent Var to the child Var.
+ */
+typedef struct SpecialJunkVarInfo
+{
+	NodeTag		type;
+
+	/* TargetEntry resname */
+	char	   *attrname;
+
+	/* Child Var info */
+	Oid			vartype;
+	int			vartypmod;
+	Oid			varcollid;
+	AttrNumber	varattno;
+
+	/*
+	 * Special parent attribute number.  If not 0, this starts at
+	 * parent's RelOptInfo.max_attr + 1.
+	 */
+	AttrNumber	special_attno;
+
+	/*
+	 * RT indexes of all child relations sharing a given instance of this
+	 * node.
+	 */
+	Relids		child_relids;
+} SpecialJunkVarInfo;
+
+/*
  * For each distinct placeholder expression generated during planning, we
  * store a PlaceHolderInfo node in the PlannerInfo node's placeholder_list.
  * This stores info that is needed centrally rather than in each copy of the
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 83e0107..909820b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -226,9 +226,10 @@ typedef struct ModifyTable
 	List	   *resultRelations;	/* integer list of RT indexes */
 	int			resultRelIndex; /* index of first resultRel in plan's list */
 	int			rootResultRelIndex; /* index of the partitioned table root */
-	List	   *plans;			/* plan(s) producing source data */
+	List	   *plans;				/* Plan(s) producing source data */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
-	List	   *returningLists; /* per-target-table RETURNING tlists */
+	List	   *returningLists;	/* per-target-table RETURNING tlists */
+	List	   *updateTargetLists;	/* per-target-table tlists */
 	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	Bitmapset  *fdwDirectModifyPlans;	/* indices of FDW DM plans */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a6..697b9d1 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -22,6 +22,8 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel,
 										   Index parentRTindex, Index childRTindex);
 extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
 									int nappinfos, AppendRelInfo **appinfos);
+extern Node *adjust_target_appendrel_attrs(PlannerInfo *root, Node *node,
+									AppendRelInfo *appinfo);
 extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 											   Relids child_relids,
 											   Relids top_parent_relids);
@@ -29,6 +31,7 @@ extern Relids adjust_child_relids(Relids relids, int nappinfos,
 								  AppendRelInfo **appinfos);
 extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 											 Relids child_relids, Relids top_parent_relids);
+extern List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context);
 extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
 											   Relids relids, int *nappinfos);
 
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 3e41710..eb9d4fc 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -152,6 +152,7 @@ extern bool predicate_refuted_by(List *predicate_list, List *clause_list,
 /* in util/tlist.c: */
 
 extern int	count_nonjunk_tlist_entries(List *tlist);
+extern List *filter_junk_tlist_entries(List *tlist);
 extern TargetEntry *get_sortgroupref_tle(Index sortref,
 										 List *targetList);
 extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 715a24a..8f684a0 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -261,7 +261,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												Index nominalRelation, Index rootRelation,
 												bool partColsUpdated,
 												List *resultRelations, List *subpaths,
-												List *subroots,
+												List *subroots, List *updateTargetLists,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
 												int epqParam);
@@ -320,5 +320,4 @@ extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
 										RelOptInfo *outer_rel, RelOptInfo *inner_rel,
 										RelOptInfo *parent_joinrel, List *restrictlist,
 										SpecialJoinInfo *sjinfo, JoinType jointype);
-
 #endif							/* PATHNODE_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a709..18f4968 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,11 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-							  bool inhparent, RelOptInfo *rel);
+							  bool inhparent, RelOptInfo *rel,
+							  RelOptInfo *parent);
+extern void make_result_relation_info(PlannerInfo *root,
+						  Index rti, Relation relation,
+						  ResultRelPlanInfo *parentInfo);
 
 extern List *infer_arbiter_indexes(PlannerInfo *root);
 
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 19c9230..6ee87af 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -16,6 +16,7 @@
 
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
+#include "utils/relcache.h"
 
 
 /*
@@ -35,7 +36,10 @@ extern Relids get_relids_for_join(Query *query, int joinrelid);
  * prototypes for preptlist.c
  */
 extern List *preprocess_targetlist(PlannerInfo *root);
-
+/* XXX not really a "prep" function? */
+extern List *make_update_targetlist(PlannerInfo *root,
+					   Index rti, Relation relation,
+					   List *plan_tlist);
 extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
 
 /*
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 1d4c7da..02af615 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -54,4 +54,6 @@ extern void split_pathtarget_at_srfs(PlannerInfo *root,
 #define create_pathtarget(root, tlist) \
 	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
 
+extern SpecialJunkVarInfo *get_special_junk_var(PlannerInfo *root,
+					Index special_attno);
 #endif							/* TLIST_H */
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef..de7d7c3 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -545,27 +545,25 @@ create table some_tab_child () inherits (some_tab);
 insert into some_tab_child values(1,2);
 explain (verbose, costs off)
 update some_tab set a = a + 1 where false;
-            QUERY PLAN            
-----------------------------------
+                             QUERY PLAN                             
+--------------------------------------------------------------------
  Update on public.some_tab
-   Update on public.some_tab
    ->  Result
-         Output: (a + 1), b, ctid
+         Output: (some_tab.a + 1), some_tab.ctid, some_tab.tableoid
          One-Time Filter: false
-(5 rows)
+(4 rows)
 
 update some_tab set a = a + 1 where false;
 explain (verbose, costs off)
 update some_tab set a = a + 1 where false returning b, a;
-            QUERY PLAN            
-----------------------------------
+                             QUERY PLAN                             
+--------------------------------------------------------------------
  Update on public.some_tab
-   Output: b, a
-   Update on public.some_tab
+   Output: some_tab.b, some_tab.a
    ->  Result
-         Output: (a + 1), b, ctid
+         Output: (some_tab.a + 1), some_tab.ctid, some_tab.tableoid
          One-Time Filter: false
-(6 rows)
+(5 rows)
 
 update some_tab set a = a + 1 where false returning b, a;
  b | a 
@@ -670,7 +668,7 @@ explain update parted_tab set a = 2 where false;
                        QUERY PLAN                       
 --------------------------------------------------------
  Update on parted_tab  (cost=0.00..0.00 rows=0 width=0)
-   ->  Result  (cost=0.00..0.00 rows=0 width=0)
+   ->  Result  (cost=0.00..0.00 rows=0 width=14)
          One-Time Filter: false
 (3 rows)
 
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index b45a590..b1bd9dc 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -1926,37 +1926,27 @@ WHERE EXISTS (
     FROM int4_tbl,
          LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss
     WHERE prt1_l.c IS NULL);
-                          QUERY PLAN                           
----------------------------------------------------------------
+                        QUERY PLAN                        
+----------------------------------------------------------
  Delete on prt1_l
    Delete on prt1_l_p1 prt1_l_1
    Delete on prt1_l_p3_p1 prt1_l_2
    Delete on prt1_l_p3_p2 prt1_l_3
    ->  Nested Loop Semi Join
-         ->  Seq Scan on prt1_l_p1 prt1_l_1
-               Filter: (c IS NULL)
-         ->  Nested Loop
-               ->  Seq Scan on int4_tbl
-               ->  Subquery Scan on ss
-                     ->  Limit
-                           ->  Seq Scan on int8_tbl
-   ->  Nested Loop Semi Join
-         ->  Seq Scan on prt1_l_p3_p1 prt1_l_2
-               Filter: (c IS NULL)
-         ->  Nested Loop
-               ->  Seq Scan on int4_tbl
-               ->  Subquery Scan on ss_1
-                     ->  Limit
-                           ->  Seq Scan on int8_tbl int8_tbl_1
-   ->  Nested Loop Semi Join
-         ->  Seq Scan on prt1_l_p3_p2 prt1_l_3
-               Filter: (c IS NULL)
-         ->  Nested Loop
-               ->  Seq Scan on int4_tbl
-               ->  Subquery Scan on ss_2
-                     ->  Limit
-                           ->  Seq Scan on int8_tbl int8_tbl_2
-(28 rows)
+         ->  Append
+               ->  Seq Scan on prt1_l_p1 prt1_l_1
+                     Filter: (c IS NULL)
+               ->  Seq Scan on prt1_l_p3_p1 prt1_l_2
+                     Filter: (c IS NULL)
+               ->  Seq Scan on prt1_l_p3_p2 prt1_l_3
+                     Filter: (c IS NULL)
+         ->  Materialize
+               ->  Nested Loop
+                     ->  Seq Scan on int4_tbl
+                     ->  Subquery Scan on ss
+                           ->  Limit
+                                 ->  Seq Scan on int8_tbl
+(18 rows)
 
 --
 -- negative testcases
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 4315e8e..a295c08 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2435,74 +2435,43 @@ deallocate ab_q6;
 insert into ab values (1,2);
 explain (analyze, costs off, summary off, timing off)
 update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
-                                     QUERY PLAN                                      
--------------------------------------------------------------------------------------
+                                        QUERY PLAN                                         
+-------------------------------------------------------------------------------------------
  Update on ab_a1 (actual rows=0 loops=1)
    Update on ab_a1_b1 ab_a1_1
    Update on ab_a1_b2 ab_a1_2
    Update on ab_a1_b3 ab_a1_3
-   ->  Nested Loop (actual rows=0 loops=1)
-         ->  Append (actual rows=1 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
-                     Recheck Cond: (a = 1)
-                     Heap Blocks: exact=1
-                     ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
-         ->  Materialize (actual rows=0 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
    ->  Nested Loop (actual rows=1 loops=1)
          ->  Append (actual rows=1 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+               ->  Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
                      Recheck Cond: (a = 1)
                      ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
                            Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
-                     Recheck Cond: (a = 1)
-                     Heap Blocks: exact=1
-                     ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-         ->  Materialize (actual rows=1 loops=1)
                ->  Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
                      Recheck Cond: (a = 1)
                      Heap Blocks: exact=1
                      ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
                            Index Cond: (a = 1)
-   ->  Nested Loop (actual rows=0 loops=1)
-         ->  Append (actual rows=1 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
-                     Recheck Cond: (a = 1)
-                     Heap Blocks: exact=1
-                     ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-         ->  Materialize (actual rows=0 loops=1)
                ->  Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
                      Recheck Cond: (a = 1)
                      ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
                            Index Cond: (a = 1)
-(65 rows)
+         ->  Materialize (actual rows=1 loops=1)
+               ->  Append (actual rows=1 loops=1)
+                     ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+                           Recheck Cond: (a = 1)
+                           ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+                                 Index Cond: (a = 1)
+                     ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
+                           Recheck Cond: (a = 1)
+                           Heap Blocks: exact=1
+                           ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
+                                 Index Cond: (a = 1)
+                     ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+                           Recheck Cond: (a = 1)
+                           ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
+                                 Index Cond: (a = 1)
+(34 rows)
 
 table ab;
  a | b 
@@ -2523,29 +2492,12 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
    Update on ab_a1_b3 ab_a1_3
    InitPlan 1 (returns $0)
      ->  Result (actual rows=1 loops=1)
-   ->  Nested Loop (actual rows=1 loops=1)
-         ->  Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
-         ->  Materialize (actual rows=1 loops=1)
-               ->  Append (actual rows=1 loops=1)
-                     ->  Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
-                           Filter: (b = $0)
-   ->  Nested Loop (actual rows=1 loops=1)
-         ->  Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
-         ->  Materialize (actual rows=1 loops=1)
-               ->  Append (actual rows=1 loops=1)
-                     ->  Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
-                           Filter: (b = $0)
-   ->  Nested Loop (actual rows=1 loops=1)
-         ->  Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
-         ->  Materialize (actual rows=1 loops=1)
+   ->  Nested Loop (actual rows=3 loops=1)
+         ->  Append (actual rows=3 loops=1)
+               ->  Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
+               ->  Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
+               ->  Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
+         ->  Materialize (actual rows=1 loops=3)
                ->  Append (actual rows=1 loops=1)
                      ->  Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
                            Filter: (b = $0)
@@ -2553,7 +2505,7 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
                            Filter: (b = $0)
                      ->  Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
                            Filter: (b = $0)
-(36 rows)
+(19 rows)
 
 select tableoid::regclass, * from ab;
  tableoid | a | b 
@@ -3392,28 +3344,30 @@ explain (costs off) select * from pp_lp where a = 1;
 (5 rows)
 
 explain (costs off) update pp_lp set value = 10 where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Update on pp_lp
    Update on pp_lp1 pp_lp_1
    Update on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 explain (costs off) delete from pp_lp where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Delete on pp_lp
    Delete on pp_lp1 pp_lp_1
    Delete on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 set constraint_exclusion = 'off'; -- this should not affect the result.
 explain (costs off) select * from pp_lp where a = 1;
@@ -3427,28 +3381,30 @@ explain (costs off) select * from pp_lp where a = 1;
 (5 rows)
 
 explain (costs off) update pp_lp set value = 10 where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Update on pp_lp
    Update on pp_lp1 pp_lp_1
    Update on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 explain (costs off) delete from pp_lp where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Delete on pp_lp
    Delete on pp_lp1 pp_lp_1
    Delete on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 drop table pp_lp;
 -- Ensure enable_partition_prune does not affect non-partitioned tables.
@@ -3472,28 +3428,31 @@ explain (costs off) select * from inh_lp where a = 1;
 (5 rows)
 
 explain (costs off) update inh_lp set value = 10 where a = 1;
-             QUERY PLAN             
-------------------------------------
+                   QUERY PLAN                   
+------------------------------------------------
  Update on inh_lp
-   Update on inh_lp
-   Update on inh_lp1 inh_lp_1
-   ->  Seq Scan on inh_lp
-         Filter: (a = 1)
-   ->  Seq Scan on inh_lp1 inh_lp_1
-         Filter: (a = 1)
-(7 rows)
+   Update on inh_lp inh_lp_1
+   Update on inh_lp1 inh_lp_2
+   ->  Result
+         ->  Append
+               ->  Seq Scan on inh_lp inh_lp_1
+                     Filter: (a = 1)
+               ->  Seq Scan on inh_lp1 inh_lp_2
+                     Filter: (a = 1)
+(9 rows)
 
 explain (costs off) delete from inh_lp where a = 1;
-             QUERY PLAN             
-------------------------------------
+                QUERY PLAN                
+------------------------------------------
  Delete on inh_lp
-   Delete on inh_lp
-   Delete on inh_lp1 inh_lp_1
-   ->  Seq Scan on inh_lp
-         Filter: (a = 1)
-   ->  Seq Scan on inh_lp1 inh_lp_1
-         Filter: (a = 1)
-(7 rows)
+   Delete on inh_lp inh_lp_1
+   Delete on inh_lp1 inh_lp_2
+   ->  Append
+         ->  Seq Scan on inh_lp inh_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on inh_lp1 inh_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 -- Ensure we don't exclude normal relations when we only expect to exclude
 -- inheritance children
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 9506aae..b02a682 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1632,19 +1632,21 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2);
 --
 SET SESSION AUTHORIZATION regress_rls_bob;
 EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
-                  QUERY PLAN                   
------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Update on t1
-   Update on t1
-   Update on t2 t1_1
-   Update on t3 t1_2
-   ->  Seq Scan on t1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t2 t1_1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t3 t1_2
-         Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+   Update on t1 t1_1
+   Update on t2 t1_2
+   Update on t3 t1_3
+   ->  Result
+         ->  Append
+               ->  Seq Scan on t1 t1_1
+                     Filter: (((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t2 t1_2
+                     Filter: (((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t3 t1_3
+                     Filter: (((a % 2) = 0) AND f_leak(b))
+(12 rows)
 
 UPDATE t1 SET b = b || b WHERE f_leak(b);
 NOTICE:  f_leak => bbb
@@ -1722,31 +1724,27 @@ NOTICE:  f_leak => cde
 NOTICE:  f_leak => yyyyyy
 EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
-                           QUERY PLAN                            
------------------------------------------------------------------
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
  Update on t1
-   Update on t1
-   Update on t2 t1_1
-   Update on t3 t1_2
-   ->  Nested Loop
-         ->  Seq Scan on t1
-               Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Seq Scan on t2
-               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-   ->  Nested Loop
-         ->  Seq Scan on t2 t1_1
-               Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Seq Scan on t2
-               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+   Update on t1 t1_1
+   Update on t2 t1_2
+   Update on t3 t1_3
    ->  Nested Loop
-         ->  Seq Scan on t3 t1_2
-               Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
          ->  Seq Scan on t2
                Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-(19 rows)
+         ->  Append
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t2 t1_2
+                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t3 t1_3
+                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+(14 rows)
 
 UPDATE t1 SET b=t1.b FROM t2
 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+NOTICE:  f_leak => cde
 EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
                               QUERY PLAN                               
@@ -1795,46 +1793,30 @@ NOTICE:  f_leak => cde
 EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
 WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
 AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
-                              QUERY PLAN                               
------------------------------------------------------------------------
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
  Update on t1 t1_1
-   Update on t1 t1_1
-   Update on t2 t1_1_1
-   Update on t3 t1_1_2
+   Update on t1 t1_1_1
+   Update on t2 t1_1_2
+   Update on t3 t1_1_3
    ->  Nested Loop
          Join Filter: (t1_1.b = t1_2.b)
-         ->  Seq Scan on t1 t1_1
-               Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Append
-               ->  Seq Scan on t1 t1_2_1
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t2 t1_2_2
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t3 t1_2_3
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-   ->  Nested Loop
-         Join Filter: (t1_1_1.b = t1_2.b)
-         ->  Seq Scan on t2 t1_1_1
-               Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
          ->  Append
-               ->  Seq Scan on t1 t1_2_1
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t2 t1_2_2
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t3 t1_2_3
+               ->  Seq Scan on t1 t1_1_1
                      Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-   ->  Nested Loop
-         Join Filter: (t1_1_2.b = t1_2.b)
-         ->  Seq Scan on t3 t1_1_2
-               Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Append
-               ->  Seq Scan on t1 t1_2_1
+               ->  Seq Scan on t2 t1_1_2
                      Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t2 t1_2_2
+               ->  Seq Scan on t3 t1_1_3
                      Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t3 t1_2_3
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-(37 rows)
+         ->  Materialize
+               ->  Append
+                     ->  Seq Scan on t1 t1_2_1
+                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+                     ->  Seq Scan on t2 t1_2_2
+                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+                     ->  Seq Scan on t3 t1_2_3
+                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(21 rows)
 
 UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
 WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
@@ -1843,8 +1825,6 @@ NOTICE:  f_leak => daddad_updt
 NOTICE:  f_leak => daddad_updt
 NOTICE:  f_leak => defdef
 NOTICE:  f_leak => defdef
-NOTICE:  f_leak => daddad_updt
-NOTICE:  f_leak => defdef
  id  | a |      b      | id  | a |      b      |        t1_1         |        t1_2         
 -----+---+-------------+-----+---+-------------+---------------------+---------------------
  104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt)
@@ -1880,19 +1860,20 @@ EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
 (3 rows)
 
 EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
-                  QUERY PLAN                   
------------------------------------------------
+                     QUERY PLAN                      
+-----------------------------------------------------
  Delete on t1
-   Delete on t1
-   Delete on t2 t1_1
-   Delete on t3 t1_2
-   ->  Seq Scan on t1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t2 t1_1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t3 t1_2
-         Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+   Delete on t1 t1_1
+   Delete on t2 t1_2
+   Delete on t3 t1_3
+   ->  Append
+         ->  Seq Scan on t1 t1_1
+               Filter: (((a % 2) = 0) AND f_leak(b))
+         ->  Seq Scan on t2 t1_2
+               Filter: (((a % 2) = 0) AND f_leak(b))
+         ->  Seq Scan on t3 t1_3
+               Filter: (((a % 2) = 0) AND f_leak(b))
+(11 rows)
 
 DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
 NOTICE:  f_leak => bbbbbb_updt
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 5de53f2..05ee269 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1283,12 +1283,12 @@ SELECT * FROM rw_view1;
 (4 rows)
 
 EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *;
-                         QUERY PLAN                          
--------------------------------------------------------------
+                   QUERY PLAN                    
+-------------------------------------------------
  Update on public.base_tbl
    Output: base_tbl.a, base_tbl.b
    ->  Seq Scan on public.base_tbl
-         Output: base_tbl.a, (base_tbl.b + 1), base_tbl.ctid
+         Output: (base_tbl.b + 1), base_tbl.ctid
 (4 rows)
 
 UPDATE rw_view1 SET b = b + 1 RETURNING *;
@@ -1572,26 +1572,21 @@ UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
                                QUERY PLAN                                
 -------------------------------------------------------------------------
  Update on base_tbl_parent
-   Update on base_tbl_parent
-   Update on base_tbl_child base_tbl_parent_1
-   ->  Hash Join
-         Hash Cond: (other_tbl_parent.id = base_tbl_parent.a)
-         ->  Append
-               ->  Seq Scan on other_tbl_parent other_tbl_parent_1
-               ->  Seq Scan on other_tbl_child other_tbl_parent_2
-         ->  Hash
-               ->  Seq Scan on base_tbl_parent
+   Update on base_tbl_parent base_tbl_parent_1
+   Update on base_tbl_child base_tbl_parent_2
    ->  Merge Join
-         Merge Cond: (base_tbl_parent_1.a = other_tbl_parent.id)
+         Merge Cond: (base_tbl_parent.a = other_tbl_parent.id)
          ->  Sort
-               Sort Key: base_tbl_parent_1.a
-               ->  Seq Scan on base_tbl_child base_tbl_parent_1
+               Sort Key: base_tbl_parent.a
+               ->  Append
+                     ->  Seq Scan on base_tbl_parent base_tbl_parent_1
+                     ->  Seq Scan on base_tbl_child base_tbl_parent_2
          ->  Sort
                Sort Key: other_tbl_parent.id
                ->  Append
                      ->  Seq Scan on other_tbl_parent other_tbl_parent_1
                      ->  Seq Scan on other_tbl_child other_tbl_parent_2
-(20 rows)
+(15 rows)
 
 UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
 SELECT * FROM ONLY base_tbl_parent ORDER BY a;
@@ -2301,42 +2296,45 @@ SELECT * FROM v1 WHERE a=8;
 
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
-                                                        QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
+                                                              QUERY PLAN                                                               
+---------------------------------------------------------------------------------------------------------------------------------------
  Update on public.t1
-   Update on public.t1
-   Update on public.t11 t1_1
-   Update on public.t12 t1_2
-   Update on public.t111 t1_3
-   ->  Index Scan using t1_a_idx on public.t1
-         Output: 100, t1.b, t1.c, t1.ctid
-         Index Cond: ((t1.a > 5) AND (t1.a < 7))
-         Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
-         SubPlan 1
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_1
-                       Filter: (t12_1.a = t1.a)
-                 ->  Seq Scan on public.t111 t12_2
-                       Filter: (t12_2.a = t1.a)
-         SubPlan 2
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_4
-                       Output: t12_4.a
-                 ->  Seq Scan on public.t111 t12_5
-                       Output: t12_5.a
-   ->  Index Scan using t11_a_idx on public.t11 t1_1
-         Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
-         Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
-         Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
-   ->  Index Scan using t12_a_idx on public.t12 t1_2
-         Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
-         Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
-         Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
-   ->  Index Scan using t111_a_idx on public.t111 t1_3
-         Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
-         Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
-         Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+   Update on public.t1 t1_1
+   Update on public.t11 t1_2
+   Update on public.t12 t1_3
+   Update on public.t111 t1_4
+   ->  Result
+         Output: 100, t1.ctid, t1.tableoid
+         ->  Append
+               ->  Index Scan using t1_a_idx on public.t1 t1_1
+                     Output: t1_1.ctid, t1_1.tableoid
+                     Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
+                     Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+                     SubPlan 1
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_1
+                                   Filter: (t12_1.a = t1_1.a)
+                             ->  Seq Scan on public.t111 t12_2
+                                   Filter: (t12_2.a = t1_1.a)
+                     SubPlan 2
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_4
+                                   Output: t12_4.a
+                             ->  Seq Scan on public.t111 t12_5
+                                   Output: t12_5.a
+               ->  Index Scan using t11_a_idx on public.t11 t1_2
+                     Output: t1_2.ctid, t1_2.tableoid
+                     Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
+                     Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+               ->  Index Scan using t12_a_idx on public.t12 t1_3
+                     Output: t1_3.ctid, t1_3.tableoid
+                     Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
+                     Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+               ->  Index Scan using t111_a_idx on public.t111 t1_4
+                     Output: t1_4.ctid, t1_4.tableoid
+                     Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
+                     Filter: ((t1_4.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(36 rows)
 
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
 SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@@ -2351,42 +2349,45 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
 
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
-                                               QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
+                                                     QUERY PLAN                                                      
+---------------------------------------------------------------------------------------------------------------------
  Update on public.t1
-   Update on public.t1
-   Update on public.t11 t1_1
-   Update on public.t12 t1_2
-   Update on public.t111 t1_3
-   ->  Index Scan using t1_a_idx on public.t1
-         Output: (t1.a + 1), t1.b, t1.c, t1.ctid
-         Index Cond: ((t1.a > 5) AND (t1.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
-         SubPlan 1
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_1
-                       Filter: (t12_1.a = t1.a)
-                 ->  Seq Scan on public.t111 t12_2
-                       Filter: (t12_2.a = t1.a)
-         SubPlan 2
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_4
-                       Output: t12_4.a
-                 ->  Seq Scan on public.t111 t12_5
-                       Output: t12_5.a
-   ->  Index Scan using t11_a_idx on public.t11 t1_1
-         Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
-         Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
-   ->  Index Scan using t12_a_idx on public.t12 t1_2
-         Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
-         Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
-   ->  Index Scan using t111_a_idx on public.t111 t1_3
-         Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
-         Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+   Update on public.t1 t1_1
+   Update on public.t11 t1_2
+   Update on public.t12 t1_3
+   Update on public.t111 t1_4
+   ->  Result
+         Output: (t1.a + 1), t1.ctid, t1.tableoid
+         ->  Append
+               ->  Index Scan using t1_a_idx on public.t1 t1_1
+                     Output: t1_1.a, t1_1.ctid, t1_1.tableoid
+                     Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+                     SubPlan 1
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_1
+                                   Filter: (t12_1.a = t1_1.a)
+                             ->  Seq Scan on public.t111 t12_2
+                                   Filter: (t12_2.a = t1_1.a)
+                     SubPlan 2
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_4
+                                   Output: t12_4.a
+                             ->  Seq Scan on public.t111 t12_5
+                                   Output: t12_5.a
+               ->  Index Scan using t11_a_idx on public.t11 t1_2
+                     Output: t1_2.a, t1_2.ctid, t1_2.tableoid
+                     Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+               ->  Index Scan using t12_a_idx on public.t12 t1_3
+                     Output: t1_3.a, t1_3.ctid, t1_3.tableoid
+                     Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+               ->  Index Scan using t111_a_idx on public.t111 t1_4
+                     Output: t1_4.a, t1_4.ctid, t1_4.tableoid
+                     Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(36 rows)
 
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
 NOTICE:  snooped value: 8
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..dc34ac6 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -172,14 +172,14 @@ EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE update_test t
   SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a)
   WHERE CURRENT_USER = SESSION_USER;
-                            QUERY PLAN                            
-------------------------------------------------------------------
+                         QUERY PLAN                          
+-------------------------------------------------------------
  Update on public.update_test t
    ->  Result
-         Output: $1, $2, t.c, (SubPlan 1 (returns $1,$2)), t.ctid
+         Output: $1, $2, (SubPlan 1 (returns $1,$2)), t.ctid
          One-Time Filter: (CURRENT_USER = SESSION_USER)
          ->  Seq Scan on public.update_test t
-               Output: t.c, t.a, t.ctid
+               Output: t.a, t.ctid
          SubPlan 1 (returns $1,$2)
            ->  Seq Scan on public.update_test s
                  Output: s.b, s.a
@@ -308,8 +308,8 @@ ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO
 
 -- The order of subplans should be in bound order
 EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
-                   QUERY PLAN                    
--------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Update on range_parted
    Update on part_a_1_a_10 range_parted_1
    Update on part_a_10_a_20 range_parted_2
@@ -318,21 +318,22 @@ EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
    Update on part_d_1_15 range_parted_5
    Update on part_d_15_20 range_parted_6
    Update on part_b_20_b_30 range_parted_7
-   ->  Seq Scan on part_a_1_a_10 range_parted_1
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_a_10_a_20 range_parted_2
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_b_1_b_10 range_parted_3
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_c_1_100 range_parted_4
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_d_1_15 range_parted_5
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_d_15_20 range_parted_6
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_b_20_b_30 range_parted_7
-         Filter: (c > '97'::numeric)
-(22 rows)
+   ->  Append
+         ->  Seq Scan on part_a_1_a_10 range_parted_1
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_a_10_a_20 range_parted_2
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_b_1_b_10 range_parted_3
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_c_1_100 range_parted_4
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_d_1_15 range_parted_5
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_d_15_20 range_parted_6
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_b_20_b_30 range_parted_7
+               Filter: (c > '97'::numeric)
+(23 rows)
 
 -- fail, row movement happens only within the partition subtree.
 UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 67eaeb4..ad793a2 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -2181,47 +2181,35 @@ SELECT * FROM parent;
 EXPLAIN (VERBOSE, COSTS OFF)
 WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
 DELETE FROM a USING wcte WHERE aa = q2;
-                     QUERY PLAN                     
-----------------------------------------------------
+                         QUERY PLAN                         
+------------------------------------------------------------
  Delete on public.a
-   Delete on public.a
-   Delete on public.b a_1
-   Delete on public.c a_2
-   Delete on public.d a_3
+   Delete on public.a a_1
+   Delete on public.b a_2
+   Delete on public.c a_3
+   Delete on public.d a_4
    CTE wcte
      ->  Insert on public.int8_tbl
            Output: int8_tbl.q2
            ->  Result
                  Output: '42'::bigint, '47'::bigint
-   ->  Nested Loop
-         Output: a.ctid, wcte.*
-         Join Filter: (a.aa = wcte.q2)
-         ->  Seq Scan on public.a
-               Output: a.ctid, a.aa
-         ->  CTE Scan on wcte
+   ->  Hash Join
+         Output: a.ctid, wcte.*, a.tableoid
+         Hash Cond: (a.aa = wcte.q2)
+         ->  Append
+               ->  Seq Scan on public.a a_1
+                     Output: a_1.ctid, a_1.aa, a_1.tableoid
+               ->  Seq Scan on public.b a_2
+                     Output: a_2.ctid, a_2.aa, a_2.tableoid
+               ->  Seq Scan on public.c a_3
+                     Output: a_3.ctid, a_3.aa, a_3.tableoid
+               ->  Seq Scan on public.d a_4
+                     Output: a_4.ctid, a_4.aa, a_4.tableoid
+         ->  Hash
                Output: wcte.*, wcte.q2
-   ->  Nested Loop
-         Output: a_1.ctid, wcte.*
-         Join Filter: (a_1.aa = wcte.q2)
-         ->  Seq Scan on public.b a_1
-               Output: a_1.ctid, a_1.aa
-         ->  CTE Scan on wcte
-               Output: wcte.*, wcte.q2
-   ->  Nested Loop
-         Output: a_2.ctid, wcte.*
-         Join Filter: (a_2.aa = wcte.q2)
-         ->  Seq Scan on public.c a_2
-               Output: a_2.ctid, a_2.aa
-         ->  CTE Scan on wcte
-               Output: wcte.*, wcte.q2
-   ->  Nested Loop
-         Output: a_3.ctid, wcte.*
-         Join Filter: (a_3.aa = wcte.q2)
-         ->  Seq Scan on public.d a_3
-               Output: a_3.ctid, a_3.aa
-         ->  CTE Scan on wcte
-               Output: wcte.*, wcte.q2
-(38 rows)
+               ->  CTE Scan on wcte
+                     Output: wcte.*, wcte.q2
+(26 rows)
 
 -- error cases
 -- data-modifying WITH tries to use its own output
-- 
1.8.3.1

