From eb8bd49aa8fefd80a27478be2d56394a400b1e93 Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Tue, 9 Jun 2020 22:14:20 +0900
Subject: [PATCH v4 1/3] Overhaul how updates compute a new tuple

Currently, the planner rewrites the top-level targetlist of an update
statement's parsetree so that it contains entries for all attributes
of the target relation, including for those columns that have not
been changed.  This arrangement means that the executor can take a
tuple that the plan produces, remove any junk attributes in it and
pass it down to the table AM or FDW update API as the new tuple.
It also means that in an inherited update, where there are multiple
target relations, the planner must produce that many plans, because
the targetlists for different target relations may not all look the
same considering that child relations may have different sets of
columns with varying attribute numbers.

This commit revises things so that the planner no longer expands
the parsetree targetlist to include unchanged columns so that the
plan only produces values of the changed columns.  To make the new
tuple to pass to table AM and FDW update API, executor now evaluates
another targetlist matching the target table's TupleDesc which refers
to the plan's output tuple to gets values of the changed columns and
to the old tuple that is refetched for values of unchanged columns.

To get values for unchanged columns to use when forming the new tuple
to pass to ExecForeignUpdate(), we now require foreign scans to
always include the wholerow Var corresponding to the old tuple being
updated, because the unchanged columns are not present in the
plan's targetlist.

As a note to FDW authors, any FDW update planning APIs that look at
the plan's targetlist for checking if it is pushable to remote side
(e.g. PlanDirectModify) should now instead look at "update targetlist"
that is set by the planner in PlannerInfo.update_tlist, because resnos
in the plan's targetlist is no longer indexable by target column's
attribute numbers.

Note that even though the main goal of doing this is to avoid having
to make multiple plans in the inherited update case, this commit does
not touch that subject.  A subsequent commit will change things that
are necessary to make inherited updates work with a single plan.
---
 contrib/postgres_fdw/deparse.c                 |   6 +-
 contrib/postgres_fdw/expected/postgres_fdw.out |  94 +++---
 contrib/postgres_fdw/postgres_fdw.c            |  16 +-
 doc/src/sgml/fdwhandler.sgml                   |   9 +-
 src/backend/commands/trigger.c                 |  16 +-
 src/backend/executor/execMain.c                |   1 -
 src/backend/executor/nodeModifyTable.c         | 418 ++++++++++++++++---------
 src/backend/nodes/copyfuncs.c                  |   1 +
 src/backend/nodes/outfuncs.c                   |   4 +-
 src/backend/nodes/readfuncs.c                  |   1 +
 src/backend/optimizer/plan/createplan.c        |  50 ++-
 src/backend/optimizer/plan/planner.c           |  17 +
 src/backend/optimizer/plan/setrefs.c           |  39 +++
 src/backend/optimizer/prep/preptlist.c         |  47 ++-
 src/backend/optimizer/util/pathnode.c          |   4 +-
 src/backend/optimizer/util/tlist.c             |  19 ++
 src/backend/rewrite/rewriteHandler.c           |  22 +-
 src/include/executor/executor.h                |   5 +
 src/include/nodes/execnodes.h                  |  24 +-
 src/include/nodes/pathnodes.h                  |  14 +-
 src/include/nodes/plannodes.h                  |   1 +
 src/include/optimizer/optimizer.h              |   1 +
 src/include/optimizer/pathnode.h               |   2 +-
 src/test/regress/expected/inherit.out          |  12 +-
 src/test/regress/expected/updatable_views.out  |  26 +-
 src/test/regress/expected/update.out           |   8 +-
 26 files changed, 580 insertions(+), 277 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 2d44df1..ad9f354 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -1809,7 +1809,7 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte,
  * 'rel' is the relation descriptor for the target relation
  * 'foreignrel' is the RelOptInfo for the target relation or the join relation
  *		containing all base relations in the query
- * 'targetlist' is the tlist of the underlying foreign-scan plan node
+ * 'update_tlist' is UPDATE targetlist
  * 'targetAttrs' is the target columns of the UPDATE
  * 'remote_conds' is the qual clauses that must be evaluated remotely
  * '*params_list' is an output list of exprs that will become remote Params
@@ -1821,7 +1821,7 @@ void
 deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
 					   Index rtindex, Relation rel,
 					   RelOptInfo *foreignrel,
-					   List *targetlist,
+					   List *update_tlist,
 					   List *targetAttrs,
 					   List *remote_conds,
 					   List **params_list,
@@ -1854,7 +1854,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
 	foreach(lc, targetAttrs)
 	{
 		int			attnum = lfirst_int(lc);
-		TargetEntry *tle = get_tle_by_resno(targetlist, attnum);
+		TargetEntry *tle = get_tle_by_resno(update_tlist, attnum);
 
 		if (!tle)
 			elog(ERROR, "attribute number %d not found in UPDATE targetlist",
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 10e23d0..eaee767 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -5470,13 +5470,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
@@ -5506,9 +5506,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 *;
@@ -5537,11 +5537,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)
@@ -6231,7 +6231,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)
 
@@ -6245,7 +6245,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)
 
@@ -6319,7 +6319,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)
 
@@ -6334,7 +6334,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)
 
@@ -6651,7 +6651,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)
 
@@ -6884,7 +6884,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)
 
@@ -6908,7 +6908,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)
 
@@ -7218,18 +7218,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
@@ -7242,11 +7242,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
@@ -7286,7 +7286,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
@@ -7300,17 +7300,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
@@ -7484,7 +7484,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)
@@ -7516,9 +7516,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)
 
@@ -7587,10 +7587,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
@@ -7845,7 +7845,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)
 
@@ -7861,13 +7861,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)
 
@@ -7897,7 +7897,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 *;
@@ -7921,20 +7921,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*"
@@ -7970,7 +7970,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)
@@ -7988,19 +7988,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 a31abce..5583fff 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -2144,6 +2144,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 	Relation	rel;
 	StringInfoData sql;
 	ForeignScan *fscan;
+	List	   *update_tlist;
 	List	   *targetAttrs = NIL;
 	List	   *remote_exprs;
 	List	   *params_list = NIL;
@@ -2190,12 +2191,19 @@ postgresPlanDirectModify(PlannerInfo *root,
 
 	/*
 	 * It's unsafe to update a foreign table directly, if any expressions to
-	 * assign to the target columns are unsafe to evaluate remotely.
+	 * assign to the target columns are unsafe to evaluate remotely.  Note
+	 * that we look those expressions up in the result relation's update_tlist,
+	 * not the subplan's targetlist, because the latter cannot be indexed with
+	 * target column attribute numbers, as the resnos of the TLEs contained in
+	 * it don't match with the target columns' attribute numbers.
 	 */
+	update_tlist = root->update_tlist;
 	if (operation == CMD_UPDATE)
 	{
 		int			col;
 
+		Assert(update_tlist != NIL);
+
 		/*
 		 * We transmit only columns that were explicitly targets of the
 		 * UPDATE, so as to avoid unnecessary data transmission.
@@ -2210,10 +2218,10 @@ 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(update_tlist, attno);
 
 			if (!tle)
-				elog(ERROR, "attribute number %d not found in subplan targetlist",
+				elog(ERROR, "attribute number %d not found in UPDATE targetlist",
 					 attno);
 
 			if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr))
@@ -2270,7 +2278,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 		case CMD_UPDATE:
 			deparseDirectUpdateSql(&sql, root, resultRelation, rel,
 								   foreignrel,
-								   ((Plan *) fscan)->targetlist,
+								   update_tlist,
 								   targetAttrs,
 								   remote_exprs, &params_list,
 								   returningList, &retrieved_attrs);
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 72fa127..16a08e7 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -629,9 +629,12 @@ ExecForeignUpdate(EState *estate,
      row-type definition of the foreign table.
      <literal>planSlot</literal> contains the tuple that was generated by the
      <structname>ModifyTable</structname> plan node's subplan; it differs from
-     <literal>slot</literal> in possibly containing additional <quote>junk</quote>
-     columns.  In particular, any junk columns that were requested by
-     <function>AddForeignUpdateTargets</function> will be available from this slot.
+     <literal>slot</literal> in that it contains only the columns changed by
+     the query, so the individual columns in it are not addressable using
+     attribute numbers.  Also, <literal>planSlot</literal> possibly contains
+     additional <quote>junk</quote> columns.  In particular, any junk columns
+     that were requested by <function>AddForeignUpdateTargets</function> will
+     be available from this slot.
     </para>
 
     <para>
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..f4b1982 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2673,20 +2673,22 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 		/*
 		 * In READ COMMITTED isolation level it's possible that target tuple
 		 * was changed due to concurrent update.  In that case we have a raw
-		 * subplan output tuple in epqslot_candidate, and need to run it
-		 * through the junk filter to produce an insertable tuple.
+		 * subplan output tuple in epqslot_candidate, and need to form a new
+		 * insertable tuple using ExecGetUpdateNewTuple to replace the one
+		 * we received in passed-in slot ('newslot').
 		 *
 		 * Caution: more than likely, the passed-in slot is the same as the
-		 * junkfilter's output slot, so we are clobbering the original value
-		 * of slottuple by doing the filtering.  This is OK since neither we
-		 * nor our caller have any more interest in the prior contents of that
-		 * slot.
+		 * slot previously returned by ExecGetUpdateNewTuple(), so we are
+		 * clobbering the original value of that slot.  This is OK since
+		 * neither we nor our caller have any more interest in the prior
+		 * contents of that slot.
 		 */
 		if (epqslot_candidate != NULL)
 		{
 			TupleTableSlot *epqslot_clean;
 
-			epqslot_clean = ExecFilterJunk(relinfo->ri_junkFilter, epqslot_candidate);
+			epqslot_clean = ExecGetUpdateNewTuple(relinfo, epqslot_candidate,
+												  oldslot);
 
 			if (newslot != epqslot_clean)
 				ExecCopySlot(newslot, epqslot_clean);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2e27e26..39fa82d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1316,7 +1316,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 9812089..f82cac3 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 *ExecGetInsertNewTuple(ResultRelInfo *relinfo,
+					  TupleTableSlot *planSlot);
 
 /*
  * Verify that the tuples to be produced by INSERT or UPDATE match the
@@ -1043,6 +1045,53 @@ ldelete:;
 	return NULL;
 }
 
+/*
+ * ExecGetInsertNewTuple
+ *		This prepares a "new" tuple ready to be inserted into given result
+ *		relation by removing any junk columns of the plan's output tuple
+ */
+static TupleTableSlot *
+ExecGetInsertNewTuple(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);
+}
+
+
+/*
+ * ExecGetUpdateNewTuple
+ *		This prepares a "new" tuple by combining an UPDATE plan's output
+ *		tuple which contains values of changed columns and old tuple being
+ *		updated from where values of unchanged columns are taken
+ *
+ * Also, this effectively filters out junk columns in the plan's output
+ * tuple.
+ */
+TupleTableSlot *
+ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+					  TupleTableSlot *planSlot,
+					  TupleTableSlot *oldSlot)
+{
+	ProjectionInfo *newProj = relinfo->ri_projectNew;
+	ExprContext   *econtext;
+
+	Assert(newProj != NULL);
+	Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
+
+	econtext = newProj->pi_exprContext;
+	econtext->ecxt_scantuple = oldSlot;
+	econtext->ecxt_outertuple = planSlot;
+	return ExecProject(newProj);
+}
+
 /* ----------------------------------------------------------------
  *		ExecUpdate
  *
@@ -1062,6 +1111,8 @@ ldelete:;
  *		foreign table triggers; it is NULL when the foreign table has
  *		no relevant triggers.
  *
+ *		oldSlot contains the old tuple being updated.
+ *
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
@@ -1071,6 +1122,7 @@ ExecUpdate(ModifyTableState *mtstate,
 		   HeapTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
+		   TupleTableSlot *oldSlot,
 		   EPQState *epqstate,
 		   EState *estate,
 		   bool canSetTag)
@@ -1269,7 +1321,15 @@ lreplace:;
 					return NULL;
 				else
 				{
-					slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+					/* Fetch the most recent version of old tuple. */
+					ExecClearTuple(oldSlot);
+					if (!table_tuple_fetch_row_version(resultRelationDesc,
+													   tupleid,
+													   SnapshotAny,
+													   oldSlot))
+						elog(ERROR, "failed to fetch tuple being updated");
+					slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
+												 oldSlot);
 					goto lreplace;
 				}
 			}
@@ -1423,7 +1483,15 @@ lreplace:;
 								/* Tuple not passing quals anymore, exiting... */
 								return NULL;
 
-							slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
+							/* Fetch the most recent version of old tuple. */
+							ExecClearTuple(oldSlot);
+							if (!table_tuple_fetch_row_version(resultRelationDesc,
+															   tupleid,
+															   SnapshotAny,
+															   oldSlot))
+								elog(ERROR, "failed to fetch tuple being updated");
+							slot = ExecGetUpdateNewTuple(resultRelInfo,
+														 epqslot, oldSlot);
 							goto lreplace;
 
 						case TM_Deleted:
@@ -1717,7 +1785,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	/* Execute UPDATE with projection */
 	*returning = ExecUpdate(mtstate, conflictTid, NULL,
 							resultRelInfo->ri_onConflict->oc_ProjSlot,
-							planSlot,
+							planSlot, existing,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
 
@@ -2022,7 +2090,6 @@ ExecModifyTable(PlanState *pstate)
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
 	PlanState  *subplanstate;
-	JunkFilter *junkfilter;
 	TupleTableSlot *slot;
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid;
@@ -2065,7 +2132,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 +2176,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 +2230,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;
+			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);
-
-					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 = ExecGetInsertNewTuple(resultRelInfo, planSlot);
 				/* Prepare for tuple routing if needed. */
 				if (proute)
 					slot = ExecPrepareTupleRouting(node, estate, proute,
@@ -2250,8 +2313,36 @@ ExecModifyTable(PlanState *pstate)
 					estate->es_result_relation_info = resultRelInfo;
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
-								  &node->mt_epqstate, estate, node->canSetTag);
+				{
+					TupleTableSlot *oldSlot = resultRelInfo->ri_oldTupleSlot;
+
+					/*
+					 * Make the new tuple by combining plan's output tuple
+					 * with the old tuple being updated.
+					 */
+					ExecClearTuple(oldSlot);
+					if (oldtuple != NULL)
+					{
+						/* Foreign table update. */
+						ExecForceStoreHeapTuple(oldtuple, oldSlot, false);
+					}
+					else
+					{
+						Relation	relation = resultRelInfo->ri_RelationDesc;
+
+						/* Fetch the most recent version of old tuple. */
+						Assert(tupleid != NULL);
+						if (!table_tuple_fetch_row_version(relation, tupleid,
+														   SnapshotAny,
+														   oldSlot))
+							elog(ERROR, "failed to fetch tuple being updated");
+					}
+					slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot,
+												 oldSlot);
+					slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
+									  oldSlot, &node->mt_epqstate, estate,
+									  node->canSetTag);
+				}
 				break;
 			case CMD_DELETE:
 				slot = ExecDelete(node, tupleid, oldtuple, planSlot,
@@ -2631,105 +2722,132 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 						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.
+	 * Initialize projection to project tuples suitable for result relations.
+	 * INSERT queries may need the projection to filter out any junk attrs in
+	 * the tlist.  UPDATE always needs the projection, because it produces
+	 * tuples for a given result relation by combining the subplan tuple,
+	 * which contains values for only the changed columns, with the old tuple
+	 * fetched from the relation from which values for unchanged columns will
+	 * be taken.
 	 *
-	 * 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.
+	 * If there are multiple result relations, each one needs its own
+	 * projection.  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).
 	 */
+	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.  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 (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");
+				/*
+				 * We require it to be present for updates to get the values
+				 * of unchanged columns.
+				 */
+				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 0409a40..fa046f6 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 f038648..c09bb41 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);
@@ -2244,12 +2246,12 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_NODE_FIELD(distinct_pathkeys);
 	WRITE_NODE_FIELD(sort_pathkeys);
 	WRITE_NODE_FIELD(processed_tlist);
+	WRITE_NODE_FIELD(update_tlist);
 	WRITE_NODE_FIELD(minmax_aggs);
 	WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
 	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);
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 3d7a4e3..11c7eb5 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);
@@ -2632,7 +2633,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 	ModifyTable *plan;
 	List	   *subplans = NIL;
 	ListCell   *subpaths,
-			   *subroots;
+			   *subroots,
+			   *lc;
 
 	/* Build the plan for each input path */
 	forboth(subpaths, best_path->subpaths,
@@ -2655,9 +2657,6 @@ 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);
-
 		subplans = lappend(subplans, subplan);
 	}
 
@@ -2672,12 +2671,48 @@ 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);
 
 	copy_generic_path_info(&plan->plan, &best_path->path);
 
+	forboth(lc, subplans,
+			subroots, best_path->subroots)
+	{
+		Plan	   *subplan = (Plan *) lfirst(lc);
+		PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots);
+
+		/*
+		 * Fix up the resnos of query's TLEs to make them match their ordinal
+		 * position in the list, which they may not in the case of an UPDATE.
+		 * It's safe to revise that targetlist now, because nothing after this
+		 * point needs those resnos to match target relation's attribute
+		 * numbers.
+		 * XXX - we do this simply because apply_tlist_labeling() asserts that
+		 * resnos in processed_tlist and resnos in subplan targetlist are
+		 * exactly same, but maybe we can just remove the assert?
+		 */
+		if (plan->operation == CMD_UPDATE)
+		{
+			ListCell   *l;
+			AttrNumber	resno = 1;
+
+			foreach(l, subroot->processed_tlist)
+			{
+				TargetEntry *tle = lfirst(l);
+
+				tle = flatCopyTargetEntry(tle);
+				tle->resno = resno++;
+				lfirst(l) = tle;
+			}
+		}
+
+		/* Transfer resname/resjunk labeling, too, to keep executor happy */
+		apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist);
+	}
+
 	return plan;
 }
 
@@ -6780,6 +6815,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);
@@ -6789,12 +6825,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;
@@ -6839,6 +6876,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 f331f82..bd160fd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -625,6 +625,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	memset(root->upper_rels, 0, sizeof(root->upper_rels));
 	memset(root->upper_targets, 0, sizeof(root->upper_targets));
 	root->processed_tlist = NIL;
+	root->update_tlist = NIL;
 	root->grouping_map = NULL;
 	root->minmax_aggs = NIL;
 	root->qual_security_level = 0;
@@ -1230,6 +1231,7 @@ inheritance_planner(PlannerInfo *root)
 	List	   *withCheckOptionLists = NIL;
 	List	   *returningLists = NIL;
 	List	   *rowMarks;
+	List	   *updateTargetLists = NIL;
 	RelOptInfo *final_rel;
 	ListCell   *lc;
 	ListCell   *lc2;
@@ -1700,6 +1702,12 @@ inheritance_planner(PlannerInfo *root)
 			returningLists = lappend(returningLists,
 									 subroot->parse->returningList);
 
+		if (parse->commandType == CMD_UPDATE)
+		{
+			Assert(subroot->update_tlist != NIL);
+			updateTargetLists = lappend(updateTargetLists,
+										subroot->update_tlist);
+		}
 		Assert(!parse->onConflict);
 	}
 
@@ -1741,6 +1749,13 @@ 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)
+		{
+			Assert(root->update_tlist != NIL);
+			updateTargetLists = lappend(updateTargetLists,
+										root->update_tlist);
+		}
 		/* Disable tuple routing, too, just to be safe */
 		root->partColsUpdated = false;
 	}
@@ -1793,6 +1808,7 @@ inheritance_planner(PlannerInfo *root)
 									 resultRelations,
 									 subpaths,
 									 subroots,
+									 updateTargetLists,
 									 withCheckOptionLists,
 									 returningLists,
 									 rowMarks,
@@ -2377,6 +2393,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										list_make1_int(parse->resultRelation),
 										list_make1(path),
 										list_make1(root),
+										list_make1(root->update_tlist),
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index dd8e2e9..a10fd2d 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -172,6 +172,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);
 
 
 /*****************************************************************************
@@ -873,6 +876,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, 1);
@@ -2880,6 +2886,39 @@ 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, splan->plans)
+	{
+		Index	resultRelation = lfirst_int(lc1);
+		List   *update_tlist = lfirst(lc2);
+		Plan   *subplan = lfirst(lc3);
+		indexed_tlist *itlist;
+
+		itlist = build_tlist_index(subplan->targetlist);
+		lfirst(lc2) = fix_join_expr(root,
+									update_tlist,
+									itlist,
+									NULL,
+									resultRelation,
+									rtoffset,
+									NUM_EXEC_TLIST(subplan));
+	}
+}
+
 
 /*****************************************************************************
  *					QUERY DEPENDENCY MANAGEMENT
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index d56d8c6..1ce5cc2 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -3,13 +3,18 @@
  * preptlist.c
  *	  Routines to preprocess the parse tree target list
  *
- * For INSERT and UPDATE queries, the targetlist must contain an entry for
- * each attribute of the target relation in the correct order.  For UPDATE and
- * DELETE queries, it must also contain junk tlist entries needed to allow the
- * executor to identify the rows to be updated or deleted.  For all query
- * types, we may need to add junk tlist entries for Vars used in the RETURNING
- * list and row ID information needed for SELECT FOR UPDATE locking and/or
- * EvalPlanQual checking.
+ * For INSERT and UPDATE, the targetlist must contain an entry for each
+ * attribute of the target relation in the correct order.  For INSERT, we
+ * modify the query's main targetlist to satisfy that requirement, whereas
+ * in UPDATE's case, we maintain another targetlist that will be computed
+ * separately from the query's main targetlist, leaving the latter to produce
+ * only values for the updated columns as before.
+ *
+ * For UPDATE and DELETE queries, the main targetlist must also contain junk
+ * tlist entries needed to allow the executor to identify the rows to be
+ * updated or deleted.  For all query types, we may need to add junk tlist
+ * entries for Vars used in the RETURNING list and row ID information needed
+ * for SELECT FOR UPDATE locking and/or EvalPlanQual checking.
  *
  * The query rewrite phase also does preprocessing of the targetlist (see
  * rewriteTargetListIU).  The division of labor between here and there is
@@ -52,10 +57,10 @@
 #include "rewrite/rewriteHandler.h"
 #include "utils/rel.h"
 
+static List *make_update_tlist(List *tlist, Index result_relation, Relation rel);
 static List *expand_targetlist(List *tlist, int command_type,
 							   Index result_relation, Relation rel);
 
-
 /*
  * preprocess_targetlist
  *	  Driver for preprocessing the parse tree targetlist.
@@ -111,11 +116,20 @@ 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 INSERT, we fix the the main targetlist itself to produce the full
+	 * tuple.  For UPDATE, we maintain another targetlist that is computed
+	 * separately from the main targetlist to get the full tuple, where any
+	 * missing values are taken from the old tuple.
 	 */
 	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);
+	else if (command_type == CMD_UPDATE)
+		root->update_tlist = make_update_tlist(tlist,
+											   result_relation,
+											   target_relation);
 
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
@@ -240,6 +254,21 @@ preprocess_targetlist(PlannerInfo *root)
 }
 
 
+/*
+ * make_update_tlist
+ * 		Wrapper over expand_targetlist for generating the expanded tlist
+ * 		for an update target relation
+ *
+ * The returned list does not contain any junk entries, only those for
+ * target relation attributes.
+ */
+static List *
+make_update_tlist(List *tlist, Index result_relation, Relation rel)
+{
+	return filter_junk_tlist_entries(expand_targetlist(tlist, CMD_UPDATE,
+													   result_relation, rel));
+}
+
 /*****************************************************************************
  *
  *		TARGETLIST EXPANSION
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index c1fc866..2fee5c5 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3523,6 +3523,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 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)
@@ -3535,7 +3536,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)
@@ -3605,6 +3606,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/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..3b9cb87 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1480,17 +1480,19 @@ 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.
+		 * For UPDATE, we need to let an FDW fetch unchanged columns by
+		 * asking it to fetch the wholerow.  That's because the top-level
+		 * targetlist only contains entries for changed columns.  Actually,
+		 * we only really need this for UPDATEs that are not pushed to the
+		 * remote side, it's hard to tell if that's the case at the point
+		 * when this function is called.
+		 *
+		 * We will need it also if there are any 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 415e117..7150cd7 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -603,4 +603,9 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd);
 extern void CheckSubscriptionRelkind(char relkind, const char *nspname,
 									 const char *relname);
 
+/* needed by trigger.c */
+extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
+						  TupleTableSlot *planSlot,
+						  TupleTableSlot *oldSlot);
+
 #endif							/* EXECUTOR_H  */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ef448d6..8f457f0 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -357,10 +357,6 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  resultSlot:		tuple slot used to hold cleaned tuple.
- *	  junkAttNo:		not used by junkfilter code.  Can be used by caller
- *						to remember the attno of a specific junk attribute
- *						(nodeModifyTable.c keeps the "ctid" or "wholerow"
- *						attno here).
  * ----------------
  */
 typedef struct JunkFilter
@@ -370,7 +366,6 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
 } JunkFilter;
 
 /*
@@ -462,9 +457,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;
 
@@ -488,6 +480,22 @@ typedef struct ResultRelInfo
 
 	/* for use by copy.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
+
+	/*
+	 * For UPDATE and DELETE result relations, this stores the attribute
+	 * number of the junk attribute in the source plan's output tuple.
+	 */
+	AttrNumber		ri_junkAttNo;
+
+	/* Slot to hold the old tuple being updated */
+	TupleTableSlot *ri_oldTupleSlot;
+
+	/*
+	 * Projection to generate new tuple of an UPDATE/INSERT and slot to hold
+	 * that tuple
+	 */
+	TupleTableSlot *ri_newTupleSlot;
+	ProjectionInfo *ri_projectNew;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index dbe86e7..f004d8a 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -315,15 +315,22 @@ struct PlannerInfo
 
 	/*
 	 * The fully-processed targetlist is kept here.  It differs from
-	 * parse->targetList in that (for INSERT and UPDATE) it's been reordered
-	 * to match the target table, and defaults have been filled in.  Also,
-	 * additional resjunk targets may be present.  preprocess_targetlist()
+	 * parse->targetList in that (for INSERT) it's been reordered to match
+	 * the target table, and defaults have been filled in.  Also, additional
+	 * resjunk targets may be present.  preprocess_targetlist() does most of
 	 * does most of this work, but note that more resjunk targets can get
 	 * added during appendrel expansion.  (Hence, upper_targets mustn't get
 	 * set up till after that.)
 	 */
 	List	   *processed_tlist;
 
+	/*
+	 * For UPDATE, this contains the targetlist to compute the updated tuple,
+	 * wherein the updated columns refer to the plan's output tuple (given by
+	 * by 'processed_tlist') and the rest to the old tuple being updated.
+	 */
+	List	   *update_tlist;
+
 	/* Fields filled during create_plan() for use in setrefs.c */
 	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
@@ -1819,6 +1826,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 UPDATE tlists */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 83e0107..360034d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -229,6 +229,7 @@ typedef struct ModifyTable
 	List	   *plans;			/* plan(s) producing source data */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
+	List	   *updateTargetLists;	/* per-target-table UPDATE 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/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 caed1c1..7e08e52 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 *;
@@ -2305,7 +2305,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 (SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a))
          SubPlan 1
@@ -2315,15 +2315,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
                  ->  Seq Scan on public.t111 t12_2
                        Filter: (t12_2.a = t1.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 (SubPlan 1) 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 (SubPlan 1) 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 (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
 (27 rows)
@@ -2341,15 +2341,15 @@ 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
+         Output: (t1.a + 1), t1.ctid
          Index Cond: ((t1.a > 5) AND (t1.a = 8))
          Filter: ((SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a))
          SubPlan 1
@@ -2359,15 +2359,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
                  ->  Seq Scan on public.t111 t12_2
                        Filter: (t12_2.a = t1.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: ((SubPlan 1) 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: ((SubPlan 1) 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: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
 (27 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

