From c43bc74ce375e68621c169942620cb9efaa387d4 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Tue, 9 Jun 2020 22:14:20 +0900
Subject: [PATCH v2 1/2] 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.
However, a full tuple to pass to the table AM API as the new UPDATE
tuple must still be constructed.  To do so, a separate targetlist is
provided which combining the partial tuple produced by the plan and
unchanged values taken from the old tuple, which is refetched after
running the plan.  This targetlist is passed separately in the
ModifyTable node.

With this change, FDW APIs for planning and executing updates should
no longer assume that the plan's output tuple covers all of the target
relation's columns.  Especially, it shouldn't assume that it can use
the target attribute's attribute number to find the TLE that
corresponds to that attribute in the plan's targetlist.

TODO:

 * Remove the hack to use a wholerow Var to construct the full tuple
   to pass to postgres_fdw, because it hasn't been fixed considering
   the above node.
---
 contrib/postgres_fdw/expected/postgres_fdw.out |  94 +++----
 contrib/postgres_fdw/postgres_fdw.c            |   7 +-
 src/backend/commands/trigger.c                 |   5 +-
 src/backend/executor/execMain.c                |   1 -
 src/backend/executor/nodeModifyTable.c         | 358 +++++++++++++++----------
 src/backend/nodes/copyfuncs.c                  |   1 +
 src/backend/nodes/outfuncs.c                   |  19 +-
 src/backend/nodes/readfuncs.c                  |   1 +
 src/backend/optimizer/plan/createplan.c        |   9 +-
 src/backend/optimizer/plan/planner.c           |  66 +++++
 src/backend/optimizer/plan/setrefs.c           |  55 ++++
 src/backend/optimizer/prep/preptlist.c         | 105 +++++++-
 src/backend/optimizer/util/pathnode.c          |   4 +-
 src/backend/optimizer/util/plancat.c           |  57 ++++
 src/backend/optimizer/util/tlist.c             |  19 ++
 src/backend/rewrite/rewriteHandler.c           |  30 ++-
 src/include/executor/executor.h                |   9 +
 src/include/nodes/execnodes.h                  |   9 +-
 src/include/nodes/nodes.h                      |   1 +
 src/include/nodes/pathnodes.h                  |  37 +++
 src/include/nodes/plannodes.h                  |   5 +-
 src/include/optimizer/optimizer.h              |   1 +
 src/include/optimizer/pathnode.h               |   2 +-
 src/include/optimizer/plancat.h                |   2 +
 src/include/optimizer/prep.h                   |   6 +-
 src/test/regress/expected/inherit.out          |  12 +-
 src/test/regress/expected/updatable_views.out  |  22 +-
 src/test/regress/expected/update.out           |   8 +-
 28 files changed, 714 insertions(+), 231 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 82fc129..45efaf7 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -5457,13 +5457,13 @@ UPDATE ft2 AS target SET (c2, c7) = (
         FROM ft2 AS src
         WHERE target.c1 = src.c1
 ) WHERE c1 > 1100;
-                                                                    QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------
+                                                      QUERY PLAN                                                       
+-----------------------------------------------------------------------------------------------------------------------
  Update on public.ft2 target
    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c7 = $3 WHERE ctid = $1
    ->  Foreign Scan on public.ft2 target
-         Output: target.c1, $1, NULL::integer, target.c3, target.c4, target.c5, target.c6, $2, target.c8, (SubPlan 1 (returns $1,$2)), target.ctid
-         Remote SQL: SELECT "C 1", c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE
+         Output: $1, $2, (SubPlan 1 (returns $1,$2)), target.ctid, target.*
+         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE
          SubPlan 1 (returns $1,$2)
            ->  Foreign Scan on public.ft2 src
                  Output: (src.c2 * 10), src.c7
@@ -5493,9 +5493,9 @@ UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
    Output: c1, c2, c3, c4, c5, c6, c7, c8
    Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
    ->  Foreign Scan on public.ft2
-         Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
+         Output: 'bar'::text, ctid, ft2.*
          Filter: (postgres_fdw_abs(ft2.c1) > 2000)
-         Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
+         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
 (7 rows)
 
 UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
@@ -5524,11 +5524,11 @@ UPDATE ft2 SET c3 = 'baz'
    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
    Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
    ->  Nested Loop
-         Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
+         Output: 'baz'::text, ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
          Join Filter: (ft2.c2 === ft4.c1)
          ->  Foreign Scan on public.ft2
-               Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid
-               Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
+               Output: ft2.ctid, ft2.*, ft2.c2
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
          ->  Foreign Scan
                Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
                Relations: (public.ft4) INNER JOIN (public.ft5)
@@ -6218,7 +6218,7 @@ UPDATE rw_view SET b = b + 5;
  Update on public.foreign_tbl
    Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Foreign Scan on public.foreign_tbl
-         Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
+         Output: (foreign_tbl.b + 5), foreign_tbl.ctid, foreign_tbl.*
          Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
 (5 rows)
 
@@ -6232,7 +6232,7 @@ UPDATE rw_view SET b = b + 15;
  Update on public.foreign_tbl
    Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Foreign Scan on public.foreign_tbl
-         Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
+         Output: (foreign_tbl.b + 15), foreign_tbl.ctid, foreign_tbl.*
          Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
 (5 rows)
 
@@ -6306,7 +6306,7 @@ UPDATE rw_view SET b = b + 5;
    Foreign Update on public.foreign_tbl parent_tbl_1
      Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Foreign Scan on public.foreign_tbl parent_tbl_1
-         Output: parent_tbl_1.a, (parent_tbl_1.b + 5), parent_tbl_1.ctid
+         Output: (parent_tbl_1.b + 5), parent_tbl_1.ctid, parent_tbl_1.*
          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
 (6 rows)
 
@@ -6321,7 +6321,7 @@ UPDATE rw_view SET b = b + 15;
    Foreign Update on public.foreign_tbl parent_tbl_1
      Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Foreign Scan on public.foreign_tbl parent_tbl_1
-         Output: parent_tbl_1.a, (parent_tbl_1.b + 15), parent_tbl_1.ctid
+         Output: (parent_tbl_1.b + 15), parent_tbl_1.ctid, parent_tbl_1.*
          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
 (6 rows)
 
@@ -6638,7 +6638,7 @@ UPDATE rem1 set f1 = 10;          -- all columns should be transmitted
  Update on public.rem1
    Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
    ->  Foreign Scan on public.rem1
-         Output: 10, f2, ctid, rem1.*
+         Output: 10, ctid, rem1.*
          Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
 (5 rows)
 
@@ -6871,7 +6871,7 @@ UPDATE rem1 set f2 = '';          -- can't be pushed down
  Update on public.rem1
    Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
    ->  Foreign Scan on public.rem1
-         Output: f1, ''::text, ctid, rem1.*
+         Output: ''::text, ctid, rem1.*
          Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
 (5 rows)
 
@@ -6895,7 +6895,7 @@ UPDATE rem1 set f2 = '';          -- can't be pushed down
  Update on public.rem1
    Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2
    ->  Foreign Scan on public.rem1
-         Output: f1, ''::text, ctid, rem1.*
+         Output: ''::text, ctid, rem1.*
          Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
 (5 rows)
 
@@ -7205,18 +7205,18 @@ select * from bar where f1 in (select f1 from foo) for share;
 -- Check UPDATE with inherited target and an inherited source table
 explain (verbose, costs off)
 update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
-                                           QUERY PLAN                                            
--------------------------------------------------------------------------------------------------
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
  Update on public.bar
    Update on public.bar
    Foreign Update on public.bar2 bar_1
      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
    ->  Hash Join
-         Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
+         Output: (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
          Inner Unique: true
          Hash Cond: (bar.f1 = foo.f1)
          ->  Seq Scan on public.bar
-               Output: bar.f1, bar.f2, bar.ctid
+               Output: bar.f2, bar.ctid, bar.f1
          ->  Hash
                Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                ->  HashAggregate
@@ -7229,11 +7229,11 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
                                  Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid
                                  Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
    ->  Hash Join
-         Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, foo.ctid, foo.*, foo.tableoid
+         Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, foo.ctid, foo.*, foo.tableoid
          Inner Unique: true
          Hash Cond: (bar_1.f1 = foo.f1)
          ->  Foreign Scan on public.bar2 bar_1
-               Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid
+               Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
                Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
          ->  Hash
                Output: foo.ctid, foo.f1, foo.*, foo.tableoid
@@ -7273,7 +7273,7 @@ where bar.f1 = ss.f1;
    Foreign Update on public.bar2 bar_1
      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
    ->  Hash Join
-         Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
+         Output: (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
          Hash Cond: (foo.f1 = bar.f1)
          ->  Append
                ->  Seq Scan on public.foo
@@ -7287,17 +7287,17 @@ where bar.f1 = ss.f1;
                      Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3)
                      Remote SQL: SELECT f1 FROM public.loct1
          ->  Hash
-               Output: bar.f1, bar.f2, bar.ctid
+               Output: bar.f2, bar.ctid, bar.f1
                ->  Seq Scan on public.bar
-                     Output: bar.f1, bar.f2, bar.ctid
+                     Output: bar.f2, bar.ctid, bar.f1
    ->  Merge Join
-         Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, (ROW(foo.f1))
+         Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, (ROW(foo.f1))
          Merge Cond: (bar_1.f1 = foo.f1)
          ->  Sort
-               Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid
+               Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
                Sort Key: bar_1.f1
                ->  Foreign Scan on public.bar2 bar_1
-                     Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid
+                     Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1
                      Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
          ->  Sort
                Output: (ROW(foo.f1)), foo.f1
@@ -7471,7 +7471,7 @@ update bar set f2 = f2 + 100 returning *;
    Update on public.bar
    Foreign Update on public.bar2 bar_1
    ->  Seq Scan on public.bar
-         Output: bar.f1, (bar.f2 + 100), bar.ctid
+         Output: (bar.f2 + 100), bar.ctid
    ->  Foreign Update on public.bar2 bar_1
          Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
 (8 rows)
@@ -7503,9 +7503,9 @@ update bar set f2 = f2 + 100;
    Foreign Update on public.bar2 bar_1
      Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3
    ->  Seq Scan on public.bar
-         Output: bar.f1, (bar.f2 + 100), bar.ctid
+         Output: (bar.f2 + 100), bar.ctid
    ->  Foreign Scan on public.bar2 bar_1
-         Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, bar_1.*
+         Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*
          Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
 (9 rows)
 
@@ -7574,10 +7574,10 @@ update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a re
    Update on public.parent
    Foreign Update on public.remt1 parent_1
    ->  Nested Loop
-         Output: parent.a, (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
+         Output: (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
          Join Filter: (parent.a = remt2.a)
          ->  Seq Scan on public.parent
-               Output: parent.a, parent.b, parent.ctid
+               Output: parent.b, parent.ctid, parent.a
          ->  Foreign Scan on public.remt2
                Output: remt2.b, remt2.*, remt2.a
                Remote SQL: SELECT a, b FROM public.loct2
@@ -7832,7 +7832,7 @@ update utrtest set a = 1 where a = 1 or a = 2 returning *;
    ->  Foreign Update on public.remp utrtest_1
          Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b
    ->  Seq Scan on public.locp utrtest_2
-         Output: 1, utrtest_2.b, utrtest_2.ctid
+         Output: 1, utrtest_2.ctid
          Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
 (9 rows)
 
@@ -7848,13 +7848,13 @@ insert into utrtest values (2, 'qux');
 -- Check case where the foreign partition isn't a subplan target rel
 explain (verbose, costs off)
 update utrtest set a = 1 where a = 2 returning *;
-                   QUERY PLAN                   
-------------------------------------------------
+               QUERY PLAN                
+-----------------------------------------
  Update on public.utrtest
    Output: utrtest_1.a, utrtest_1.b
    Update on public.locp utrtest_1
    ->  Seq Scan on public.locp utrtest_1
-         Output: 1, utrtest_1.b, utrtest_1.ctid
+         Output: 1, utrtest_1.ctid
          Filter: (utrtest_1.a = 2)
 (6 rows)
 
@@ -7884,7 +7884,7 @@ update utrtest set a = 1 returning *;
    ->  Foreign Update on public.remp utrtest_1
          Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b
    ->  Seq Scan on public.locp utrtest_2
-         Output: 1, utrtest_2.b, utrtest_2.ctid
+         Output: 1, utrtest_2.ctid
 (8 rows)
 
 update utrtest set a = 1 returning *;
@@ -7908,20 +7908,20 @@ update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
      Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
    Update on public.locp utrtest_2
    ->  Hash Join
-         Output: 1, utrtest_1.b, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1
+         Output: 1, utrtest_1.ctid, utrtest_1.*, "*VALUES*".*, "*VALUES*".column1
          Hash Cond: (utrtest_1.a = "*VALUES*".column1)
          ->  Foreign Scan on public.remp utrtest_1
-               Output: utrtest_1.b, utrtest_1.ctid, utrtest_1.a
+               Output: utrtest_1.ctid, utrtest_1.*, utrtest_1.a
                Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
          ->  Hash
                Output: "*VALUES*".*, "*VALUES*".column1
                ->  Values Scan on "*VALUES*"
                      Output: "*VALUES*".*, "*VALUES*".column1
    ->  Hash Join
-         Output: 1, utrtest_2.b, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1
+         Output: 1, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1
          Hash Cond: (utrtest_2.a = "*VALUES*".column1)
          ->  Seq Scan on public.locp utrtest_2
-               Output: utrtest_2.b, utrtest_2.ctid, utrtest_2.a
+               Output: utrtest_2.ctid, utrtest_2.a
          ->  Hash
                Output: "*VALUES*".*, "*VALUES*".column1
                ->  Values Scan on "*VALUES*"
@@ -7957,7 +7957,7 @@ update utrtest set a = 3 returning *;
    Update on public.locp utrtest_1
    Foreign Update on public.remp utrtest_2
    ->  Seq Scan on public.locp utrtest_1
-         Output: 3, utrtest_1.b, utrtest_1.ctid
+         Output: 3, utrtest_1.ctid
    ->  Foreign Update on public.remp utrtest_2
          Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b
 (8 rows)
@@ -7975,19 +7975,19 @@ update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
    Foreign Update on public.remp utrtest_2
      Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
    ->  Hash Join
-         Output: 3, utrtest_1.b, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1
+         Output: 3, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1
          Hash Cond: (utrtest_1.a = "*VALUES*".column1)
          ->  Seq Scan on public.locp utrtest_1
-               Output: utrtest_1.b, utrtest_1.ctid, utrtest_1.a
+               Output: utrtest_1.ctid, utrtest_1.a
          ->  Hash
                Output: "*VALUES*".*, "*VALUES*".column1
                ->  Values Scan on "*VALUES*"
                      Output: "*VALUES*".*, "*VALUES*".column1
    ->  Hash Join
-         Output: 3, utrtest_2.b, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1
+         Output: 3, utrtest_2.ctid, utrtest_2.*, "*VALUES*".*, "*VALUES*".column1
          Hash Cond: (utrtest_2.a = "*VALUES*".column1)
          ->  Foreign Scan on public.remp utrtest_2
-               Output: utrtest_2.b, utrtest_2.ctid, utrtest_2.a
+               Output: utrtest_2.ctid, utrtest_2.*, utrtest_2.a
                Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
          ->  Hash
                Output: "*VALUES*".*, "*VALUES*".column1
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 9fc53ca..16c5f19 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -2150,6 +2150,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.
@@ -2166,6 +2167,8 @@ postgresPlanDirectModify(PlannerInfo *root,
 	 * joins needed.
 	 */
 	subplan = (Plan *) list_nth(plan->plans, subplan_index);
+	resultInfo = linitial(root->result_rel_list);
+	Assert(resultInfo != NULL);
 	if (!IsA(subplan, ForeignScan))
 		return false;
 	fscan = (ForeignScan *) subplan;
@@ -2211,7 +2214,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 +2274,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/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/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c47..0a99818 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,65 @@ ldelete:;
 	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 +1330,8 @@ lreplace:;
 					return NULL;
 				else
 				{
-					slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+					slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
+												 tupleid, oldtuple);
 					goto lreplace;
 				}
 			}
@@ -1423,7 +1485,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:
@@ -2022,7 +2086,6 @@ ExecModifyTable(PlanState *pstate)
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
 	PlanState  *subplanstate;
-	JunkFilter *junkfilter;
 	TupleTableSlot *slot;
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid;
@@ -2065,7 +2128,6 @@ ExecModifyTable(PlanState *pstate)
 	/* Preload local variables */
 	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
 	subplanstate = node->mt_plans[node->mt_whichplan];
-	junkfilter = resultRelInfo->ri_junkFilter;
 
 	/*
 	 * es_result_relation_info must point to the currently active result
@@ -2110,7 +2172,6 @@ ExecModifyTable(PlanState *pstate)
 			{
 				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]);
@@ -2165,80 +2226,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;
-				}
+			char		relkind;
+			Datum		datum;
+			bool		isNull;
 
-				/*
-				 * 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);
-
-					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 +2309,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;
@@ -2646,90 +2709,111 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 * This section of code is also a convenient place to verify that the
 	 * output of an INSERT or UPDATE matches the target table(s).
 	 */
+	for (i = 0; i < nplans; i++)
 	{
-		bool		junk_filter_needed = false;
+		List	   *resultTargetList = NIL;
+		bool		need_projection = false;
 
-		switch (operation)
+		resultRelInfo = &mtstate->resultRelInfo[i];
+		subplan = mtstate->mt_plans[i]->plan;
+
+		/*
+		 * Prepare to generate tuples suitable for the target relation.
+		 */
+		if (operation == CMD_INSERT || operation == CMD_UPDATE)
 		{
-			case CMD_INSERT:
+			if (operation == CMD_INSERT)
+			{
 				foreach(l, subplan->targetlist)
 				{
 					TargetEntry *tle = (TargetEntry *) lfirst(l);
 
-					if (tle->resjunk)
-					{
-						junk_filter_needed = true;
-						break;
-					}
+					if (!tle->resjunk)
+						resultTargetList = lappend(resultTargetList, tle);
+					else
+						need_projection = true;
 				}
-				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++)
+			}
+			else
 			{
-				JunkFilter *j;
-				TupleTableSlot *junkresslot;
+				resultTargetList = (List *) list_nth(node->updateTargetLists,
+													 i);
+				need_projection = true;
+			}
 
-				subplan = mtstate->mt_plans[i]->plan;
-				if (operation == CMD_INSERT || operation == CMD_UPDATE)
-					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
-										subplan->targetlist);
+			/*
+			 * The clean list must produce a tuple suitable for the result
+			 * relation.
+			 */
+			ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
+								resultTargetList);
+		}
 
-				junkresslot =
-					ExecInitExtraTupleSlot(estate, NULL,
-										   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
-				j = ExecInitJunkFilter(subplan->targetlist,
-									   junkresslot);
+		if (need_projection)
+		{
+			TupleDesc	relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
 
-				if (operation == CMD_UPDATE || operation == CMD_DELETE)
-				{
-					/* For UPDATE/DELETE, find the appropriate junk attr now */
-					char		relkind;
+			/*
+			 * 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);
+		}
 
-					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");
-					}
-				}
+		/*
+		 * For UPDATE/DELETE, find the appropriate junk attr now.
+		 */
+		if (operation == CMD_UPDATE || operation == CMD_DELETE)
+		{
+			char	relkind;
 
-				resultRelInfo->ri_junkFilter = j;
-				resultRelInfo++;
+			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)
+			{
+				/*
+				 * When there is a row-level trigger, there should be
+				 * a wholerow attribute.
+				 */
+				resultRelInfo->ri_junkAttno =
+					ExecFindJunkAttributeInTlist(subplan->targetlist,
+												 "wholerow");
+				/* HACK: we require it to be present for updates. */
+				if (mtstate->operation == CMD_UPDATE &&
+					!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");
 			}
-		}
-		else
-		{
-			if (operation == CMD_INSERT)
-				ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
-									subplan->targetlist);
 		}
 	}
 
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..cd8dfcf 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,7 @@ _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(rowMarks);
 	WRITE_NODE_FIELD(placeholder_list);
 	WRITE_NODE_FIELD(fkey_list);
@@ -2249,7 +2252,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 +2548,18 @@ _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
 _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
 {
 	WRITE_NODE_TYPE("PLACEHOLDERINFO");
@@ -4148,6 +4162,9 @@ outNode(StringInfo str, const void *obj)
 			case T_AppendRelInfo:
 				_outAppendRelInfo(str, obj);
 				break;
+			case T_ResultRelPlanInfo:
+				_outResultRelPlanInfo(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/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index eb9543f..07fc199 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);
@@ -2690,6 +2691,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,6 +6800,7 @@ 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);
@@ -6807,12 +6810,13 @@ make_modifytable(PlannerInfo *root,
 	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 +6861,7 @@ make_modifytable(PlannerInfo *root,
 	}
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
+	node->updateTargetLists = updateTargetLists;
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5ac7fed..0470888 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -636,6 +636,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		root->wt_param_id = -1;
 	root->non_recursive_path = NULL;
 	root->partColsUpdated = false;
+	root->result_rel_list = NIL;
 
 	/*
 	 * If there is a WITH list, process each WITH query and either convert it
@@ -1231,6 +1232,7 @@ inheritance_planner(PlannerInfo *root)
 	List	   *withCheckOptionLists = NIL;
 	List	   *returningLists = NIL;
 	List	   *rowMarks;
+	List	   *updateTargetLists = NIL;
 	RelOptInfo *final_rel;
 	ListCell   *lc;
 	ListCell   *lc2;
@@ -1484,6 +1486,8 @@ inheritance_planner(PlannerInfo *root)
 		RangeTblEntry *child_rte;
 		RelOptInfo *sub_final_rel;
 		Path	   *subpath;
+		ResultRelPlanInfo *resultInfo;
+		AttrNumber	resno;
 
 		/*
 		 * expand_inherited_rtentry() always processes a parent before any of
@@ -1581,6 +1585,9 @@ inheritance_planner(PlannerInfo *root)
 		subroot->append_rel_list = copyObject(root->append_rel_list);
 		subroot->rowMarks = copyObject(root->rowMarks);
 
+		/* Child result relation's only ResultRelPlanInfo goes here. */
+		subroot->result_rel_list = NIL;
+
 		/*
 		 * If this isn't the first child Query, adjust Vars and jointree
 		 * entries to reference the appropriate set of subquery RTEs.
@@ -1701,6 +1708,27 @@ inheritance_planner(PlannerInfo *root)
 			returningLists = lappend(returningLists,
 									 subroot->parse->returningList);
 
+		Assert(list_length(subroot->result_rel_list) == 1);
+		resultInfo = linitial(subroot->result_rel_list);
+		if (parse->commandType == CMD_UPDATE)
+			updateTargetLists = lappend(updateTargetLists,
+										resultInfo->updateTargetList);
+		root->result_rel_list = lappend(root->result_rel_list, resultInfo);
+
+		/*
+		 * 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, subroot->processed_tlist)
+		{
+			TargetEntry *tle = lfirst(lc);
+
+			tle->resno = resno++;
+		}
+
 		Assert(!parse->onConflict);
 	}
 
@@ -1726,7 +1754,10 @@ inheritance_planner(PlannerInfo *root)
 		Path	   *dummy_path;
 
 		/* tlist processing never got done, either */
+		Assert(root->result_rel_list == NIL);
 		root->processed_tlist = preprocess_targetlist(root);
+		/* that should also have a made a ResultRelPlanInfo for us. */
+		Assert(list_length(root->result_rel_list) == 1);
 		final_rel->reltarget = create_pathtarget(root, root->processed_tlist);
 
 		/* Make a dummy path, cf set_dummy_rel_pathlist() */
@@ -1742,6 +1773,14 @@ inheritance_planner(PlannerInfo *root)
 			withCheckOptionLists = list_make1(parse->withCheckOptions);
 		if (parse->returningList)
 			returningLists = list_make1(parse->returningList);
+		/* ExecInitModifyTable insists that updateTargetList is present. */
+		if (parse->commandType == CMD_UPDATE)
+		{
+			ResultRelPlanInfo *resultInfo;
+
+			resultInfo = linitial(root->result_rel_list);
+			updateTargetLists = list_make1(resultInfo->updateTargetList);
+		}
 		/* Disable tuple routing, too, just to be safe */
 		root->partColsUpdated = false;
 	}
@@ -1794,6 +1833,7 @@ inheritance_planner(PlannerInfo *root)
 									 resultRelations,
 									 subpaths,
 									 subroots,
+									 updateTargetLists,
 									 withCheckOptionLists,
 									 returningLists,
 									 rowMarks,
@@ -2333,6 +2373,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 			List	   *withCheckOptionLists;
 			List	   *returningLists;
 			List	   *rowMarks;
+			List	   *updateTargetLists = NIL;
+			AttrNumber	resno;
 
 			/*
 			 * If target is a partition root table, we need to mark the
@@ -2368,6 +2410,29 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 			else
 				rowMarks = root->rowMarks;
 
+			if (root->result_rel_list)
+			{
+				ResultRelPlanInfo *resultInfo =
+										linitial(root->result_rel_list);
+
+				if (resultInfo->updateTargetList)
+					updateTargetLists = list_make1(resultInfo->updateTargetList);
+			}
+
+			/*
+			 * 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,
@@ -2378,6 +2443,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										list_make1_int(parse->resultRelation),
 										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..2c31a7c 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);
 
 
 /*****************************************************************************
@@ -812,6 +815,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				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);
 
@@ -2694,6 +2700,55 @@ 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,
+			   *lc3;
+
+	forthree(lc1, splan->resultRelations,
+			 lc2, splan->updateTargetLists,
+			 lc3, root->result_rel_list)
+	{
+		Index	resultRel = lfirst_int(lc1);
+		List   *updateTargetList = lfirst(lc2);
+		ResultRelPlanInfo *resultInfo = lfirst(lc3);
+		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/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index d56d8c6..89f7d89 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,12 +112,21 @@ 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 (target_relation && command_type != CMD_INSERT)
+		make_result_relation_info(root, result_relation, target_relation);
+
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
 	 * for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual
@@ -415,6 +425,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/pathnode.c b/src/backend/optimizer/util/pathnode.c
index e845a4b..64da7f5 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)
@@ -3572,6 +3573,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..a9f833a 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"
@@ -2345,3 +2346,59 @@ 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)
+{
+	Query			  *parse = root->parse;
+	ResultRelPlanInfo *resultInfo = makeNode(ResultRelPlanInfo);
+	List   *subplanTargetList;
+	List   *returningList = NIL;
+	List   *updateTargetList = NIL;
+	List   *withCheckOptions = NIL;
+
+	/* 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;
+
+		/* 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);
+}
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..7bef075 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1480,17 +1480,27 @@ 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.
+		 *
+		 * This is a HACK. The previous comment and if condition:
+		 *
+		 * -* 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.
+		 * -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 (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..63e496c 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;
 
 /* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b..173f66d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -269,6 +269,7 @@ typedef enum NodeTag
 	T_PlaceHolderVar,
 	T_SpecialJoinInfo,
 	T_AppendRelInfo,
+	T_ResultRelPlanInfo,
 	T_PlaceHolderInfo,
 	T_MinMaxAggInfo,
 	T_PlannerParamItem,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 485d1b0..3cee3a3 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -218,6 +218,11 @@ 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;
+
 	/*
 	 * 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
@@ -1818,6 +1823,7 @@ typedef struct ModifyTablePath
 	List	   *resultRelations;	/* integer list of RT indexes */
 	List	   *subpaths;		/* Path(s) producing source data */
 	List	   *subroots;		/* per-target-table 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 +2280,37 @@ 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;
+
+/*
  * 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/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..21caac3 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);
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a709..66dbde2 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -27,6 +27,8 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
 							  bool inhparent, RelOptInfo *rel);
+extern void make_result_relation_info(PlannerInfo *root,
+						  Index rti, Relation relation);
 
 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/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 2b68aef..94e43c3 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -545,25 +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: (a + 1), ctid
          One-Time Filter: false
 (5 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
    ->  Result
-         Output: (a + 1), b, ctid
+         Output: (a + 1), ctid
          One-Time Filter: false
 (6 rows)
 
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 5de53f2..13d0d2a 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 *;
@@ -2309,7 +2309,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
    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
+         Output: 100, 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
@@ -2325,15 +2325,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
                  ->  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
+         Output: 100, 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
+         Output: 100, 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
+         Output: 100, 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)
@@ -2359,7 +2359,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
    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
+         Output: (t1.a + 1), 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
@@ -2375,15 +2375,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
                  ->  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
+         Output: (t1_1.a + 1), 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
+         Output: (t1_2.a + 1), 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
+         Output: (t1_3.a + 1), 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)
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index bf939d7..dece036 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
-- 
1.8.3.1

