From 9005f9f47067e48162696b3e0d1054a57b1d9e2b Mon Sep 17 00:00:00 2001
From: amitlan <amitlangote09@gmail.com>
Date: Fri, 29 May 2020 21:49:56 +0900
Subject: [PATCH v3 3/3] Revise how inherited update/delete are handled

Now that we have the ability to maintain and evaluate the targetlist
needed to generate an update's new tuples independently of the plan
which fetches the tuples to be updated, there is no need to make
separate plans for child result relations as inheritance_planner()
currently does.  We generated separate plans before such capability
was present, because that was the only way to generate new tuples of
child relations where each may have its own unique set of columns
(albeit all sharing the set columns present in the root parent).

With this commit, an inherited update/delete query will now be planned
just as a non-inherited one, generating a single plan that goes under
ModifyTable.  The plan for the inherited case is essentially the one
that we get for a select query, although the targetlist additionally
contains junk attributes needed by update/delete.

By going from one plan per result relation to only one shared across
all result relations, the executor now needs a new way to identify the
result relation to direct a given tuple's update/delete to, whereas
before, it could tell that from the plan it is executing.  To that
end, the planner now adds a new junk attribute to the query's
targetlist that for each tuple gives the index of the result relation
in the query's list of result relations.  That is in addition to the
junk attribute that the planner already adds to identify the tuple's
position in a given relation (such as "ctid").

Given the way query planning with inherited tables work where child
relations are not part of the query's jointree and only the root
parent is, there are some challenges that arise in the update/delete
case:

* The junk attributes needed by child result relations need to be
represented as root parent Vars, which is a non-issue for a given
child if what the child needs and what is added for the root parent
are one and the same column.  But considering that that may not
always be the case, more parent Vars might get added to the top-level
targetlist as children are added to the query as result relations.
In some cases, a child relation may use a column that is not present
in the parent (allowed by traditional inheritance) or a non-column
expression, which must be represented using what this patch calls
"fake" parent vars.  These fake parent vars are really only
placeholders for the underlying child relation's column or expression
and don't reach the executor's expression evluation machinery.

* FDWs that are able to push update/delete fully to the remote side
using DirectModify set of APIs now have to go through hoops to
identify the subplan and the UPDATE targetlist to push for child
result relations, because the subplans for individual result
relations are no loger top-level plans.  In fact, if the result
relation is joined to another relation, update/delete cannot be
pushed down at all anymore, whereas before since the child relations
would be present in the main jointree, they could be in the case
where the relation being joined to was present on the same server as
the child result relation.
---
 contrib/postgres_fdw/deparse.c                 |   3 +-
 contrib/postgres_fdw/expected/postgres_fdw.out | 409 ++++--------
 contrib/postgres_fdw/postgres_fdw.c            |  98 ++-
 contrib/postgres_fdw/sql/postgres_fdw.sql      |  62 +-
 doc/src/sgml/fdwhandler.sgml                   |   4 +-
 src/backend/commands/explain.c                 |  15 +-
 src/backend/executor/execPartition.c           |  12 +-
 src/backend/executor/nodeModifyTable.c         | 153 +++--
 src/backend/nodes/copyfuncs.c                  |   3 +-
 src/backend/nodes/equalfuncs.c                 |   1 +
 src/backend/nodes/nodeFuncs.c                  |   4 +-
 src/backend/nodes/outfuncs.c                   |  21 +-
 src/backend/nodes/readfuncs.c                  |   3 +-
 src/backend/optimizer/path/allpaths.c          |  24 +-
 src/backend/optimizer/path/indxpath.c          |   3 +-
 src/backend/optimizer/plan/createplan.c        |  95 +--
 src/backend/optimizer/plan/planner.c           | 842 +++++--------------------
 src/backend/optimizer/plan/setrefs.c           |  78 ++-
 src/backend/optimizer/plan/subselect.c         |  18 +-
 src/backend/optimizer/prep/prepjointree.c      |   1 -
 src/backend/optimizer/prep/preptlist.c         |  12 +-
 src/backend/optimizer/util/appendinfo.c        | 220 ++-----
 src/backend/optimizer/util/inherit.c           | 650 ++++++++++++++++++-
 src/backend/optimizer/util/pathnode.c          |   2 -
 src/backend/optimizer/util/plancat.c           |  19 +-
 src/backend/optimizer/util/relnode.c           |   9 +
 src/backend/utils/adt/ruleutils.c              |   2 +-
 src/include/nodes/execnodes.h                  |  20 +-
 src/include/nodes/nodes.h                      |   1 +
 src/include/nodes/pathnodes.h                  |  73 ++-
 src/include/nodes/plannodes.h                  |   4 +-
 src/include/nodes/primnodes.h                  |  16 +-
 src/include/optimizer/appendinfo.h             |   3 +
 src/include/optimizer/planmain.h               |   6 +
 src/include/optimizer/planner.h                |   2 +
 src/include/optimizer/prep.h                   |   2 +
 src/test/regress/expected/inherit.out          |  22 +-
 src/test/regress/expected/insert_conflict.out  |   2 +-
 src/test/regress/expected/partition_join.out   |  42 +-
 src/test/regress/expected/partition_prune.out  | 199 +++---
 src/test/regress/expected/rowsecurity.out      | 135 ++--
 src/test/regress/expected/updatable_views.out  | 167 ++---
 src/test/regress/expected/update.out           |  35 +-
 src/test/regress/expected/with.out             |  52 +-
 44 files changed, 1703 insertions(+), 1841 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 62b640a..1cef8a4 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -47,6 +47,7 @@
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/planmain.h"
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
@@ -1270,7 +1271,7 @@ deparseLockingClause(deparse_expr_cxt *context)
 		 * that DECLARE CURSOR ... FOR UPDATE is supported, which it isn't
 		 * before 8.3.
 		 */
-		if (relid == root->parse->resultRelation &&
+		if (is_result_relation(relid, root) &&
 			(root->parse->commandType == CMD_UPDATE ||
 			 root->parse->commandType == CMD_DELETE))
 		{
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index a4e0b70..cb0a2d2 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -6327,7 +6327,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.b + 5), parent_tbl_1.ctid, parent_tbl_1.*
+         Output: (parent_tbl_1.b + 5), parent_tbl_1.ctid, 0, parent_tbl_1.*
          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
 (6 rows)
 
@@ -6342,7 +6342,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.b + 15), parent_tbl_1.ctid, parent_tbl_1.*
+         Output: (parent_tbl_1.b + 15), parent_tbl_1.ctid, 0, parent_tbl_1.*
          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
 (6 rows)
 
@@ -7229,33 +7229,19 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
                                       QUERY PLAN                                       
 ---------------------------------------------------------------------------------------
  Update on public.bar
-   Update on public.bar
-   Foreign Update on public.bar2 bar_1
+   Update on public.bar bar_1
+   Foreign Update on public.bar2 bar_2
      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
    ->  Hash Join
-         Output: (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
+         Output: (bar.f2 + 100), bar.ctid, foo.ctid, (0), bar.*, foo.*, foo.tableoid
          Inner Unique: true
          Hash Cond: (bar.f1 = foo.f1)
-         ->  Seq Scan on public.bar
-               Output: bar.f2, bar.ctid, bar.f1
-         ->  Hash
-               Output: foo.ctid, foo.f1, foo.*, foo.tableoid
-               ->  HashAggregate
-                     Output: foo.ctid, foo.f1, foo.*, foo.tableoid
-                     Group Key: foo.f1
-                     ->  Append
-                           ->  Seq Scan on public.foo foo_1
-                                 Output: foo_1.ctid, foo_1.f1, foo_1.*, foo_1.tableoid
-                           ->  Foreign Scan on public.foo2 foo_2
-                                 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.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.f2, bar_1.ctid, bar_1.*, bar_1.f1
-               Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+         ->  Append
+               ->  Seq Scan on public.bar bar_1
+                     Output: bar_1.f2, bar_1.ctid, bar_1.f1, 0, bar_1.*
+               ->  Foreign Scan on public.bar2 bar_2
+                     Output: bar_2.f2, bar_2.ctid, bar_2.f1, 1, bar_2.*
+                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
          ->  Hash
                Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                ->  HashAggregate
@@ -7267,7 +7253,7 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
                            ->  Foreign Scan on public.foo2 foo_2
                                  Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid
                                  Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
-(39 rows)
+(25 rows)
 
 update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
 select tableoid::regclass, * from bar order by 1,2;
@@ -7287,39 +7273,24 @@ update bar set f2 = f2 + 100
 from
   ( select f1 from foo union all select f1+3 from foo ) ss
 where bar.f1 = ss.f1;
-                                      QUERY PLAN                                      
---------------------------------------------------------------------------------------
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
  Update on public.bar
-   Update on public.bar
-   Foreign Update on public.bar2 bar_1
+   Update on public.bar bar_1
+   Foreign Update on public.bar2 bar_2
      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-   ->  Hash Join
-         Output: (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
-         Hash Cond: (foo.f1 = bar.f1)
-         ->  Append
-               ->  Seq Scan on public.foo
-                     Output: ROW(foo.f1), foo.f1
-               ->  Foreign Scan on public.foo2 foo_1
-                     Output: ROW(foo_1.f1), foo_1.f1
-                     Remote SQL: SELECT f1 FROM public.loct1
-               ->  Seq Scan on public.foo foo_2
-                     Output: ROW((foo_2.f1 + 3)), (foo_2.f1 + 3)
-               ->  Foreign Scan on public.foo2 foo_3
-                     Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3)
-                     Remote SQL: SELECT f1 FROM public.loct1
-         ->  Hash
-               Output: bar.f2, bar.ctid, bar.f1
-               ->  Seq Scan on public.bar
-                     Output: bar.f2, bar.ctid, bar.f1
    ->  Merge Join
-         Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, (ROW(foo.f1))
-         Merge Cond: (bar_1.f1 = foo.f1)
+         Output: (bar.f2 + 100), bar.ctid, (ROW(foo.f1)), (0), bar.*
+         Merge Cond: (bar.f1 = foo.f1)
          ->  Sort
-               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.f2, bar_1.ctid, bar_1.*, bar_1.f1
-                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+               Output: bar.f2, bar.ctid, bar.f1, (0), bar.*
+               Sort Key: bar.f1
+               ->  Append
+                     ->  Seq Scan on public.bar bar_1
+                           Output: bar_1.f2, bar_1.ctid, bar_1.f1, 0, bar_1.*
+                     ->  Foreign Scan on public.bar2 bar_2
+                           Output: bar_2.f2, bar_2.ctid, bar_2.f1, 1, bar_2.*
+                           Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
          ->  Sort
                Output: (ROW(foo.f1)), foo.f1
                Sort Key: foo.f1
@@ -7334,7 +7305,7 @@ where bar.f1 = ss.f1;
                      ->  Foreign Scan on public.foo2 foo_3
                            Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3)
                            Remote SQL: SELECT f1 FROM public.loct1
-(45 rows)
+(30 rows)
 
 update bar set f2 = f2 + 100
 from
@@ -7460,18 +7431,19 @@ ERROR:  WHERE CURRENT OF is not supported for this table type
 rollback;
 explain (verbose, costs off)
 delete from foo where f1 < 5 returning *;
-                                   QUERY PLAN                                   
---------------------------------------------------------------------------------
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
  Delete on public.foo
-   Output: foo.f1, foo.f2
-   Delete on public.foo
-   Foreign Delete on public.foo2 foo_1
-   ->  Index Scan using i_foo_f1 on public.foo
-         Output: foo.ctid
-         Index Cond: (foo.f1 < 5)
-   ->  Foreign Delete on public.foo2 foo_1
-         Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
-(9 rows)
+   Output: foo_1.f1, foo_1.f2
+   Delete on public.foo foo_1
+   Foreign Delete on public.foo2 foo_2
+   ->  Append
+         ->  Index Scan using i_foo_f1 on public.foo foo_1
+               Output: foo_1.ctid, 0
+               Index Cond: (foo_1.f1 < 5)
+         ->  Foreign Delete on public.foo2 foo_2
+               Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
+(10 rows)
 
 delete from foo where f1 < 5 returning *;
  f1 | f2 
@@ -7485,17 +7457,20 @@ delete from foo where f1 < 5 returning *;
 
 explain (verbose, costs off)
 update bar set f2 = f2 + 100 returning *;
-                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
  Update on public.bar
-   Output: bar.f1, bar.f2
-   Update on public.bar
-   Foreign Update on public.bar2 bar_1
-   ->  Seq Scan on public.bar
-         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)
+   Output: bar_1.f1, bar_1.f2
+   Update on public.bar bar_1
+   Foreign Update on public.bar2 bar_2
+   ->  Result
+         Output: (bar.f2 + 100), bar.ctid, (0), bar.*
+         ->  Append
+               ->  Seq Scan on public.bar bar_1
+                     Output: bar_1.f2, bar_1.ctid, 0, bar_1.*
+               ->  Foreign Update on public.bar2 bar_2
+                     Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
+(11 rows)
 
 update bar set f2 = f2 + 100 returning *;
  f1 | f2  
@@ -7520,15 +7495,18 @@ update bar set f2 = f2 + 100;
                                                QUERY PLAN                                               
 --------------------------------------------------------------------------------------------------------
  Update on public.bar
-   Update on public.bar
-   Foreign Update on public.bar2 bar_1
+   Update on public.bar bar_1
+   Foreign Update on public.bar2 bar_2
      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.f2 + 100), bar.ctid
-   ->  Foreign Scan on public.bar2 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)
+   ->  Result
+         Output: (bar.f2 + 100), bar.ctid, (0), bar.*
+         ->  Append
+               ->  Seq Scan on public.bar bar_1
+                     Output: bar_1.f2, bar_1.ctid, 0, bar_1.*
+               ->  Foreign Scan on public.bar2 bar_2
+                     Output: bar_2.f2, bar_2.ctid, 1, bar_2.*
+                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
+(12 rows)
 
 update bar set f2 = f2 + 100;
 NOTICE:  trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2
@@ -7545,19 +7523,20 @@ NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
 NOTICE:  OLD: (7,277,77),NEW: (7,377,77)
 explain (verbose, costs off)
 delete from bar where f2 < 400;
-                                         QUERY PLAN                                          
----------------------------------------------------------------------------------------------
+                                            QUERY PLAN                                             
+---------------------------------------------------------------------------------------------------
  Delete on public.bar
-   Delete on public.bar
-   Foreign Delete on public.bar2 bar_1
+   Delete on public.bar bar_1
+   Foreign Delete on public.bar2 bar_2
      Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3
-   ->  Seq Scan on public.bar
-         Output: bar.ctid
-         Filter: (bar.f2 < 400)
-   ->  Foreign Scan on public.bar2 bar_1
-         Output: bar_1.ctid, bar_1.*
-         Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
-(10 rows)
+   ->  Append
+         ->  Seq Scan on public.bar bar_1
+               Output: bar_1.ctid, 0, bar_1.*
+               Filter: (bar_1.f2 < 400)
+         ->  Foreign Scan on public.bar2 bar_2
+               Output: bar_2.ctid, 1, bar_2.*
+               Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
+(11 rows)
 
 delete from bar where f2 < 400;
 NOTICE:  trig_row_before(23, skidoo) BEFORE ROW DELETE ON bar2
@@ -7588,23 +7567,28 @@ analyze remt1;
 analyze remt2;
 explain (verbose, costs off)
 update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
-                                                                  QUERY PLAN                                                                   
------------------------------------------------------------------------------------------------------------------------------------------------
+                                          QUERY PLAN                                          
+----------------------------------------------------------------------------------------------
  Update on public.parent
-   Output: parent.a, parent.b, remt2.a, remt2.b
-   Update on public.parent
-   Foreign Update on public.remt1 parent_1
+   Output: parent_1.a, parent_1.b, remt2.a, remt2.b
+   Update on public.parent parent_1
+   Foreign Update on public.remt1 parent_2
+     Remote SQL: UPDATE public.loct1 SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Nested Loop
-         Output: (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
+         Output: (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b, (0), parent.*
          Join Filter: (parent.a = remt2.a)
-         ->  Seq Scan on public.parent
-               Output: parent.b, parent.ctid, parent.a
-         ->  Foreign Scan on public.remt2
+         ->  Append
+               ->  Seq Scan on public.parent parent_1
+                     Output: parent_1.b, parent_1.ctid, parent_1.a, 0, parent_1.*
+               ->  Foreign Scan on public.remt1 parent_2
+                     Output: parent_2.b, parent_2.ctid, parent_2.a, 1, parent_2.*
+                     Remote SQL: SELECT a, b, ctid FROM public.loct1 FOR UPDATE
+         ->  Materialize
                Output: remt2.b, remt2.*, remt2.a
-               Remote SQL: SELECT a, b FROM public.loct2
-   ->  Foreign Update
-         Remote SQL: UPDATE public.loct1 r4 SET b = (r4.b || r2.b) FROM public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b, r2.a, r2.b
-(14 rows)
+               ->  Foreign Scan on public.remt2
+                     Output: remt2.b, remt2.*, remt2.a
+                     Remote SQL: SELECT a, b FROM public.loct2
+(19 rows)
 
 update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
  a |   b    | a |  b  
@@ -7615,23 +7599,28 @@ update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a re
 
 explain (verbose, costs off)
 delete from parent using remt2 where parent.a = remt2.a returning parent;
-                                                    QUERY PLAN                                                    
-------------------------------------------------------------------------------------------------------------------
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
  Delete on public.parent
-   Output: parent.*
-   Delete on public.parent
-   Foreign Delete on public.remt1 parent_1
+   Output: parent_1.*
+   Delete on public.parent parent_1
+   Foreign Delete on public.remt1 parent_2
+     Remote SQL: DELETE FROM public.loct1 WHERE ctid = $1 RETURNING a, b
    ->  Nested Loop
-         Output: parent.ctid, remt2.*
+         Output: parent.ctid, remt2.*, (0)
          Join Filter: (parent.a = remt2.a)
-         ->  Seq Scan on public.parent
-               Output: parent.ctid, parent.a
-         ->  Foreign Scan on public.remt2
+         ->  Append
+               ->  Seq Scan on public.parent parent_1
+                     Output: parent_1.ctid, parent_1.a, 0
+               ->  Foreign Scan on public.remt1 parent_2
+                     Output: parent_2.ctid, parent_2.a, 1
+                     Remote SQL: SELECT a, ctid FROM public.loct1 FOR UPDATE
+         ->  Materialize
                Output: remt2.*, remt2.a
-               Remote SQL: SELECT a, b FROM public.loct2
-   ->  Foreign Delete
-         Remote SQL: DELETE FROM public.loct1 r4 USING public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b
-(14 rows)
+               ->  Foreign Scan on public.remt2
+                     Output: remt2.*, remt2.a
+                     Remote SQL: SELECT a, b FROM public.loct2
+(19 rows)
 
 delete from parent using remt2 where parent.a = remt2.a returning parent;
    parent   
@@ -7783,13 +7772,11 @@ create table locp (a int check (a in (2)), b text);
 alter table utrtest attach partition remp for values in (1);
 alter table utrtest attach partition locp for values in (2);
 insert into utrtest values (1, 'foo');
-insert into utrtest values (2, 'qux');
 select tableoid::regclass, * FROM utrtest;
  tableoid | a |  b  
 ----------+---+-----
  remp     | 1 | foo
- locp     | 2 | qux
-(2 rows)
+(1 row)
 
 select tableoid::regclass, * FROM remp;
  tableoid | a |  b  
@@ -7798,18 +7785,21 @@ select tableoid::regclass, * FROM remp;
 (1 row)
 
 select tableoid::regclass, * FROM locp;
- tableoid | a |  b  
-----------+---+-----
- locp     | 2 | qux
-(1 row)
+ tableoid | a | b 
+----------+---+---
+(0 rows)
 
 -- It's not allowed to move a row from a partition that is foreign to another
 update utrtest set a = 2 where b = 'foo' returning *;
 ERROR:  new row for relation "loct" violates check constraint "loct_a_check"
 DETAIL:  Failing row contains (2, foo).
 CONTEXT:  remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
--- But the reverse is allowed
+-- But the reverse is allowed provided the target foreign partition is itself
+-- not an UPDATE target
+insert into utrtest values (2, 'qux');
 update utrtest set a = 1 where b = 'qux' returning *;
+ERROR:  cannot route tuples into foreign table to be updated "remp"
+update utrtest set a = 1 where a = 2 returning *;
  a |  b  
 ---+-----
  1 | qux
@@ -7841,32 +7831,6 @@ create trigger loct_br_insert_trigger before insert on loct
 	for each row execute procedure br_insert_trigfunc();
 delete from utrtest;
 insert into utrtest values (2, 'qux');
--- Check case where the foreign partition is a subplan target rel
-explain (verbose, costs off)
-update utrtest set a = 1 where a = 1 or a = 2 returning *;
-                                          QUERY PLAN                                          
-----------------------------------------------------------------------------------------------
- Update on public.utrtest
-   Output: utrtest_1.a, utrtest_1.b
-   Foreign Update on public.remp utrtest_1
-   Update on public.locp utrtest_2
-   ->  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.ctid
-         Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
-(9 rows)
-
--- The new values are concatenated with ' triggered !'
-update utrtest set a = 1 where a = 1 or a = 2 returning *;
- a |        b        
----+-----------------
- 1 | qux triggered !
-(1 row)
-
-delete from utrtest;
-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                
@@ -7875,7 +7839,7 @@ update utrtest set a = 1 where a = 2 returning *;
    Output: utrtest_1.a, utrtest_1.b
    Update on public.locp utrtest_1
    ->  Seq Scan on public.locp utrtest_1
-         Output: 1, utrtest_1.ctid
+         Output: 1, utrtest_1.ctid, 0
          Filter: (utrtest_1.a = 2)
 (6 rows)
 
@@ -7887,137 +7851,6 @@ update utrtest set a = 1 where a = 2 returning *;
 (1 row)
 
 drop trigger loct_br_insert_trigger on loct;
--- We can move rows to a foreign partition that has been updated already,
--- but can't move rows to a foreign partition that hasn't been updated yet
-delete from utrtest;
-insert into utrtest values (1, 'foo');
-insert into utrtest values (2, 'qux');
--- Test the former case:
--- with a direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 1 returning *;
-                           QUERY PLAN                            
------------------------------------------------------------------
- Update on public.utrtest
-   Output: utrtest_1.a, utrtest_1.b
-   Foreign Update on public.remp utrtest_1
-   Update on public.locp utrtest_2
-   ->  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.ctid
-(8 rows)
-
-update utrtest set a = 1 returning *;
- a |  b  
----+-----
- 1 | foo
- 1 | qux
-(2 rows)
-
-delete from utrtest;
-insert into utrtest values (1, 'foo');
-insert into utrtest values (2, 'qux');
--- with a non-direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
-                                    QUERY PLAN                                    
-----------------------------------------------------------------------------------
- Update on public.utrtest
-   Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1
-   Foreign Update on public.remp utrtest_1
-     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.ctid, utrtest_1.*, "*VALUES*".*, "*VALUES*".column1
-         Hash Cond: (utrtest_1.a = "*VALUES*".column1)
-         ->  Foreign Scan on public.remp utrtest_1
-               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.ctid, "*VALUES*".*, "*VALUES*".column1
-         Hash Cond: (utrtest_2.a = "*VALUES*".column1)
-         ->  Seq Scan on public.locp utrtest_2
-               Output: utrtest_2.ctid, utrtest_2.a
-         ->  Hash
-               Output: "*VALUES*".*, "*VALUES*".column1
-               ->  Values Scan on "*VALUES*"
-                     Output: "*VALUES*".*, "*VALUES*".column1
-(24 rows)
-
-update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
- a |  b  | x 
----+-----+---
- 1 | foo | 1
- 1 | qux | 2
-(2 rows)
-
--- Change the definition of utrtest so that the foreign partition get updated
--- after the local partition
-delete from utrtest;
-alter table utrtest detach partition remp;
-drop foreign table remp;
-alter table loct drop constraint loct_a_check;
-alter table loct add check (a in (3));
-create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct');
-alter table utrtest attach partition remp for values in (3);
-insert into utrtest values (2, 'qux');
-insert into utrtest values (3, 'xyzzy');
--- Test the latter case:
--- with a direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 3 returning *;
-                           QUERY PLAN                            
------------------------------------------------------------------
- Update on public.utrtest
-   Output: utrtest_1.a, utrtest_1.b
-   Update on public.locp utrtest_1
-   Foreign Update on public.remp utrtest_2
-   ->  Seq Scan on public.locp utrtest_1
-         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)
-
-update utrtest set a = 3 returning *; -- ERROR
-ERROR:  cannot route tuples into foreign table to be updated "remp"
--- with a non-direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
-                                    QUERY PLAN                                    
-----------------------------------------------------------------------------------
- Update on public.utrtest
-   Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1
-   Update on public.locp utrtest_1
-   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.ctid, "*VALUES*".*, "*VALUES*".column1
-         Hash Cond: (utrtest_1.a = "*VALUES*".column1)
-         ->  Seq Scan on public.locp utrtest_1
-               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.ctid, utrtest_2.*, "*VALUES*".*, "*VALUES*".column1
-         Hash Cond: (utrtest_2.a = "*VALUES*".column1)
-         ->  Foreign Scan on public.remp utrtest_2
-               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
-               ->  Values Scan on "*VALUES*"
-                     Output: "*VALUES*".*, "*VALUES*".column1
-(24 rows)
-
-update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR
-ERROR:  cannot route tuples into foreign table to be updated "remp"
 drop table utrtest;
 drop table loct;
 -- Test copy tuple routing
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 963c2ac..65e72c3 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -1828,7 +1828,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
 									rte,
 									resultRelInfo,
 									mtstate->operation,
-									mtstate->mt_plans[subplan_index]->plan,
+									mtstate->mt_subplan->plan,
 									query,
 									target_attrs,
 									has_returning,
@@ -1941,8 +1941,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
 	 */
 	if (plan && plan->operation == CMD_UPDATE &&
 		(resultRelInfo->ri_usesFdwDirectModify ||
-		 resultRelInfo->ri_FdwState) &&
-		resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
+		 resultRelInfo->ri_FdwState))
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@@ -2128,6 +2127,82 @@ postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot)
 }
 
 /*
+ * modifytable_result_subplan_pushable
+ *		Helper routine for postgresPlanDirectModify to find subplan
+ *		corresponding to subplan_index'th result relation of the given
+ *		ModifyTable node and check if it's pushable, returning true if
+ *		so and setting *subplan_p to thus found subplan
+ *
+ * *subplan_p will be set to NULL if a pushable subplan can't be located.
+ */
+static bool
+modifytable_result_subplan_pushable(PlannerInfo *root,
+									ModifyTable *plan,
+									int subplan_index,
+									Plan **subplan_p)
+{
+	Plan   *subplan = plan->subplan;
+
+	/*
+	 * In a non-inherited update, check the top-level plan itself.
+	 */
+	if (IsA(subplan, ForeignScan))
+	{
+		*subplan_p = subplan;
+		return true;
+	}
+
+	/*
+	 * In an inherited update, unless the result relation is joined to another
+	 * relation, the top-level plan would be an Append/MergeAppend with result
+	 * relation subplans underneath, and in some cases even a Result node on
+	 * top of the Append/MergeAppend.  These nodes atop result relation
+	 * subplans can be ignored as no-op as far determining if the subplan can
+	 * be pushed to remote side is concerned, because their job is to for the
+	 * most part passing the tuples fetched from the subplan along to the
+	 * ModifyTable node which performs the actual update/delete operation.
+	 * It's true that Result node isn't entirely no-op, because it is added
+	 * to compute the query's targetlist, but if the targetlist is pushable,
+	 * it can be safely ignored too.
+	 */
+	if (IsA(subplan, Append))
+	{
+		Append	   *appendplan = (Append *) subplan;
+
+		subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
+	}
+	else if (IsA(plan->subplan, Result) && IsA(outerPlan(subplan), Append))
+	{
+		Append	   *appendplan = (Append *) outerPlan(subplan);
+
+		subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
+	}
+	else if (IsA(subplan, MergeAppend))
+	{
+		MergeAppend	   *maplan = (MergeAppend *) subplan;
+
+		subplan = (Plan *) list_nth(maplan->mergeplans, subplan_index);
+	}
+	else if (IsA(subplan, Result) && IsA(outerPlan(subplan), MergeAppend))
+	{
+		MergeAppend	   *maplan = (MergeAppend *) outerPlan(subplan);
+
+		subplan = (Plan *) list_nth(maplan->mergeplans, subplan_index);
+	}
+
+	if (IsA(subplan, ForeignScan))
+	{
+		*subplan_p = subplan;
+		return true;
+	}
+
+	/* Caller won't use it, but set anyway. */
+	*subplan_p = NULL;
+
+	return false;
+}
+
+/*
  * postgresPlanDirectModify
  *		Consider a direct foreign table modification
  *
@@ -2166,12 +2241,14 @@ postgresPlanDirectModify(PlannerInfo *root,
 		return false;
 
 	/*
-	 * It's unsafe to modify a foreign table directly if there are any local
-	 * joins needed.
+	 * The following checks if the subplan corresponding to this result
+	 * relation is pushable, if so, returns the ForeignScan node for the
+	 * pushable subplan.
 	 */
-	subplan = (Plan *) list_nth(plan->plans, subplan_index);
-	if (!IsA(subplan, ForeignScan))
+	if (!modifytable_result_subplan_pushable(root, plan, subplan_index,
+											 &subplan))
 		return false;
+	Assert(IsA(subplan, ForeignScan));
 	fscan = (ForeignScan *) subplan;
 
 	/*
@@ -2190,6 +2267,11 @@ postgresPlanDirectModify(PlannerInfo *root,
 	}
 	else
 		foreignrel = root->simple_rel_array[resultRelation];
+
+	/* Sanity check. */
+	if (!bms_is_member(resultRelation, foreignrel->relids))
+		elog(ERROR, "invalid subplan for result relation %u", resultRelation);
+
 	rte = root->simple_rte_array[resultRelation];
 	fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
 
@@ -2201,7 +2283,7 @@ postgresPlanDirectModify(PlannerInfo *root,
 	 * 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;
+	update_tlist = get_result_update_tlist(root, resultRelation);
 	if (operation == CMD_UPDATE)
 	{
 		int			col;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index d452d06..c1c5c2f 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -2057,7 +2057,6 @@ alter table utrtest attach partition remp for values in (1);
 alter table utrtest attach partition locp for values in (2);
 
 insert into utrtest values (1, 'foo');
-insert into utrtest values (2, 'qux');
 
 select tableoid::regclass, * FROM utrtest;
 select tableoid::regclass, * FROM remp;
@@ -2066,8 +2065,11 @@ select tableoid::regclass, * FROM locp;
 -- It's not allowed to move a row from a partition that is foreign to another
 update utrtest set a = 2 where b = 'foo' returning *;
 
--- But the reverse is allowed
+-- But the reverse is allowed provided the target foreign partition is itself
+-- not an UPDATE target
+insert into utrtest values (2, 'qux');
 update utrtest set a = 1 where b = 'qux' returning *;
+update utrtest set a = 1 where a = 2 returning *;
 
 select tableoid::regclass, * FROM utrtest;
 select tableoid::regclass, * FROM remp;
@@ -2082,17 +2084,6 @@ create trigger loct_br_insert_trigger before insert on loct
 
 delete from utrtest;
 insert into utrtest values (2, 'qux');
-
--- Check case where the foreign partition is a subplan target rel
-explain (verbose, costs off)
-update utrtest set a = 1 where a = 1 or a = 2 returning *;
--- The new values are concatenated with ' triggered !'
-update utrtest set a = 1 where a = 1 or a = 2 returning *;
-
-delete from utrtest;
-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 *;
 -- The new values are concatenated with ' triggered !'
@@ -2100,51 +2091,6 @@ update utrtest set a = 1 where a = 2 returning *;
 
 drop trigger loct_br_insert_trigger on loct;
 
--- We can move rows to a foreign partition that has been updated already,
--- but can't move rows to a foreign partition that hasn't been updated yet
-
-delete from utrtest;
-insert into utrtest values (1, 'foo');
-insert into utrtest values (2, 'qux');
-
--- Test the former case:
--- with a direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 1 returning *;
-update utrtest set a = 1 returning *;
-
-delete from utrtest;
-insert into utrtest values (1, 'foo');
-insert into utrtest values (2, 'qux');
-
--- with a non-direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
-update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
-
--- Change the definition of utrtest so that the foreign partition get updated
--- after the local partition
-delete from utrtest;
-alter table utrtest detach partition remp;
-drop foreign table remp;
-alter table loct drop constraint loct_a_check;
-alter table loct add check (a in (3));
-create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct');
-alter table utrtest attach partition remp for values in (3);
-insert into utrtest values (2, 'qux');
-insert into utrtest values (3, 'xyzzy');
-
--- Test the latter case:
--- with a direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 3 returning *;
-update utrtest set a = 3 returning *; -- ERROR
-
--- with a non-direct modification plan
-explain (verbose, costs off)
-update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
-update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR
-
 drop table utrtest;
 drop table loct;
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 7c6c48a..c290f58 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -495,8 +495,8 @@ PlanForeignModify(PlannerInfo *root,
      <literal>resultRelation</literal> identifies the target foreign table by its
      range table index.  <literal>subplan_index</literal> identifies which target of
      the <structname>ModifyTable</structname> plan node this is, counting from zero;
-     use this if you want to index into <literal>plan-&gt;plans</literal> or other
-     substructure of the <literal>plan</literal> node.
+     use this if you want to index into per-target-relation substructures of the
+     <literal>plan</literal> node.
     </para>
 
     <para>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c98c9b5..7d3a368 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2077,9 +2077,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	switch (nodeTag(plan))
 	{
 		case T_ModifyTable:
-			ExplainMemberNodes(((ModifyTableState *) planstate)->mt_plans,
-							   ((ModifyTableState *) planstate)->mt_nplans,
-							   ancestors, es);
+			ExplainNode(((ModifyTableState *) planstate)->mt_subplan, ancestors,
+						"Source", NULL, es);
 			break;
 		case T_Append:
 			ExplainMemberNodes(((AppendState *) planstate)->appendplans,
@@ -3679,14 +3678,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 	}
 
 	/* Should we explicitly label target relations? */
-	labeltargets = (mtstate->mt_nplans > 1 ||
-					(mtstate->mt_nplans == 1 &&
+	labeltargets = (mtstate->mt_nrels > 1 ||
+					(mtstate->mt_nrels == 1 &&
 					 mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
 
 	if (labeltargets)
 		ExplainOpenGroup("Target Tables", "Target Tables", false, es);
 
-	for (j = 0; j < mtstate->mt_nplans; j++)
+	for (j = 0; j < mtstate->mt_nrels; j++)
 	{
 		ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
 		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
@@ -3781,10 +3780,10 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
 			double		insert_path;
 			double		other_path;
 
-			InstrEndLoop(mtstate->mt_plans[0]->instrument);
+			InstrEndLoop(mtstate->mt_subplan->instrument);
 
 			/* count the number of source rows */
-			total = mtstate->mt_plans[0]->instrument->ntuples;
+			total = mtstate->mt_subplan->instrument->ntuples;
 			other_path = mtstate->ps.instrument->ntuples2;
 			insert_path = total - other_path;
 
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index bd2ea25..283889a 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -522,12 +522,12 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 	ctl.entrysize = sizeof(SubplanResultRelHashElem);
 	ctl.hcxt = CurrentMemoryContext;
 
-	htab = hash_create("PartitionTupleRouting table", mtstate->mt_nplans,
+	htab = hash_create("PartitionTupleRouting table", mtstate->mt_nrels,
 					   &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
 	proute->subplan_resultrel_htab = htab;
 
 	/* Hash all subplans by their Oid */
-	for (i = 0; i < mtstate->mt_nplans; i++)
+	for (i = 0; i < mtstate->mt_nrels; i++)
 	{
 		ResultRelInfo *rri = &mtstate->resultRelInfo[i];
 		bool		found;
@@ -623,10 +623,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		 */
 		Assert((node->operation == CMD_INSERT &&
 				list_length(node->withCheckOptionLists) == 1 &&
-				list_length(node->plans) == 1) ||
+				list_length(node->resultRelations) == 1) ||
 			   (node->operation == CMD_UPDATE &&
 				list_length(node->withCheckOptionLists) ==
-				list_length(node->plans)));
+				list_length(node->resultRelations)));
 
 		/*
 		 * Use the WCO list of the first plan as a reference to calculate
@@ -683,10 +683,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
 		/* See the comment above for WCO lists. */
 		Assert((node->operation == CMD_INSERT &&
 				list_length(node->returningLists) == 1 &&
-				list_length(node->plans) == 1) ||
+				list_length(node->resultRelations) == 1) ||
 			   (node->operation == CMD_UPDATE &&
 				list_length(node->returningLists) ==
-				list_length(node->plans)));
+				list_length(node->resultRelations)));
 
 		/*
 		 * Use the RETURNING list of the first plan as a reference to
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f6c539d..d367cc1 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1353,7 +1353,7 @@ lreplace:;
 			 * position of the resultRel in mtstate->resultRelInfo[].
 			 */
 			map_index = resultRelInfo - mtstate->resultRelInfo;
-			Assert(map_index >= 0 && map_index < mtstate->mt_nplans);
+			Assert(map_index >= 0 && map_index < mtstate->mt_nrels);
 			tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
 			if (tupconv_map != NULL)
 				slot = execute_attr_map_slot(tupconv_map->attrMap,
@@ -2036,7 +2036,7 @@ ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate)
 	ResultRelInfo *targetRelInfo = getTargetResultRelInfo(mtstate);
 	ResultRelInfo *resultRelInfos = mtstate->resultRelInfo;
 	TupleDesc	outdesc;
-	int			numResultRelInfos = mtstate->mt_nplans;
+	int			numResultRelInfos = mtstate->mt_nrels;
 	int			i;
 
 	/*
@@ -2069,7 +2069,7 @@ tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
 	if (mtstate->mt_per_subplan_tupconv_maps == NULL)
 		ExecSetupChildParentMapForSubplan(mtstate);
 
-	Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
+	Assert(whichplan >= 0 && whichplan < mtstate->mt_nrels);
 	return mtstate->mt_per_subplan_tupconv_maps[whichplan];
 }
 
@@ -2089,6 +2089,7 @@ ExecModifyTable(PlanState *pstate)
 	CmdType		operation = node->operation;
 	ResultRelInfo *saved_resultRelInfo;
 	ResultRelInfo *resultRelInfo;
+	int			resultindex = 0;
 	PlanState  *subplanstate;
 	TupleTableSlot *slot;
 	TupleTableSlot *planSlot;
@@ -2130,8 +2131,8 @@ ExecModifyTable(PlanState *pstate)
 	}
 
 	/* Preload local variables */
-	resultRelInfo = node->resultRelInfo + node->mt_whichplan;
-	subplanstate = node->mt_plans[node->mt_whichplan];
+	resultRelInfo = node->resultRelInfo;
+	subplanstate = node->mt_subplan;
 
 	/*
 	 * es_result_relation_info must point to the currently active result
@@ -2168,41 +2169,49 @@ ExecModifyTable(PlanState *pstate)
 
 		planSlot = ExecProcNode(subplanstate);
 
+		/* No more tuples to process? */
 		if (TupIsNull(planSlot))
+			break;
+
+		/*
+		 * When there are multiple result relations, tuple contains a junk
+		 * column that gives the index of the one from which it came.  Extract
+		 * it and select the result relation.
+		 */
+		if (AttributeNumberIsValid(node->mt_resultIndexAttno))
 		{
-			/* advance to next subplan if any */
-			node->mt_whichplan++;
-			if (node->mt_whichplan < node->mt_nplans)
+			bool	isNull;
+			Datum	datum;
+
+			datum = ExecGetJunkAttribute(planSlot, node->mt_resultIndexAttno,
+										 &isNull);
+			if (isNull)
+				elog(ERROR, "__result_index is NULL");
+			resultindex = DatumGetInt32(datum);
+			Assert(resultindex >= 0 && resultindex < node->mt_nrels);
+			resultRelInfo = node->resultRelInfo + resultindex;
+			estate->es_result_relation_info = resultRelInfo;
+
+			/* Prepare to convert transition tuples from this child. */
+			if (node->mt_transition_capture != NULL)
 			{
-				resultRelInfo++;
-				subplanstate = node->mt_plans[node->mt_whichplan];
-				estate->es_result_relation_info = resultRelInfo;
-				EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
-									node->mt_arowmarks[node->mt_whichplan]);
-				/* Prepare to convert transition tuples from this child. */
-				if (node->mt_transition_capture != NULL)
-				{
-					node->mt_transition_capture->tcs_map =
-						tupconv_map_for_subplan(node, node->mt_whichplan);
-				}
-				if (node->mt_oc_transition_capture != NULL)
-				{
-					node->mt_oc_transition_capture->tcs_map =
-						tupconv_map_for_subplan(node, node->mt_whichplan);
-				}
-				continue;
+				node->mt_transition_capture->tcs_map =
+				tupconv_map_for_subplan(node, resultindex);
+			}
+			if (node->mt_oc_transition_capture != NULL)
+			{
+				node->mt_oc_transition_capture->tcs_map =
+				tupconv_map_for_subplan(node, resultindex);
 			}
-			else
-				break;
 		}
 
 		/*
 		 * Ensure input tuple is the right format for the target relation.
 		 */
-		if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
+		if (node->mt_scans[resultindex]->tts_ops != planSlot->tts_ops)
 		{
-			ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
-			planSlot = node->mt_scans[node->mt_whichplan];
+			ExecCopySlot(node->mt_scans[resultindex], planSlot);
+			planSlot = node->mt_scans[resultindex];
 		}
 
 		/*
@@ -2388,10 +2397,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 {
 	ModifyTableState *mtstate;
 	CmdType		operation = node->operation;
-	int			nplans = list_length(node->plans);
-	ResultRelInfo *saved_resultRelInfo;
+	int			nrels = list_length(node->resultRelations);
 	ResultRelInfo *resultRelInfo;
-	Plan	   *subplan;
+	Plan	   *subplan = node->subplan;
+	TupleDesc	plan_result_type;
 	ListCell   *l;
 	int			i;
 	Relation	rel;
@@ -2412,40 +2421,32 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	mtstate->canSetTag = node->canSetTag;
 	mtstate->mt_done = false;
 
-	mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
 	mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
-	mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
+	mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nrels);
 
 	/* If modifying a partitioned table, initialize the root table info */
 	if (node->rootResultRelIndex >= 0)
 		mtstate->rootResultRelInfo = estate->es_root_result_relations +
 			node->rootResultRelIndex;
 
-	mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
-	mtstate->mt_nplans = nplans;
+	mtstate->mt_nrels = nrels;
 
 	/* set up epqstate with dummy subplan data for the moment */
 	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
 	mtstate->fireBSTriggers = true;
 
 	/*
-	 * call ExecInitNode on each of the plans to be executed and save the
-	 * results into the array "mt_plans".  This is also a convenient place to
-	 * verify that the proposed target relations are valid and open their
-	 * indexes for insertion of new index entries.  Note we *must* set
-	 * estate->es_result_relation_info correctly while we initialize each
-	 * sub-plan; external modules such as FDWs may depend on that (see
-	 * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one
-	 * example).
+	 * Call ExecInitNode on subplan.
 	 */
-	saved_resultRelInfo = estate->es_result_relation_info;
+	mtstate->mt_subplan = ExecInitNode(subplan, estate, eflags);
+	plan_result_type = ExecGetResultType(mtstate->mt_subplan);
 
+	/*
+	 * Per result relation initializations.
+	 */
 	resultRelInfo = mtstate->resultRelInfo;
-	i = 0;
-	foreach(l, node->plans)
+	for (i = 0; i < nrels; i++)
 	{
-		subplan = (Plan *) lfirst(l);
-
 		/* Initialize the usesFdwDirectModify flag */
 		resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
 															  node->fdwDirectModifyPlans);
@@ -2480,11 +2481,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			operation == CMD_UPDATE)
 			update_tuple_routing_needed = true;
 
-		/* Now init the plan for this result rel */
-		estate->es_result_relation_info = resultRelInfo;
-		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 		mtstate->mt_scans[i] =
-			ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
+			ExecInitExtraTupleSlot(mtstate->ps.state, plan_result_type,
 								   table_slot_callbacks(resultRelInfo->ri_RelationDesc));
 
 		/* Also let FDWs init themselves for foreign-table result rels */
@@ -2502,11 +2500,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		}
 
 		resultRelInfo++;
-		i++;
 	}
 
-	estate->es_result_relation_info = saved_resultRelInfo;
-
 	/* Get the target relation */
 	rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc;
 
@@ -2639,8 +2634,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		TupleDesc	relationDesc;
 		TupleDesc	tupDesc;
 
-		/* insert may only have one plan, inheritance is not expanded */
-		Assert(nplans == 1);
+		/* insert may only have one relation, inheritance is not expanded */
+		Assert(nrels == 1);
 
 		/* already exists if created by RETURNING processing above */
 		if (mtstate->ps.ps_ExprContext == NULL)
@@ -2696,30 +2691,20 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	{
 		PlanRowMark *rc = lfirst_node(PlanRowMark, l);
 		ExecRowMark *erm;
+		ExecAuxRowMark *aerm;
 
 		/* ignore "parent" rowmarks; they are irrelevant at runtime */
 		if (rc->isParent)
 			continue;
 
-		/* find ExecRowMark (same for all subplans) */
+		/* Find ExecRowMark and build ExecAuxRowMark */
 		erm = ExecFindRowMark(estate, rc->rti, false);
-
-		/* build ExecAuxRowMark for each subplan */
-		for (i = 0; i < nplans; i++)
-		{
-			ExecAuxRowMark *aerm;
-
-			subplan = mtstate->mt_plans[i]->plan;
-			aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
-			mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm);
-		}
+		aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
+		mtstate->mt_arowmarks = lappend(mtstate->mt_arowmarks, aerm);
 	}
 
-	/* select first subplan */
-	mtstate->mt_whichplan = 0;
-	subplan = (Plan *) linitial(node->plans);
 	EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
-						mtstate->mt_arowmarks[0]);
+						mtstate->mt_arowmarks);
 
 	/*
 	 * Initialize projection to project tuples suitable for result relations.
@@ -2737,13 +2722,12 @@ 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++)
+	for (i = 0; i < nrels; i++)
 	{
 		List	   *resultTargetList = NIL;
 		bool		need_projection = false;
 
 		resultRelInfo = &mtstate->resultRelInfo[i];
-		subplan = mtstate->mt_plans[i]->plan;
 
 		/*
 		 * Prepare to generate tuples suitable for the target relation.
@@ -2852,6 +2836,16 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/*
+	 * If this is an inherited update/delete, there will be a junk attribute
+	 * named "__result_index" present in the subplan's targetlist.  It will be
+	 * used to identify the result relation for a given tuple to be updated/
+	 * deleted.
+	 */
+	mtstate->mt_resultIndexAttno =
+		ExecFindJunkAttributeInTlist(subplan->targetlist, "__result_index");
+	Assert(AttributeNumberIsValid(mtstate->mt_resultIndexAttno) || nrels == 1);
+
+	/*
 	 * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it
 	 * to estate->es_auxmodifytables so that it will be run to completion by
 	 * ExecPostprocessPlan.  (It'd actually work fine to add the primary
@@ -2883,7 +2877,7 @@ ExecEndModifyTable(ModifyTableState *node)
 	/*
 	 * Allow any FDWs to shut down
 	 */
-	for (i = 0; i < node->mt_nplans; i++)
+	for (i = 0; i < node->mt_nrels; i++)
 	{
 		ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
 
@@ -2923,10 +2917,9 @@ ExecEndModifyTable(ModifyTableState *node)
 	EvalPlanQualEnd(&node->mt_epqstate);
 
 	/*
-	 * shut down subplans
+	 * shut down subplan
 	 */
-	for (i = 0; i < node->mt_nplans; i++)
-		ExecEndNode(node->mt_plans[i]);
+	ExecEndNode(node->mt_subplan);
 }
 
 void
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4a32d5f..3303032 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -209,7 +209,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(resultRelations);
 	COPY_SCALAR_FIELD(resultRelIndex);
 	COPY_SCALAR_FIELD(rootResultRelIndex);
-	COPY_NODE_FIELD(plans);
+	COPY_NODE_FIELD(subplan);
 	COPY_NODE_FIELD(withCheckOptionLists);
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(updateTargetLists);
@@ -2372,6 +2372,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
 	COPY_NODE_FIELD(translated_vars);
+	COPY_NODE_FIELD(translated_fake_vars);
 	COPY_SCALAR_FIELD(num_child_cols);
 	COPY_POINTER_FIELD(parent_colnos, from->num_child_cols * sizeof(AttrNumber));
 	COPY_SCALAR_FIELD(parent_reloid);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..e3d8fb1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -904,6 +904,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
 	COMPARE_NODE_FIELD(translated_vars);
+	COMPARE_NODE_FIELD(translated_fake_vars);
 	COMPARE_SCALAR_FIELD(num_child_cols);
 	COMPARE_POINTER_FIELD(parent_colnos, a->num_child_cols * sizeof(AttrNumber));
 	COMPARE_SCALAR_FIELD(parent_reloid);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 9ce8f43..54c4cb4 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3960,9 +3960,7 @@ planstate_tree_walker(PlanState *planstate,
 	switch (nodeTag(plan))
 	{
 		case T_ModifyTable:
-			if (planstate_walk_members(((ModifyTableState *) planstate)->mt_plans,
-									   ((ModifyTableState *) planstate)->mt_nplans,
-									   walker, context))
+			if (walker(((ModifyTableState *) planstate)->mt_subplan, context))
 				return true;
 			break;
 		case T_Append:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2731393..4c192aa 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -410,7 +410,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_INT_FIELD(resultRelIndex);
 	WRITE_INT_FIELD(rootResultRelIndex);
-	WRITE_NODE_FIELD(plans);
+	WRITE_NODE_FIELD(subplan);
 	WRITE_NODE_FIELD(withCheckOptionLists);
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(updateTargetLists);
@@ -2238,6 +2238,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(inherit_result_rels);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_NODE_FIELD(placeholder_list);
 	WRITE_NODE_FIELD(fkey_list);
@@ -2248,6 +2249,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_NODE_FIELD(sort_pathkeys);
 	WRITE_NODE_FIELD(processed_tlist);
 	WRITE_NODE_FIELD(update_tlist);
+	WRITE_NODE_FIELD(inherit_junk_tlist);
 	WRITE_NODE_FIELD(minmax_aggs);
 	WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
 	WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
@@ -2262,6 +2264,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
 	WRITE_BITMAPSET_FIELD(curOuterRels);
 	WRITE_NODE_FIELD(curOuterParams);
 	WRITE_BOOL_FIELD(partColsUpdated);
+	WRITE_INT_FIELD(lastResultRelIndex);
 }
 
 static void
@@ -2543,12 +2546,25 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
 	WRITE_NODE_FIELD(translated_vars);
+	WRITE_NODE_FIELD(translated_fake_vars);
 	WRITE_INT_FIELD(num_child_cols);
 	WRITE_ATTRNUMBER_ARRAY(parent_colnos, node->num_child_cols);
 	WRITE_OID_FIELD(parent_reloid);
 }
 
 static void
+_outInheritResultRelInfo(StringInfo str, const InheritResultRelInfo *node)
+{
+	WRITE_NODE_TYPE("INHERITRESULTRELINFO");
+
+	WRITE_UINT_FIELD(resultRelation);
+	WRITE_NODE_FIELD(withCheckOptions);
+	WRITE_NODE_FIELD(returningList);
+	WRITE_NODE_FIELD(processed_tlist);
+	WRITE_NODE_FIELD(update_tlist);
+}
+
+static void
 _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
 {
 	WRITE_NODE_TYPE("PLACEHOLDERINFO");
@@ -4151,6 +4167,9 @@ outNode(StringInfo str, const void *obj)
 			case T_AppendRelInfo:
 				_outAppendRelInfo(str, obj);
 				break;
+			case T_InheritResultRelInfo:
+				_outInheritResultRelInfo(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 872cf10..a472af2 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1363,6 +1363,7 @@ _readAppendRelInfo(void)
 	READ_OID_FIELD(parent_reltype);
 	READ_OID_FIELD(child_reltype);
 	READ_NODE_FIELD(translated_vars);
+	READ_NODE_FIELD(translated_fake_vars);
 	READ_INT_FIELD(num_child_cols);
 	READ_ATTRNUMBER_ARRAY(parent_colnos, local_node->num_child_cols);
 	READ_OID_FIELD(parent_reloid);
@@ -1641,7 +1642,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(resultRelations);
 	READ_INT_FIELD(resultRelIndex);
 	READ_INT_FIELD(rootResultRelIndex);
-	READ_NODE_FIELD(plans);
+	READ_NODE_FIELD(subplan);
 	READ_NODE_FIELD(withCheckOptionLists);
 	READ_NODE_FIELD(returningLists);
 	READ_NODE_FIELD(updateTargetLists);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index b399592..467e455 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -39,6 +39,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/plancat.h"
+#include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
@@ -1060,10 +1061,25 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			adjust_appendrel_attrs(root,
 								   (Node *) rel->joininfo,
 								   1, &appinfo);
-		childrel->reltarget->exprs = (List *)
-			adjust_appendrel_attrs(root,
-								   (Node *) rel->reltarget->exprs,
-								   1, &appinfo);
+
+		/*
+		 * If the child is a result relation, the executor expects that any
+		 * wholerow Vars in the targetlist are of its reltype, not parent's
+		 * reltype.  So use adjust_target_appendrel_attrs() to translate the
+		 * reltarget expressions, because it does not wrap a translated
+		 * wholerow Var with ConcertRowtypeExpr to convert it back to the
+		 * parent's reltype.
+		 */
+		if (is_result_relation(childRTindex, root))
+			childrel->reltarget->exprs = (List *)
+				adjust_target_appendrel_attrs(root,
+											  (Node *) rel->reltarget->exprs,
+											  appinfo);
+		else
+			childrel->reltarget->exprs = (List *)
+				adjust_appendrel_attrs(root,
+									   (Node *) rel->reltarget->exprs,
+									   1, &appinfo);
 
 		/*
 		 * We have to make child entries in the EquivalenceClass data
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index bcb1bc6..ebde791 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -30,6 +30,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
+#include "optimizer/planmain.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "utils/lsyscache.h"
@@ -3386,7 +3387,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
 	 * and pass them through to EvalPlanQual via a side channel; but for now,
 	 * we just don't remove implied quals at all for target relations.
 	 */
-	is_target_rel = (rel->relid == root->parse->resultRelation ||
+	is_target_rel = (is_result_relation(rel->relid, root) ||
 					 get_plan_rowmark(root->rowMarks, rel->relid) != NULL);
 
 	/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 1913217..5dcac0d 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -295,7 +295,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
 									 CmdType operation, bool canSetTag,
 									 Index nominalRelation, Index rootRelation,
 									 bool partColsUpdated,
-									 List *resultRelations, List *subplans, List *subroots,
+									 List *resultRelations, Plan *subplan,
 									 List *withCheckOptionLists, List *returningLists,
 									 List *updateTargetLists,
 									 List *rowMarks, OnConflictExpr *onconflict, int epqParam);
@@ -2247,7 +2247,6 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
 	 * create_modifytable_plan).  Fortunately we can't be because there would
 	 * never be grouping in an UPDATE/DELETE; but let's Assert that.
 	 */
-	Assert(root->inhTargetKind == INHKIND_NONE);
 	Assert(root->grouping_map == NULL);
 	root->grouping_map = grouping_map;
 
@@ -2409,12 +2408,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
 	 * with InitPlan output params.  (We can't just do that locally in the
 	 * MinMaxAgg node, because path nodes above here may have Agg references
 	 * as well.)  Save the mmaggregates list to tell setrefs.c to do that.
-	 *
-	 * This doesn't work if we're in an inheritance subtree (see notes in
-	 * create_modifytable_plan).  Fortunately we can't be because there would
-	 * never be aggregates in an UPDATE/DELETE; but let's Assert that.
 	 */
-	Assert(root->inhTargetKind == INHKIND_NONE);
 	Assert(root->minmax_aggs == NIL);
 	root->minmax_aggs = best_path->mmaggregates;
 
@@ -2631,34 +2625,11 @@ static ModifyTable *
 create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 {
 	ModifyTable *plan;
-	List	   *subplans = NIL;
-	ListCell   *subpaths,
-			   *subroots,
-			   *lc;
-
-	/* Build the plan for each input path */
-	forboth(subpaths, best_path->subpaths,
-			subroots, best_path->subroots)
-	{
-		Path	   *subpath = (Path *) lfirst(subpaths);
-		PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots);
-		Plan	   *subplan;
-
-		/*
-		 * In an inherited UPDATE/DELETE, reference the per-child modified
-		 * subroot while creating Plans from Paths for the child rel.  This is
-		 * a kluge, but otherwise it's too hard to ensure that Plan creation
-		 * functions (particularly in FDWs) don't depend on the contents of
-		 * "root" matching what they saw at Path creation time.  The main
-		 * downside is that creation functions for Plans that might appear
-		 * below a ModifyTable cannot expect to modify the contents of "root"
-		 * and have it "stick" for subsequent processing such as setrefs.c.
-		 * That's not great, but it seems better than the alternative.
-		 */
-		subplan = create_plan_recurse(subroot, subpath, CP_EXACT_TLIST);
+	Path	   *subpath = (Path *) linitial(best_path->subpaths);
+	Plan	   *subplan;
 
-		subplans = lappend(subplans, subplan);
-	}
+	/* Build the plan. */
+	subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
 
 	plan = make_modifytable(root,
 							best_path->operation,
@@ -2667,8 +2638,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->rootRelation,
 							best_path->partColsUpdated,
 							best_path->resultRelations,
-							subplans,
-							best_path->subroots,
+							subplan,
 							best_path->withCheckOptionLists,
 							best_path->returningLists,
 							best_path->updateTargetLists,
@@ -2678,11 +2648,10 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 
 	copy_generic_path_info(&plan->plan, &best_path->path);
 
-	forboth(lc, subplans,
-			subroots, best_path->subroots)
+	if (plan->operation == CMD_UPDATE)
 	{
-		Plan	   *subplan = (Plan *) lfirst(lc);
-		PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots);
+		ListCell   *l;
+		AttrNumber	resno = 1;
 
 		/*
 		 * Fix up the resnos of query's TLEs to make them match their ordinal
@@ -2694,25 +2663,19 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 		 * 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)
+		foreach(l, root->processed_tlist)
 		{
-			ListCell   *l;
-			AttrNumber	resno = 1;
-
-			foreach(l, subroot->processed_tlist)
-			{
-				TargetEntry *tle = lfirst(l);
+			TargetEntry *tle = lfirst(l);
 
-				tle = flatCopyTargetEntry(tle);
-				tle->resno = resno++;
-				lfirst(l) = tle;
-			}
+			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);
 	}
 
+	/* Transfer resname/resjunk labeling, too, to keep executor happy */
+	apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
+
 	return plan;
 }
 
@@ -6815,7 +6778,7 @@ make_modifytable(PlannerInfo *root,
 				 CmdType operation, bool canSetTag,
 				 Index nominalRelation, Index rootRelation,
 				 bool partColsUpdated,
-				 List *resultRelations, List *subplans, List *subroots,
+				 List *resultRelations, Plan *subplan,
 				 List *withCheckOptionLists, List *returningLists,
 				 List *updateTargetLists,
 				 List *rowMarks, OnConflictExpr *onconflict, int epqParam)
@@ -6824,7 +6787,6 @@ make_modifytable(PlannerInfo *root,
 	List	   *fdw_private_list;
 	Bitmapset  *direct_modify_plans;
 	ListCell   *lc;
-	ListCell   *lc2;
 	int			i;
 
 	Assert(withCheckOptionLists == NIL ||
@@ -6849,7 +6811,7 @@ make_modifytable(PlannerInfo *root,
 	node->resultRelations = resultRelations;
 	node->resultRelIndex = -1;	/* will be set correctly in setrefs.c */
 	node->rootResultRelIndex = -1;	/* will be set correctly in setrefs.c */
-	node->plans = subplans;
+	node->subplan = subplan;
 	if (!onconflict)
 	{
 		node->onConflictAction = ONCONFLICT_NONE;
@@ -6889,10 +6851,9 @@ make_modifytable(PlannerInfo *root,
 	fdw_private_list = NIL;
 	direct_modify_plans = NULL;
 	i = 0;
-	forboth(lc, resultRelations, lc2, subroots)
+	foreach(lc, resultRelations)
 	{
 		Index		rti = lfirst_int(lc);
-		PlannerInfo *subroot = lfirst_node(PlannerInfo, lc2);
 		FdwRoutine *fdwroutine;
 		List	   *fdw_private;
 		bool		direct_modify;
@@ -6904,16 +6865,16 @@ make_modifytable(PlannerInfo *root,
 		 * so it's not a baserel; and there are also corner cases for
 		 * updatable views where the target rel isn't a baserel.)
 		 */
-		if (rti < subroot->simple_rel_array_size &&
-			subroot->simple_rel_array[rti] != NULL)
+		if (rti < root->simple_rel_array_size &&
+			root->simple_rel_array[rti] != NULL)
 		{
-			RelOptInfo *resultRel = subroot->simple_rel_array[rti];
+			RelOptInfo *resultRel = root->simple_rel_array[rti];
 
 			fdwroutine = resultRel->fdwroutine;
 		}
 		else
 		{
-			RangeTblEntry *rte = planner_rt_fetch(rti, subroot);
+			RangeTblEntry *rte = planner_rt_fetch(rti, root);
 
 			Assert(rte->rtekind == RTE_RELATION);
 			if (rte->relkind == RELKIND_FOREIGN_TABLE)
@@ -6936,16 +6897,16 @@ make_modifytable(PlannerInfo *root,
 			fdwroutine->IterateDirectModify != NULL &&
 			fdwroutine->EndDirectModify != NULL &&
 			withCheckOptionLists == NIL &&
-			!has_row_triggers(subroot, rti, operation) &&
-			!has_stored_generated_columns(subroot, rti))
-			direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
+			!has_row_triggers(root, rti, operation) &&
+			!has_stored_generated_columns(root, rti))
+			direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
 		if (direct_modify)
 			direct_modify_plans = bms_add_member(direct_modify_plans, i);
 
 		if (!direct_modify &&
 			fdwroutine != NULL &&
 			fdwroutine->PlanForeignModify != NULL)
-			fdw_private = fdwroutine->PlanForeignModify(subroot, node, rti, i);
+			fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
 		else
 			fdw_private = NIL;
 		fdw_private_list = lappend(fdw_private_list, fdw_private);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index dc13efe..8f3bde1 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -129,9 +129,7 @@ typedef struct
 /* Local functions */
 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
-static void inheritance_planner(PlannerInfo *root);
-static void grouping_planner(PlannerInfo *root, bool inheritance_update,
-							 double tuple_fraction);
+static void grouping_planner(PlannerInfo *root, double tuple_fraction);
 static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root);
 static List *remap_to_groupclause_idx(List *groupClause, List *gsets,
 									  int *tleref_to_colnum_map);
@@ -626,10 +624,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	memset(root->upper_targets, 0, sizeof(root->upper_targets));
 	root->processed_tlist = NIL;
 	root->update_tlist = NIL;
+	root->inherit_junk_tlist = NIL;
 	root->grouping_map = NULL;
 	root->minmax_aggs = NIL;
 	root->qual_security_level = 0;
-	root->inhTargetKind = INHKIND_NONE;
 	root->hasRecursion = hasRecursion;
 	if (hasRecursion)
 		root->wt_param_id = assign_special_exec_param(root);
@@ -637,6 +635,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		root->wt_param_id = -1;
 	root->non_recursive_path = NULL;
 	root->partColsUpdated = false;
+	root->inherit_result_rels = NIL;
+	root->lastResultRelIndex = 0;
 
 	/*
 	 * If there is a WITH list, process each WITH query and either convert it
@@ -1005,15 +1005,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	if (hasResultRTEs)
 		remove_useless_result_rtes(root);
 
-	/*
-	 * Do the main planning.  If we have an inherited target relation, that
-	 * needs special processing, else go straight to grouping_planner.
-	 */
-	if (parse->resultRelation &&
-		rt_fetch(parse->resultRelation, parse->rtable)->inh)
-		inheritance_planner(root);
-	else
-		grouping_planner(root, false, tuple_fraction);
+	/* Do the main planning. */
+	grouping_planner(root, tuple_fraction);
 
 	/*
 	 * Capture the set of outer-level param IDs we have access to, for use in
@@ -1187,636 +1180,6 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
 	return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV);
 }
 
-/*
- * inheritance_planner
- *	  Generate Paths in the case where the result relation is an
- *	  inheritance set.
- *
- * We have to handle this case differently from cases where a source relation
- * is an inheritance set. Source inheritance is expanded at the bottom of the
- * plan tree (see allpaths.c), but target inheritance has to be expanded at
- * the top.  The reason is that for UPDATE, each target relation needs a
- * different targetlist matching its own column set.  Fortunately,
- * the UPDATE/DELETE target can never be the nullable side of an outer join,
- * so it's OK to generate the plan this way.
- *
- * Returns nothing; the useful output is in the Paths we attach to
- * the (UPPERREL_FINAL, NULL) upperrel stored in *root.
- *
- * Note that we have not done set_cheapest() on the final rel; it's convenient
- * to leave this to the caller.
- */
-static void
-inheritance_planner(PlannerInfo *root)
-{
-	Query	   *parse = root->parse;
-	int			top_parentRTindex = parse->resultRelation;
-	List	   *select_rtable;
-	List	   *select_appinfos;
-	List	   *child_appinfos;
-	List	   *old_child_rtis;
-	List	   *new_child_rtis;
-	Bitmapset  *subqueryRTindexes;
-	Index		next_subquery_rti;
-	int			nominalRelation = -1;
-	Index		rootRelation = 0;
-	List	   *final_rtable = NIL;
-	List	   *final_rowmarks = NIL;
-	List	   *final_appendrels = NIL;
-	int			save_rel_array_size = 0;
-	RelOptInfo **save_rel_array = NULL;
-	AppendRelInfo **save_append_rel_array = NULL;
-	List	   *subpaths = NIL;
-	List	   *subroots = NIL;
-	List	   *resultRelations = NIL;
-	List	   *withCheckOptionLists = NIL;
-	List	   *returningLists = NIL;
-	List	   *rowMarks;
-	List	   *updateTargetLists = NIL;
-	RelOptInfo *final_rel;
-	ListCell   *lc;
-	ListCell   *lc2;
-	Index		rti;
-	RangeTblEntry *parent_rte;
-	Bitmapset  *parent_relids;
-	Query	  **parent_parses;
-
-	/* Should only get here for UPDATE or DELETE */
-	Assert(parse->commandType == CMD_UPDATE ||
-		   parse->commandType == CMD_DELETE);
-
-	/*
-	 * We generate a modified instance of the original Query for each target
-	 * relation, plan that, and put all the plans into a list that will be
-	 * controlled by a single ModifyTable node.  All the instances share the
-	 * same rangetable, but each instance must have its own set of subquery
-	 * RTEs within the finished rangetable because (1) they are likely to get
-	 * scribbled on during planning, and (2) it's not inconceivable that
-	 * subqueries could get planned differently in different cases.  We need
-	 * not create duplicate copies of other RTE kinds, in particular not the
-	 * target relations, because they don't have either of those issues.  Not
-	 * having to duplicate the target relations is important because doing so
-	 * (1) would result in a rangetable of length O(N^2) for N targets, with
-	 * at least O(N^3) work expended here; and (2) would greatly complicate
-	 * management of the rowMarks list.
-	 *
-	 * To begin with, generate a bitmapset of the relids of the subquery RTEs.
-	 */
-	subqueryRTindexes = NULL;
-	rti = 1;
-	foreach(lc, parse->rtable)
-	{
-		RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-		if (rte->rtekind == RTE_SUBQUERY)
-			subqueryRTindexes = bms_add_member(subqueryRTindexes, rti);
-		rti++;
-	}
-
-	/*
-	 * If the parent RTE is a partitioned table, we should use that as the
-	 * nominal target relation, because the RTEs added for partitioned tables
-	 * (including the root parent) as child members of the inheritance set do
-	 * not appear anywhere else in the plan, so the confusion explained below
-	 * for non-partitioning inheritance cases is not possible.
-	 */
-	parent_rte = rt_fetch(top_parentRTindex, parse->rtable);
-	Assert(parent_rte->inh);
-	if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
-	{
-		nominalRelation = top_parentRTindex;
-		rootRelation = top_parentRTindex;
-	}
-
-	/*
-	 * Before generating the real per-child-relation plans, do a cycle of
-	 * planning as though the query were a SELECT.  The objective here is to
-	 * find out which child relations need to be processed, using the same
-	 * expansion and pruning logic as for a SELECT.  We'll then pull out the
-	 * RangeTblEntry-s generated for the child rels, and make use of the
-	 * AppendRelInfo entries for them to guide the real planning.  (This is
-	 * rather inefficient; we could perhaps stop short of making a full Path
-	 * tree.  But this whole function is inefficient and slated for
-	 * destruction, so let's not contort query_planner for that.)
-	 */
-	{
-		PlannerInfo *subroot;
-
-		/*
-		 * Flat-copy the PlannerInfo to prevent modification of the original.
-		 */
-		subroot = makeNode(PlannerInfo);
-		memcpy(subroot, root, sizeof(PlannerInfo));
-
-		/*
-		 * Make a deep copy of the parsetree for this planning cycle to mess
-		 * around with, and change it to look like a SELECT.  (Hack alert: the
-		 * target RTE still has updatedCols set if this is an UPDATE, so that
-		 * expand_partitioned_rtentry will correctly update
-		 * subroot->partColsUpdated.)
-		 */
-		subroot->parse = copyObject(root->parse);
-
-		subroot->parse->commandType = CMD_SELECT;
-		subroot->parse->resultRelation = 0;
-
-		/*
-		 * Ensure the subroot has its own copy of the original
-		 * append_rel_list, since it'll be scribbled on.  (Note that at this
-		 * point, the list only contains AppendRelInfos for flattened UNION
-		 * ALL subqueries.)
-		 */
-		subroot->append_rel_list = copyObject(root->append_rel_list);
-
-		/*
-		 * Better make a private copy of the rowMarks, too.
-		 */
-		subroot->rowMarks = copyObject(root->rowMarks);
-
-		/* There shouldn't be any OJ info to translate, as yet */
-		Assert(subroot->join_info_list == NIL);
-		/* and we haven't created PlaceHolderInfos, either */
-		Assert(subroot->placeholder_list == NIL);
-
-		/* Generate Path(s) for accessing this result relation */
-		grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
-		/* Extract the info we need. */
-		select_rtable = subroot->parse->rtable;
-		select_appinfos = subroot->append_rel_list;
-
-		/*
-		 * We need to propagate partColsUpdated back, too.  (The later
-		 * planning cycles will not set this because they won't run
-		 * expand_partitioned_rtentry for the UPDATE target.)
-		 */
-		root->partColsUpdated = subroot->partColsUpdated;
-	}
-
-	/*----------
-	 * Since only one rangetable can exist in the final plan, we need to make
-	 * sure that it contains all the RTEs needed for any child plan.  This is
-	 * complicated by the need to use separate subquery RTEs for each child.
-	 * We arrange the final rtable as follows:
-	 * 1. All original rtable entries (with their original RT indexes).
-	 * 2. All the relation RTEs generated for children of the target table.
-	 * 3. Subquery RTEs for children after the first.  We need N * (K - 1)
-	 *    RT slots for this, if there are N subqueries and K child tables.
-	 * 4. Additional RTEs generated during the child planning runs, such as
-	 *    children of inheritable RTEs other than the target table.
-	 * We assume that each child planning run will create an identical set
-	 * of type-4 RTEs.
-	 *
-	 * So the next thing to do is append the type-2 RTEs (the target table's
-	 * children) to the original rtable.  We look through select_appinfos
-	 * to find them.
-	 *
-	 * To identify which AppendRelInfos are relevant as we thumb through
-	 * select_appinfos, we need to look for both direct and indirect children
-	 * of top_parentRTindex, so we use a bitmap of known parent relids.
-	 * expand_inherited_rtentry() always processes a parent before any of that
-	 * parent's children, so we should see an intermediate parent before its
-	 * children.
-	 *----------
-	 */
-	child_appinfos = NIL;
-	old_child_rtis = NIL;
-	new_child_rtis = NIL;
-	parent_relids = bms_make_singleton(top_parentRTindex);
-	foreach(lc, select_appinfos)
-	{
-		AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
-		RangeTblEntry *child_rte;
-
-		/* append_rel_list contains all append rels; ignore others */
-		if (!bms_is_member(appinfo->parent_relid, parent_relids))
-			continue;
-
-		/* remember relevant AppendRelInfos for use below */
-		child_appinfos = lappend(child_appinfos, appinfo);
-
-		/* extract RTE for this child rel */
-		child_rte = rt_fetch(appinfo->child_relid, select_rtable);
-
-		/* and append it to the original rtable */
-		parse->rtable = lappend(parse->rtable, child_rte);
-
-		/* remember child's index in the SELECT rtable */
-		old_child_rtis = lappend_int(old_child_rtis, appinfo->child_relid);
-
-		/* and its new index in the final rtable */
-		new_child_rtis = lappend_int(new_child_rtis, list_length(parse->rtable));
-
-		/* if child is itself partitioned, update parent_relids */
-		if (child_rte->inh)
-		{
-			Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
-			parent_relids = bms_add_member(parent_relids, appinfo->child_relid);
-		}
-	}
-
-	/*
-	 * It's possible that the RTIs we just assigned for the child rels in the
-	 * final rtable are different from what they were in the SELECT query.
-	 * Adjust the AppendRelInfos so that they will correctly map RT indexes to
-	 * the final indexes.  We can do this left-to-right since no child rel's
-	 * final RT index could be greater than what it had in the SELECT query.
-	 */
-	forboth(lc, old_child_rtis, lc2, new_child_rtis)
-	{
-		int			old_child_rti = lfirst_int(lc);
-		int			new_child_rti = lfirst_int(lc2);
-
-		if (old_child_rti == new_child_rti)
-			continue;			/* nothing to do */
-
-		Assert(old_child_rti > new_child_rti);
-
-		ChangeVarNodes((Node *) child_appinfos,
-					   old_child_rti, new_child_rti, 0);
-	}
-
-	/*
-	 * Now set up rangetable entries for subqueries for additional children
-	 * (the first child will just use the original ones).  These all have to
-	 * look more or less real, or EXPLAIN will get unhappy; so we just make
-	 * them all clones of the original subqueries.
-	 */
-	next_subquery_rti = list_length(parse->rtable) + 1;
-	if (subqueryRTindexes != NULL)
-	{
-		int			n_children = list_length(child_appinfos);
-
-		while (n_children-- > 1)
-		{
-			int			oldrti = -1;
-
-			while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
-			{
-				RangeTblEntry *subqrte;
-
-				subqrte = rt_fetch(oldrti, parse->rtable);
-				parse->rtable = lappend(parse->rtable, copyObject(subqrte));
-			}
-		}
-	}
-
-	/*
-	 * The query for each child is obtained by translating the query for its
-	 * immediate parent, since the AppendRelInfo data we have shows deltas
-	 * between parents and children.  We use the parent_parses array to
-	 * remember the appropriate query trees.  This is indexed by parent relid.
-	 * Since the maximum number of parents is limited by the number of RTEs in
-	 * the SELECT query, we use that number to allocate the array.  An extra
-	 * entry is needed since relids start from 1.
-	 */
-	parent_parses = (Query **) palloc0((list_length(select_rtable) + 1) *
-									   sizeof(Query *));
-	parent_parses[top_parentRTindex] = parse;
-
-	/*
-	 * And now we can get on with generating a plan for each child table.
-	 */
-	foreach(lc, child_appinfos)
-	{
-		AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
-		Index		this_subquery_rti = next_subquery_rti;
-		Query	   *parent_parse;
-		PlannerInfo *subroot;
-		RangeTblEntry *child_rte;
-		RelOptInfo *sub_final_rel;
-		Path	   *subpath;
-
-		/*
-		 * expand_inherited_rtentry() always processes a parent before any of
-		 * that parent's children, so the parent query for this relation
-		 * should already be available.
-		 */
-		parent_parse = parent_parses[appinfo->parent_relid];
-		Assert(parent_parse != NULL);
-
-		/*
-		 * We need a working copy of the PlannerInfo so that we can control
-		 * propagation of information back to the main copy.
-		 */
-		subroot = makeNode(PlannerInfo);
-		memcpy(subroot, root, sizeof(PlannerInfo));
-
-		/*
-		 * Generate modified query with this rel as target.  We first apply
-		 * adjust_appendrel_attrs, which copies the Query and changes
-		 * references to the parent RTE to refer to the current child RTE,
-		 * then fool around with subquery RTEs.
-		 */
-		subroot->parse = (Query *)
-			adjust_appendrel_attrs(subroot,
-								   (Node *) parent_parse,
-								   1, &appinfo);
-
-		/*
-		 * If there are securityQuals attached to the parent, move them to the
-		 * child rel (they've already been transformed properly for that).
-		 */
-		parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable);
-		child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
-		child_rte->securityQuals = parent_rte->securityQuals;
-		parent_rte->securityQuals = NIL;
-
-		/*
-		 * HACK: setting this to a value other than INHKIND_NONE signals to
-		 * relation_excluded_by_constraints() to treat the result relation as
-		 * being an appendrel member.
-		 */
-		subroot->inhTargetKind =
-			(rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED;
-
-		/*
-		 * If this child is further partitioned, remember it as a parent.
-		 * Since a partitioned table does not have any data, we don't need to
-		 * create a plan for it, and we can stop processing it here.  We do,
-		 * however, need to remember its modified PlannerInfo for use when
-		 * processing its children, since we'll update their varnos based on
-		 * the delta from immediate parent to child, not from top to child.
-		 *
-		 * Note: a very non-obvious point is that we have not yet added
-		 * duplicate subquery RTEs to the subroot's rtable.  We mustn't,
-		 * because then its children would have two sets of duplicates,
-		 * confusing matters.
-		 */
-		if (child_rte->inh)
-		{
-			Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
-			parent_parses[appinfo->child_relid] = subroot->parse;
-			continue;
-		}
-
-		/*
-		 * Set the nominal target relation of the ModifyTable node if not
-		 * already done.  If the target is a partitioned table, we already set
-		 * nominalRelation to refer to the partition root, above.  For
-		 * non-partitioned inheritance cases, we'll use the first child
-		 * relation (even if it's excluded) as the nominal target relation.
-		 * Because of the way expand_inherited_rtentry works, that should be
-		 * the RTE representing the parent table in its role as a simple
-		 * member of the inheritance set.
-		 *
-		 * It would be logically cleaner to *always* use the inheritance
-		 * parent RTE as the nominal relation; but that RTE is not otherwise
-		 * referenced in the plan in the non-partitioned inheritance case.
-		 * Instead the duplicate child RTE created by expand_inherited_rtentry
-		 * is used elsewhere in the plan, so using the original parent RTE
-		 * would give rise to confusing use of multiple aliases in EXPLAIN
-		 * output for what the user will think is the "same" table.  OTOH,
-		 * it's not a problem in the partitioned inheritance case, because
-		 * there is no duplicate RTE for the parent.
-		 */
-		if (nominalRelation < 0)
-			nominalRelation = appinfo->child_relid;
-
-		/*
-		 * As above, each child plan run needs its own append_rel_list and
-		 * rowmarks, which should start out as pristine copies of the
-		 * originals.  There can't be any references to UPDATE/DELETE target
-		 * rels in them; but there could be subquery references, which we'll
-		 * fix up in a moment.
-		 */
-		subroot->append_rel_list = copyObject(root->append_rel_list);
-		subroot->rowMarks = copyObject(root->rowMarks);
-
-		/*
-		 * If this isn't the first child Query, adjust Vars and jointree
-		 * entries to reference the appropriate set of subquery RTEs.
-		 */
-		if (final_rtable != NIL && subqueryRTindexes != NULL)
-		{
-			int			oldrti = -1;
-
-			while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
-			{
-				Index		newrti = next_subquery_rti++;
-
-				ChangeVarNodes((Node *) subroot->parse, oldrti, newrti, 0);
-				ChangeVarNodes((Node *) subroot->append_rel_list,
-							   oldrti, newrti, 0);
-				ChangeVarNodes((Node *) subroot->rowMarks, oldrti, newrti, 0);
-			}
-		}
-
-		/* There shouldn't be any OJ info to translate, as yet */
-		Assert(subroot->join_info_list == NIL);
-		/* and we haven't created PlaceHolderInfos, either */
-		Assert(subroot->placeholder_list == NIL);
-
-		/* Generate Path(s) for accessing this result relation */
-		grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
-
-		/*
-		 * Select cheapest path in case there's more than one.  We always run
-		 * modification queries to conclusion, so we care only for the
-		 * cheapest-total path.
-		 */
-		sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
-		set_cheapest(sub_final_rel);
-		subpath = sub_final_rel->cheapest_total_path;
-
-		/*
-		 * If this child rel was excluded by constraint exclusion, exclude it
-		 * from the result plan.
-		 */
-		if (IS_DUMMY_REL(sub_final_rel))
-			continue;
-
-		/*
-		 * If this is the first non-excluded child, its post-planning rtable
-		 * becomes the initial contents of final_rtable; otherwise, copy its
-		 * modified subquery RTEs into final_rtable, to ensure we have sane
-		 * copies of those.  Also save the first non-excluded child's version
-		 * of the rowmarks list; we assume all children will end up with
-		 * equivalent versions of that.  Likewise for append_rel_list.
-		 */
-		if (final_rtable == NIL)
-		{
-			final_rtable = subroot->parse->rtable;
-			final_rowmarks = subroot->rowMarks;
-			final_appendrels = subroot->append_rel_list;
-		}
-		else
-		{
-			Assert(list_length(final_rtable) ==
-				   list_length(subroot->parse->rtable));
-			if (subqueryRTindexes != NULL)
-			{
-				int			oldrti = -1;
-
-				while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
-				{
-					Index		newrti = this_subquery_rti++;
-					RangeTblEntry *subqrte;
-					ListCell   *newrticell;
-
-					subqrte = rt_fetch(newrti, subroot->parse->rtable);
-					newrticell = list_nth_cell(final_rtable, newrti - 1);
-					lfirst(newrticell) = subqrte;
-				}
-			}
-		}
-
-		/*
-		 * We need to collect all the RelOptInfos from all child plans into
-		 * the main PlannerInfo, since setrefs.c will need them.  We use the
-		 * last child's simple_rel_array, so we have to propagate forward the
-		 * RelOptInfos that were already built in previous children.
-		 */
-		Assert(subroot->simple_rel_array_size >= save_rel_array_size);
-		for (rti = 1; rti < save_rel_array_size; rti++)
-		{
-			RelOptInfo *brel = save_rel_array[rti];
-
-			if (brel)
-				subroot->simple_rel_array[rti] = brel;
-		}
-		save_rel_array_size = subroot->simple_rel_array_size;
-		save_rel_array = subroot->simple_rel_array;
-		save_append_rel_array = subroot->append_rel_array;
-
-		/*
-		 * Make sure any initplans from this rel get into the outer list. Note
-		 * we're effectively assuming all children generate the same
-		 * init_plans.
-		 */
-		root->init_plans = subroot->init_plans;
-
-		/* Build list of sub-paths */
-		subpaths = lappend(subpaths, subpath);
-
-		/* Build list of modified subroots, too */
-		subroots = lappend(subroots, subroot);
-
-		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
-
-		/* Build lists of per-relation WCO and RETURNING targetlists */
-		if (parse->withCheckOptions)
-			withCheckOptionLists = lappend(withCheckOptionLists,
-										   subroot->parse->withCheckOptions);
-		if (parse->returningList)
-			returningLists = lappend(returningLists,
-									 subroot->parse->returningList);
-
-		if (parse->commandType == CMD_UPDATE)
-		{
-			Assert(subroot->update_tlist != NIL);
-			updateTargetLists = lappend(updateTargetLists,
-										subroot->update_tlist);
-		}
-		Assert(!parse->onConflict);
-	}
-
-	/* Result path must go into outer query's FINAL upperrel */
-	final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
-
-	/*
-	 * We don't currently worry about setting final_rel's consider_parallel
-	 * flag in this case, nor about allowing FDWs or create_upper_paths_hook
-	 * to get control here.
-	 */
-
-	if (subpaths == NIL)
-	{
-		/*
-		 * We managed to exclude every child rel, so generate a dummy path
-		 * representing the empty set.  Although it's clear that no data will
-		 * be updated or deleted, we will still need to have a ModifyTable
-		 * node so that any statement triggers are executed.  (This could be
-		 * cleaner if we fixed nodeModifyTable.c to support zero child nodes,
-		 * but that probably wouldn't be a net win.)
-		 */
-		Path	   *dummy_path;
-
-		/* tlist processing never got done, either */
-		root->processed_tlist = preprocess_targetlist(root);
-		final_rel->reltarget = create_pathtarget(root, root->processed_tlist);
-
-		/* Make a dummy path, cf set_dummy_rel_pathlist() */
-		dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
-												 NIL, NULL, 0, false,
-												 NIL, -1);
-
-		/* These lists must be nonempty to make a valid ModifyTable node */
-		subpaths = list_make1(dummy_path);
-		subroots = list_make1(root);
-		resultRelations = list_make1_int(parse->resultRelation);
-		if (parse->withCheckOptions)
-			withCheckOptionLists = list_make1(parse->withCheckOptions);
-		if (parse->returningList)
-			returningLists = list_make1(parse->returningList);
-		/* 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;
-	}
-	else
-	{
-		/*
-		 * Put back the final adjusted rtable into the original copy of the
-		 * Query.  (We mustn't do this if we found no non-excluded children,
-		 * since we never saved an adjusted rtable at all.)
-		 */
-		parse->rtable = final_rtable;
-		root->simple_rel_array_size = save_rel_array_size;
-		root->simple_rel_array = save_rel_array;
-		root->append_rel_array = save_append_rel_array;
-
-		/* Must reconstruct original's simple_rte_array, too */
-		root->simple_rte_array = (RangeTblEntry **)
-			palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *));
-		rti = 1;
-		foreach(lc, final_rtable)
-		{
-			RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
-
-			root->simple_rte_array[rti++] = rte;
-		}
-
-		/* Put back adjusted rowmarks and appendrels, too */
-		root->rowMarks = final_rowmarks;
-		root->append_rel_list = final_appendrels;
-	}
-
-	/*
-	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
-	 * have dealt with fetching non-locked marked rows, else we need to have
-	 * ModifyTable do that.
-	 */
-	if (parse->rowMarks)
-		rowMarks = NIL;
-	else
-		rowMarks = root->rowMarks;
-
-	/* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
-	add_path(final_rel, (Path *)
-			 create_modifytable_path(root, final_rel,
-									 parse->commandType,
-									 parse->canSetTag,
-									 nominalRelation,
-									 rootRelation,
-									 root->partColsUpdated,
-									 resultRelations,
-									 subpaths,
-									 subroots,
-									 updateTargetLists,
-									 withCheckOptionLists,
-									 returningLists,
-									 rowMarks,
-									 NULL,
-									 assign_special_exec_param(root)));
-}
-
 /*--------------------
  * grouping_planner
  *	  Perform planning steps related to grouping, aggregation, etc.
@@ -1824,11 +1187,6 @@ inheritance_planner(PlannerInfo *root)
  * This function adds all required top-level processing to the scan/join
  * Path(s) produced by query_planner.
  *
- * If inheritance_update is true, we're being called from inheritance_planner
- * and should not include a ModifyTable step in the resulting Path(s).
- * (inheritance_planner will create a single ModifyTable node covering all the
- * target tables.)
- *
  * tuple_fraction is the fraction of tuples we expect will be retrieved.
  * tuple_fraction is interpreted as follows:
  *	  0: expect all tuples to be retrieved (normal case)
@@ -1846,8 +1204,7 @@ inheritance_planner(PlannerInfo *root)
  *--------------------
  */
 static void
-grouping_planner(PlannerInfo *root, bool inheritance_update,
-				 double tuple_fraction)
+grouping_planner(PlannerInfo *root, double tuple_fraction)
 {
 	Query	   *parse = root->parse;
 	int64		offset_est = 0;
@@ -2339,16 +1696,110 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 											  offset_est, count_est);
 		}
 
-		/*
-		 * If this is an INSERT/UPDATE/DELETE, and we're not being called from
-		 * inheritance_planner, add the ModifyTable node.
-		 */
-		if (parse->commandType != CMD_SELECT && !inheritance_update)
+		/* If this is an INSERT/UPDATE/DELETE, add the ModifyTable node. */
+		if (parse->commandType != CMD_SELECT)
 		{
 			Index		rootRelation;
-			List	   *withCheckOptionLists;
-			List	   *returningLists;
+			List	   *resultRelations = NIL;
+			List	   *updateTargetLists = NIL;
+			List	   *withCheckOptionLists = NIL;
+			List	   *returningLists = NIL;
 			List	   *rowMarks;
+			ListCell *l;
+
+			if (root->inherit_result_rels)
+			{
+				/* Inherited UPDATE/DELETE */
+				foreach(l, root->inherit_result_rels)
+				{
+					InheritResultRelInfo *resultInfo = lfirst(l);
+					Index	resultRelation = resultInfo->resultRelation;
+
+					/* Add only leaf children to ModifyTable. */
+					if (planner_rt_fetch(resultInfo->resultRelation,
+										 root)->inh)
+						continue;
+
+					/*
+					 * Also exclude any leaf rels that have turned dummy since
+					 * being added to the list, for example, by being excluded
+					 * by constraint exclusion.
+					 */
+					if (IS_DUMMY_REL(find_base_rel(root, resultRelation)))
+						continue;
+
+					resultRelations = lappend_int(resultRelations,
+												  resultInfo->resultRelation);
+					updateTargetLists = lappend(updateTargetLists,
+												resultInfo->update_tlist);
+					if (resultInfo->withCheckOptions)
+						withCheckOptionLists = lappend(withCheckOptionLists,
+													   resultInfo->withCheckOptions);
+					if (resultInfo->returningList)
+						returningLists = lappend(returningLists,
+												 resultInfo->returningList);
+				}
+
+				/*
+				 * We managed to exclude every child rel, so generate a dummy
+				 * path representing the empty set.  Although it's clear that
+				 * no data will be updated or deleted, we will still need to
+				 * have a ModifyTable node so that any statement triggers are
+				 * executed.  (This could be cleaner if we fixed
+				 * nodeModifyTable.c to support zero child nodes, but that
+				 * probably wouldn't be a net win.)
+				 */
+				if (resultRelations == NIL)
+				{
+					InheritResultRelInfo *resultInfo = linitial(root->inherit_result_rels);
+					RelOptInfo *rel = find_base_rel(root, resultInfo->resultRelation);
+					List	   *newlist;
+
+					resultRelations = list_make1_int(resultInfo->resultRelation);
+					updateTargetLists = list_make1(resultInfo->update_tlist);
+					if (resultInfo->withCheckOptions)
+						withCheckOptionLists = list_make1(resultInfo->withCheckOptions);
+					if (resultInfo->returningList)
+						returningLists = list_make1(resultInfo->returningList);
+
+					/*
+					 * Must remove special junk attributes from the targetlist
+					 * that were added for child relations, because they are
+					 * no longer necessary and in fact may not even be
+					 * computable using root parent relation.
+					 */
+					newlist = NIL;
+					foreach(l, root->processed_tlist)
+					{
+						TargetEntry *tle = lfirst(l);
+
+						if (!list_member(root->inherit_junk_tlist, tle))
+							newlist = lappend(newlist, tle);
+					}
+					root->processed_tlist = newlist;
+					rel->reltarget = create_pathtarget(root,
+													   root->processed_tlist);
+					/*
+					 * Override the existing path with a dummy Append path,
+					 * because the old path still references the old
+					 * reltarget.
+					 */
+					path = (Path *) create_append_path(NULL, rel, NIL, NIL,
+													   NIL, NULL, 0, false,
+													   NIL, -1);
+				}
+			}
+			else
+			{
+				/* Single-relation UPDATE/DELETE or INSERT. */
+				resultRelations = list_make1_int(parse->resultRelation);
+				if (parse->commandType == CMD_UPDATE)
+					updateTargetLists = list_make1(root->update_tlist);
+				if (parse->withCheckOptions)
+					withCheckOptionLists = list_make1(parse->withCheckOptions);
+				if (parse->returningList)
+					returningLists = list_make1(parse->returningList);
+			}
 
 			/*
 			 * If target is a partition root table, we need to mark the
@@ -2361,20 +1812,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 				rootRelation = 0;
 
 			/*
-			 * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
-			 * needed.
-			 */
-			if (parse->withCheckOptions)
-				withCheckOptionLists = list_make1(parse->withCheckOptions);
-			else
-				withCheckOptionLists = NIL;
-
-			if (parse->returningList)
-				returningLists = list_make1(parse->returningList);
-			else
-				returningLists = NIL;
-
-			/*
 			 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node
 			 * will have dealt with fetching non-locked marked rows, else we
 			 * need to have ModifyTable do that.
@@ -2390,11 +1827,11 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										parse->canSetTag,
 										parse->resultRelation,
 										rootRelation,
-										false,
-										list_make1_int(parse->resultRelation),
+										root->partColsUpdated,
+										resultRelations,
 										list_make1(path),
 										list_make1(root),
-										list_make1(root->update_tlist),
+										updateTargetLists,
 										withCheckOptionLists,
 										returningLists,
 										rowMarks,
@@ -7776,3 +7213,60 @@ group_by_has_partkey(RelOptInfo *input_rel,
 
 	return true;
 }
+
+/*
+ * is_result_relation
+ * 		Is passed-in relation a result relation of this query?
+ *
+ * While root->parse->resultRelation gives the query's original target
+ * relation, other target relations resulting from adding inheritance child
+ * relations of the main target relation are tracked elsewhere.
+ */
+bool
+is_result_relation(Index relid, PlannerInfo *root)
+{
+	InheritResultRelInfo *resultInfo;
+
+	if (relid == root->parse->resultRelation)
+		return true;
+
+	/*
+	 * There can be only one result relation in a given subquery before
+	 * inheritance child relation are added.
+	 */
+	if (root->inherit_result_rel_array == NULL)
+		return false;
+
+	resultInfo = root->inherit_result_rel_array[relid];
+	if (resultInfo == NULL)
+		return false;
+
+	return (relid == resultInfo->resultRelation);
+}
+
+/*
+ * get_result_update_tlist
+ *		Returns update targetlist of a given result relation
+ *
+ * Update targetlist for the main target relation is present in
+ * root->update_tlist, however that of inheritance child result relations is
+ * tracked elsewhere.
+ *
+ * Note: Don't call for a relation that is certainly not a result relation!
+ */
+List *
+get_result_update_tlist(PlannerInfo *root, Index result_relation)
+{
+	InheritResultRelInfo *resultInfo;
+
+	Assert(is_result_relation(result_relation, root));
+
+	if (result_relation == root->parse->resultRelation)
+		return root->update_tlist;
+
+	Assert(root->inherit_result_rel_array != NULL);
+	resultInfo = root->inherit_result_rel_array[result_relation];
+	Assert(resultInfo != NULL);
+
+	return resultInfo->update_tlist;
+}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 4e439be..b900b23 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -273,6 +273,7 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 		 * Neither the executor nor EXPLAIN currently need that data.
 		 */
 		appinfo->translated_vars = NIL;
+		appinfo->translated_fake_vars = NIL;
 
 		glob->appendRelations = lappend(glob->appendRelations, appinfo);
 	}
@@ -825,26 +826,21 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 				{
 					List	   *newRL = NIL;
 					ListCell   *lcrl,
-							   *lcrr,
-							   *lcp;
+							   *lcrr;
 
 					/*
-					 * Pass each per-subplan returningList through
+					 * Pass each per-resultrel returningList through
 					 * set_returning_clause_references().
 					 */
 					Assert(list_length(splan->returningLists) == list_length(splan->resultRelations));
-					Assert(list_length(splan->returningLists) == list_length(splan->plans));
-					forthree(lcrl, splan->returningLists,
-							 lcrr, splan->resultRelations,
-							 lcp, splan->plans)
+					forboth(lcrl, splan->returningLists, lcrr, splan->resultRelations)
 					{
 						List	   *rlist = (List *) lfirst(lcrl);
 						Index		resultrel = lfirst_int(lcrr);
-						Plan	   *subplan = (Plan *) lfirst(lcp);
 
 						rlist = set_returning_clause_references(root,
 																rlist,
-																subplan,
+																splan->subplan,
 																resultrel,
 																rtoffset);
 						newRL = lappend(newRL, rlist);
@@ -911,18 +907,15 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					rc->prti += rtoffset;
 				}
 				/*
-				 * Caution: Do not change the relative ordering of this loop
-				 * and the statement below that adds the result relations to
-				 * root->glob->resultRelations, because we need to use the
+				 * Caution: Do not change the relative ordering of this and
+				 * the statement that follows that adds the result relations
+				 * to root->glob->resultRelations, because we need to use the
 				 * current value of list_length(root->glob->resultRelations)
 				 * in some plans.
 				 */
-				foreach(l, splan->plans)
-				{
-					lfirst(l) = set_plan_refs(root,
-											  (Plan *) lfirst(l),
-											  rtoffset);
-				}
+				splan->subplan = set_plan_refs(root,
+											   splan->subplan,
+											   rtoffset);
 
 				/*
 				 * Append this ModifyTable node's final result relation RT
@@ -2725,26 +2718,59 @@ set_update_tlist_references(PlannerInfo *root,
 							int rtoffset)
 {
 	ListCell   *lc1,
-			   *lc2,
-			   *lc3;
+			   *lc2;
 
-	forthree(lc1, splan->resultRelations,
-			 lc2, splan->updateTargetLists,
-			 lc3, splan->plans)
+	forboth(lc1, splan->resultRelations, lc2, splan->updateTargetLists)
 	{
 		Index	resultRelation = lfirst_int(lc1);
 		List   *update_tlist = lfirst(lc2);
-		Plan   *subplan = lfirst(lc3);
+		List   *targetlist;
 		indexed_tlist *itlist;
 
-		itlist = build_tlist_index(subplan->targetlist);
+		/*
+		 * root->processed_tlist in a non-inherited UPDATE or a child result
+		 * relation's version of it in an inherited UPDATE refers to the same
+		 * targetlist that splan->subplan produces.  update_tlist of a child
+		 * relation contains translated version of the expressions in the
+		 * splan->subplan's targetlist, so it is important that we use the
+		 * correct translated list.
+		 */
+		if (resultRelation == root->parse->resultRelation)
+		{
+			targetlist = root->processed_tlist;
+		}
+		else
+		{
+			ListCell   *lc;
+			AttrNumber	resno;
+			InheritResultRelInfo *resultInfo;
+
+			resultInfo = root->inherit_result_rel_array[resultRelation];
+			targetlist = resultInfo->processed_tlist;
+
+			/*
+			 * Make resnos of plan tlist TLEs match their ordinal position in
+			 * the list so that fix_join_expr() uses correct varattno for
+			 * OUTER Vars in update_tlist.  It's okay to scribble on these
+			 * entries now, because nobody should need to inspect them at
+			 * this point.
+			 */
+			resno = 1;
+			foreach(lc, targetlist)
+			{
+				TargetEntry *tle = lfirst(lc);
+
+				tle->resno = resno++;
+			}
+		}
+
+		itlist = build_tlist_index(targetlist);
 		lfirst(lc2) = fix_join_expr(root,
 									update_tlist,
 									itlist,
 									NULL,
 									resultRelation,
 									rtoffset);
-
 	}
 }
 
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6eb7946..1ed5a07 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2497,7 +2497,6 @@ finalize_plan(PlannerInfo *root, Plan *plan,
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
-				ListCell   *l;
 
 				/* Force descendant scan nodes to reference epqParam */
 				locally_added_param = mtplan->epqParam;
@@ -2512,16 +2511,13 @@ finalize_plan(PlannerInfo *root, Plan *plan,
 				finalize_primnode((Node *) mtplan->onConflictWhere,
 								  &context);
 				/* exclRelTlist contains only Vars, doesn't need examination */
-				foreach(l, mtplan->plans)
-				{
-					context.paramids =
-						bms_add_members(context.paramids,
-										finalize_plan(root,
-													  (Plan *) lfirst(l),
-													  gather_param,
-													  valid_params,
-													  scan_params));
-				}
+				context.paramids =
+					bms_add_members(context.paramids,
+									finalize_plan(root,
+												  mtplan->subplan,
+												  gather_param,
+												  valid_params,
+												  scan_params));
 			}
 			break;
 
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1452172..d24ac34 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -926,7 +926,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->grouping_map = NULL;
 	subroot->minmax_aggs = NIL;
 	subroot->qual_security_level = 0;
-	subroot->inhTargetKind = INHKIND_NONE;
 	subroot->hasRecursion = false;
 	subroot->wt_param_id = -1;
 	subroot->non_recursive_path = NULL;
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 1ce5cc2..79c044a 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -57,7 +57,6 @@
 #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);
 
@@ -108,6 +107,10 @@ preprocess_targetlist(PlannerInfo *root)
 	 * to identify the rows to be updated or deleted.  Note that this step
 	 * scribbles on parse->targetList, which is not very desirable, but we
 	 * keep it that way to avoid changing APIs used by FDWs.
+	 *
+	 * If target relation has inheritance children, junk column(s) needed
+	 * by the individual leaf child relations are added by
+	 * inherit.c: add_child_junk_attrs().
 	 */
 	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
 		rewriteTargetListUD(parse, target_rte, target_relation);
@@ -120,7 +123,9 @@ preprocess_targetlist(PlannerInfo *root)
 	 * 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.
+	 * missing values are taken from the old tuple.  If the UPDATE's target
+	 * relation has inheritance children, their update_tlist is maintained
+	 * separately; see inherit.c: add_inherit_result_relation_info().
 	 */
 	tlist = parse->targetList;
 	if (command_type == CMD_INSERT)
@@ -253,7 +258,6 @@ preprocess_targetlist(PlannerInfo *root)
 	return tlist;
 }
 
-
 /*
  * make_update_tlist
  * 		Wrapper over expand_targetlist for generating the expanded tlist
@@ -262,7 +266,7 @@ preprocess_targetlist(PlannerInfo *root)
  * The returned list does not contain any junk entries, only those for
  * target relation attributes.
  */
-static List *
+List *
 make_update_tlist(List *tlist, Index result_relation, Relation rel)
 {
 	return filter_junk_tlist_entries(expand_targetlist(tlist, CMD_UPDATE,
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index d722063..64c4ed1 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -29,6 +29,7 @@ typedef struct
 	PlannerInfo *root;
 	int			nappinfos;
 	AppendRelInfo **appinfos;
+	bool		need_parent_wholerow;
 } adjust_appendrel_attrs_context;
 
 static void make_inh_translation_list(Relation oldrelation,
@@ -37,8 +38,6 @@ static void make_inh_translation_list(Relation oldrelation,
 									  AppendRelInfo *appinfo);
 static Node *adjust_appendrel_attrs_mutator(Node *node,
 											adjust_appendrel_attrs_context *context);
-static List *adjust_inherited_tlist(List *tlist,
-									AppendRelInfo *context);
 
 
 /*
@@ -200,42 +199,42 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
 	context.root = root;
 	context.nappinfos = nappinfos;
 	context.appinfos = appinfos;
+	context.need_parent_wholerow = true;
 
 	/* If there's nothing to adjust, don't call this function. */
 	Assert(nappinfos >= 1 && appinfos != NULL);
 
-	/*
-	 * Must be prepared to start with a Query or a bare expression tree.
-	 */
-	if (node && IsA(node, Query))
-	{
-		Query	   *newnode;
-		int			cnt;
+	/* Should never be translating a Query tree. */
+	Assert (node == NULL || !IsA(node, Query));
+	result = adjust_appendrel_attrs_mutator(node, &context);
 
-		newnode = query_tree_mutator((Query *) node,
-									 adjust_appendrel_attrs_mutator,
-									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
-		for (cnt = 0; cnt < nappinfos; cnt++)
-		{
-			AppendRelInfo *appinfo = appinfos[cnt];
+	return result;
+}
 
-			if (newnode->resultRelation == appinfo->parent_relid)
-			{
-				newnode->resultRelation = appinfo->child_relid;
-				/* Fix tlist resnos too, if it's inherited UPDATE */
-				if (newnode->commandType == CMD_UPDATE)
-					newnode->targetList =
-						adjust_inherited_tlist(newnode->targetList,
-											   appinfo);
-				break;
-			}
-		}
+/*
+ * adjust_target_appendrel_attrs
+ *		like adjust_appendrel_attrs, but treats wholerow Vars a bit
+ *		differently in that it doesn't convert any child table
+ *		wholerows contained in 'node' back to the parent reltype.
+ */
+Node *
+adjust_target_appendrel_attrs(PlannerInfo *root, Node *node,
+							  AppendRelInfo *appinfo)
+{
+	Node	   *result;
+	adjust_appendrel_attrs_context context;
 
-		result = (Node *) newnode;
-	}
-	else
-		result = adjust_appendrel_attrs_mutator(node, &context);
+	context.root = root;
+	context.nappinfos = 1;
+	context.appinfos = &appinfo;
+	context.need_parent_wholerow = false;
+
+	/* If there's nothing to adjust, don't call this function. */
+	Assert(appinfo != NULL);
+
+	/* Should never be translating a Query tree. */
+	Assert (node == NULL || !IsA(node, Query));
+	result = adjust_appendrel_attrs_mutator(node, &context);
 
 	return result;
 }
@@ -277,11 +276,16 @@ adjust_appendrel_attrs_mutator(Node *node,
 			{
 				Node	   *newnode;
 
+				/*
+				 * If this Var appears to have a unusual attno assigned, it
+				 * must be one of the "fake" vars added to a parent target
+				 * relation's reltarget; see add_inherit_junk_var().
+				 */
 				if (var->varattno > list_length(appinfo->translated_vars))
-					elog(ERROR, "attribute %d of relation \"%s\" does not exist",
-						 var->varattno, get_rel_name(appinfo->parent_reloid));
-				newnode = copyObject(list_nth(appinfo->translated_vars,
-											  var->varattno - 1));
+					newnode = translate_fake_parent_var(var, appinfo);
+				else
+					newnode = copyObject(list_nth(appinfo->translated_vars,
+												  var->varattno - 1));
 				if (newnode == NULL)
 					elog(ERROR, "attribute %d of relation \"%s\" does not exist",
 						 var->varattno, get_rel_name(appinfo->parent_reloid));
@@ -298,7 +302,10 @@ adjust_appendrel_attrs_mutator(Node *node,
 				if (OidIsValid(appinfo->child_reltype))
 				{
 					Assert(var->vartype == appinfo->parent_reltype);
-					if (appinfo->parent_reltype != appinfo->child_reltype)
+					/* Make sure the Var node has the right type ID, too */
+					var->vartype = appinfo->child_reltype;
+					if (appinfo->parent_reltype != appinfo->child_reltype &&
+						context->need_parent_wholerow)
 					{
 						ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
 
@@ -306,8 +313,6 @@ adjust_appendrel_attrs_mutator(Node *node,
 						r->resulttype = appinfo->parent_reltype;
 						r->convertformat = COERCE_IMPLICIT_CAST;
 						r->location = -1;
-						/* Make sure the Var node has the right type ID, too */
-						var->vartype = appinfo->child_reltype;
 						return (Node *) r;
 					}
 				}
@@ -361,44 +366,6 @@ adjust_appendrel_attrs_mutator(Node *node,
 		}
 		return (Node *) cexpr;
 	}
-	if (IsA(node, RangeTblRef))
-	{
-		RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
-
-		for (cnt = 0; cnt < nappinfos; cnt++)
-		{
-			AppendRelInfo *appinfo = appinfos[cnt];
-
-			if (rtr->rtindex == appinfo->parent_relid)
-			{
-				rtr->rtindex = appinfo->child_relid;
-				break;
-			}
-		}
-		return (Node *) rtr;
-	}
-	if (IsA(node, JoinExpr))
-	{
-		/* Copy the JoinExpr node with correct mutation of subnodes */
-		JoinExpr   *j;
-		AppendRelInfo *appinfo;
-
-		j = (JoinExpr *) expression_tree_mutator(node,
-												 adjust_appendrel_attrs_mutator,
-												 (void *) context);
-		/* now fix JoinExpr's rtindex (probably never happens) */
-		for (cnt = 0; cnt < nappinfos; cnt++)
-		{
-			appinfo = appinfos[cnt];
-
-			if (j->rtindex == appinfo->parent_relid)
-			{
-				j->rtindex = appinfo->child_relid;
-				break;
-			}
-		}
-		return (Node *) j;
-	}
 	if (IsA(node, PlaceHolderVar))
 	{
 		/* Copy the PlaceHolderVar node with correct mutation of subnodes */
@@ -487,6 +454,10 @@ adjust_appendrel_attrs_mutator(Node *node,
 	Assert(!IsA(node, SubLink));
 	Assert(!IsA(node, Query));
 
+	/* We should never see these Query substructures. */
+	Assert(!IsA(node, RangeTblRef));
+	Assert(!IsA(node, JoinExpr));
+
 	return expression_tree_mutator(node, adjust_appendrel_attrs_mutator,
 								   (void *) context);
 }
@@ -621,103 +592,6 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 }
 
 /*
- * Adjust the targetlist entries of an inherited UPDATE operation
- *
- * The expressions have already been fixed, but we have to make sure that
- * the target resnos match the child table (they may not, in the case of
- * a column that was added after-the-fact by ALTER TABLE).  In some cases
- * this can force us to re-order the tlist to preserve resno ordering.
- * (We do all this work in special cases so that preptlist.c is fast for
- * the typical case.)
- *
- * The given tlist has already been through expression_tree_mutator;
- * therefore the TargetEntry nodes are fresh copies that it's okay to
- * scribble on.
- *
- * Note that this is not needed for INSERT because INSERT isn't inheritable.
- */
-static List *
-adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
-{
-	bool		changed_it = false;
-	ListCell   *tl;
-	List	   *new_tlist;
-	bool		more;
-	int			attrno;
-
-	/* This should only happen for an inheritance case, not UNION ALL */
-	Assert(OidIsValid(context->parent_reloid));
-
-	/* Scan tlist and update resnos to match attnums of child rel */
-	foreach(tl, tlist)
-	{
-		TargetEntry *tle = (TargetEntry *) lfirst(tl);
-		Var		   *childvar;
-
-		if (tle->resjunk)
-			continue;			/* ignore junk items */
-
-		/* Look up the translation of this column: it must be a Var */
-		if (tle->resno <= 0 ||
-			tle->resno > list_length(context->translated_vars))
-			elog(ERROR, "attribute %d of relation \"%s\" does not exist",
-				 tle->resno, get_rel_name(context->parent_reloid));
-		childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1);
-		if (childvar == NULL || !IsA(childvar, Var))
-			elog(ERROR, "attribute %d of relation \"%s\" does not exist",
-				 tle->resno, get_rel_name(context->parent_reloid));
-
-		if (tle->resno != childvar->varattno)
-		{
-			tle->resno = childvar->varattno;
-			changed_it = true;
-		}
-	}
-
-	/*
-	 * If we changed anything, re-sort the tlist by resno, and make sure
-	 * resjunk entries have resnos above the last real resno.  The sort
-	 * algorithm is a bit stupid, but for such a seldom-taken path, small is
-	 * probably better than fast.
-	 */
-	if (!changed_it)
-		return tlist;
-
-	new_tlist = NIL;
-	more = true;
-	for (attrno = 1; more; attrno++)
-	{
-		more = false;
-		foreach(tl, tlist)
-		{
-			TargetEntry *tle = (TargetEntry *) lfirst(tl);
-
-			if (tle->resjunk)
-				continue;		/* ignore junk items */
-
-			if (tle->resno == attrno)
-				new_tlist = lappend(new_tlist, tle);
-			else if (tle->resno > attrno)
-				more = true;
-		}
-	}
-
-	foreach(tl, tlist)
-	{
-		TargetEntry *tle = (TargetEntry *) lfirst(tl);
-
-		if (!tle->resjunk)
-			continue;			/* here, ignore non-junk items */
-
-		tle->resno = attrno;
-		new_tlist = lappend(new_tlist, tle);
-		attrno++;
-	}
-
-	return new_tlist;
-}
-
-/*
  * find_appinfos_by_relids
  * 		Find AppendRelInfo structures for all relations specified by relids.
  *
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 3132fd3..9c811b4 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -21,6 +21,7 @@
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
 #include "optimizer/inherit.h"
 #include "optimizer/optimizer.h"
@@ -29,9 +30,12 @@
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
+#include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "partitioning/partprune.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
 
 
@@ -49,6 +53,16 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
 									  List *translated_vars);
 static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
 									  RangeTblEntry *rte, Index rti);
+static void add_inherit_result_relation_info(PlannerInfo *root, Index rti,
+							Relation relation,
+							InheritResultRelInfo *parentInfo);
+static List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context);
+static void add_child_junk_attrs(PlannerInfo *root,
+					Index childRTindex, Relation childrelation,
+					RelOptInfo *rel, Relation relation);
+static void add_inherit_junk_var(PlannerInfo *root, char *attrname, Node *child_expr,
+					   AppendRelInfo *appinfo,
+					   RelOptInfo *parentrelinfo, Relation parentrelation);
 
 
 /*
@@ -85,6 +99,8 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 	PlanRowMark *oldrc;
 	bool		old_isParent = false;
 	int			old_allMarkTypes = 0;
+	ListCell   *l;
+	List	   *newvars = NIL;
 
 	Assert(rte->inh);			/* else caller error */
 
@@ -128,6 +144,22 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		old_allMarkTypes = oldrc->allMarkTypes;
 	}
 
+	/*
+	 * Make an InheritResultRelInfo for the root parent if it's an
+	 * UPDATE/DELETE result relation.
+	 */
+	if (rti == root->parse->resultRelation&&
+		root->parse->commandType != CMD_INSERT)
+	{
+		/* Make an array indexable by RT indexes for easy lookup. */
+		root->inherit_result_rel_array = (InheritResultRelInfo **)
+			palloc0(root->simple_rel_array_size *
+					sizeof(InheritResultRelInfo *));
+
+		add_inherit_result_relation_info(root, root->parse->resultRelation,
+										 oldrelation, NULL);
+	}
+
 	/* Scan the inheritance set and expand it */
 	if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
@@ -151,7 +183,6 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		 * children, so it's not possible for both cases to apply.)
 		 */
 		List	   *inhOIDs;
-		ListCell   *l;
 
 		/* Scan for all members of inheritance set, acquire needed locks */
 		inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
@@ -226,7 +257,6 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 		Var		   *var;
 		TargetEntry *tle;
 		char		resname[32];
-		List	   *newvars = NIL;
 
 		/* The old PlanRowMark should already have necessitated adding TID */
 		Assert(old_allMarkTypes & ~(1 << ROW_MARK_COPY));
@@ -265,13 +295,24 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
 			root->processed_tlist = lappend(root->processed_tlist, tle);
 			newvars = lappend(newvars, var);
 		}
+	}
 
-		/*
-		 * Add the newly added Vars to parent's reltarget.  We needn't worry
-		 * about the children's reltargets, they'll be made later.
-		 */
+	/*
+	 * Also pull any appendrel parent junk vars added due to child result
+	 * relations.
+	 */
+	if (rti == root->parse->resultRelation &&
+		list_length(root->inherit_junk_tlist) > 0)
+		newvars = list_concat(newvars,
+							  pull_var_clause((Node *)
+											  root->inherit_junk_tlist, 0));
+
+	/*
+	 * Add the newly added Vars to parent's reltarget.  We needn't worry
+	 * about the children's reltargets, they'll be made later.
+	 */
+	if (newvars != NIL)
 		add_vars_to_targetlist(root, newvars, bms_make_singleton(0), false);
-	}
 
 	table_close(oldrelation, NoLock);
 }
@@ -381,10 +422,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
 
 		/* If this child is itself partitioned, recurse */
 		if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		{
 			expand_partitioned_rtentry(root, childrelinfo,
 									   childrte, childRTindex,
 									   childrel, top_parentrc, lockmode);
 
+			/*
+			 * Add junk attributes needed by this child relation or really by
+			 * its children.  We must do this after having added all the leaf
+			 * children of this relation, because the add_child_junk_attrs()
+			 * call below simply propagates their junk attributes that are in
+			 * the form of this child relation's vars up to its own parent.
+			 */
+			if (is_result_relation(childRTindex, root))
+				add_child_junk_attrs(root, childRTindex, childrel,
+									 relinfo, parentrel);
+		}
+
 		/* Close child relation, but keep locks */
 		table_close(childrel, NoLock);
 	}
@@ -585,6 +639,27 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 
 		root->rowMarks = lappend(root->rowMarks, childrc);
 	}
+
+	/*
+	 * If this appears to be a child of an UPDATE/DELETE result relation, we
+	 * need to remember some additional information.
+	 */
+	if (is_result_relation(parentRTindex, root))
+	{
+		InheritResultRelInfo *parentInfo = root->inherit_result_rel_array[parentRTindex];
+		RelOptInfo *parentrelinfo = root->simple_rel_array[parentRTindex];
+
+		add_inherit_result_relation_info(root, childRTindex, childrel,
+										 parentInfo);
+
+		/*
+		 * Add junk attributes needed by this leaf child result relation, if
+		 * one.
+		 */
+		if (childrte->relkind != RELKIND_PARTITIONED_TABLE)
+			add_child_junk_attrs(root, childRTindex, childrel, parentrelinfo,
+								 parentrel);
+	}
 }
 
 /*
@@ -804,3 +879,564 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 
 	return true;
 }
+
+/*
+ * add_inherit_result_relation
+ *		Adds information to PlannerInfo about an inherited UPDATE/DELETE
+ *		result relation
+ */
+static void
+add_inherit_result_relation_info(PlannerInfo *root, Index rti,
+								 Relation relation,
+								 InheritResultRelInfo *parentInfo)
+{
+	InheritResultRelInfo *resultInfo = makeNode(InheritResultRelInfo);
+
+	if (parentInfo == NULL)
+	{
+		/* Root result relation. */
+		resultInfo->resultRelation = rti;
+		resultInfo->withCheckOptions = root->parse->withCheckOptions;
+		resultInfo->returningList = root->parse->returningList;
+		if (root->parse->commandType == CMD_UPDATE)
+		{
+			resultInfo->processed_tlist = root->processed_tlist;
+			resultInfo->update_tlist = root->update_tlist;
+		}
+	}
+	else
+	{
+		/* Child result relation. */
+		AppendRelInfo *appinfo = root->append_rel_array[rti];
+
+		Assert(appinfo != NULL);
+
+		resultInfo->resultRelation = rti;
+
+		if (parentInfo->withCheckOptions)
+			resultInfo->withCheckOptions = (List *)
+				adjust_appendrel_attrs(root,
+									   (Node *) parentInfo->withCheckOptions,
+									   1, &appinfo);
+		if (parentInfo->returningList)
+			resultInfo->returningList = (List *)
+				adjust_appendrel_attrs(root,
+									   (Node *) parentInfo->returningList,
+									   1, &appinfo);
+
+		/* Build UPDATE targetlist for this child. */
+		if (root->parse->commandType == CMD_UPDATE)
+		{
+			List    *tmp;
+
+			/*
+			 * First fix up any Vars in the parent's version of the top-level
+			 * targetlist.
+			 */
+			resultInfo->processed_tlist = (List *)
+				adjust_appendrel_attrs(root,
+									   (Node *) parentInfo->processed_tlist,
+									   1, &appinfo);
+
+			/*
+			 * Re-assign resnos to match child attnos.  The returned tlist may
+			 * have the individual entries in a different order than they were
+			 * in before because it will match the order of child's attributes,
+			 * which we need to make update_tlist below.  But we better not
+			 * overwrite resultInfo->processed_tlist, because that list must
+			 * be in the root parent attribute order to match the order of
+			 * top-level targetlist.
+			 */
+			tmp = adjust_inherited_tlist(resultInfo->processed_tlist, appinfo);
+			resultInfo->update_tlist = make_update_tlist(copyObject(tmp),
+														 rti, relation);
+		}
+	}
+
+	root->inherit_result_rels = lappend(root->inherit_result_rels, resultInfo);
+	Assert(root->inherit_result_rel_array);
+	Assert(root->inherit_result_rel_array[rti] == NULL);
+	root->inherit_result_rel_array[rti] = resultInfo;
+}
+
+/*
+ * Adjust the targetlist entries of an inherited UPDATE operation
+ *
+ * The input tlist is that of an UPDATE targeting the given parent table.
+ * Expressions of the individual target entries have already been fixed to
+ * convert any parent table Vars in them into child table Vars, but the
+ * target resnos still match the parent attnos, which we fix here to match
+ * the corresponding child table attnos.
+ *
+ * In some cases this can force us to re-order the tlist to preserve resno
+ * ordering, which make_update_targetlist() that will be called on this tlist
+ * later expects to be the case.
+ *
+ * Note that the caller must be okay with the input tlist being scribbled on.
+ */
+static List *
+adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
+{
+	bool		changed_it = false;
+	ListCell   *tl;
+	List	   *new_tlist;
+	bool		more;
+	int			attrno;
+
+	/* This should only happen for an inheritance case, not UNION ALL */
+	Assert(OidIsValid(context->parent_reloid));
+
+	/* Scan tlist and update resnos to match attnums of child rel */
+	foreach(tl, tlist)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(tl);
+		Var		   *childvar;
+
+		if (tle->resjunk)
+			continue;			/* ignore junk items */
+
+		/* Look up the translation of this column: it must be a Var */
+		if (tle->resno <= 0 ||
+			tle->resno > list_length(context->translated_vars))
+			elog(ERROR, "attribute %d of relation \"%s\" does not exist",
+				 tle->resno, get_rel_name(context->parent_reloid));
+		childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1);
+		if (childvar == NULL || !IsA(childvar, Var))
+			elog(ERROR, "attribute %d of relation \"%s\" does not exist",
+				 tle->resno, get_rel_name(context->parent_reloid));
+
+		if (tle->resno != childvar->varattno)
+		{
+			tle->resno = childvar->varattno;
+			changed_it = true;
+		}
+	}
+
+	/*
+	 * If we changed anything, re-sort the tlist by resno, and make sure
+	 * resjunk entries have resnos above the last real resno.  The sort
+	 * algorithm is a bit stupid, but for such a seldom-taken path, small is
+	 * probably better than fast.
+	 */
+	if (!changed_it)
+		return tlist;
+
+	new_tlist = NIL;
+	more = true;
+	for (attrno = 1; more; attrno++)
+	{
+		more = false;
+		foreach(tl, tlist)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tl);
+
+			if (tle->resjunk)
+				continue;		/* ignore junk items */
+
+			if (tle->resno == attrno)
+				new_tlist = lappend(new_tlist, tle);
+			else if (tle->resno > attrno)
+				more = true;
+		}
+	}
+
+	foreach(tl, tlist)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(tl);
+
+		if (!tle->resjunk)
+			continue;			/* here, ignore non-junk items */
+
+		tle->resno = attrno;
+		new_tlist = lappend(new_tlist, tle);
+		attrno++;
+	}
+
+	return new_tlist;
+}
+
+/*
+ * add_child_junk_attrs
+ *		Adds junk attributes needed by leaf child result relations to
+ *		identify tuples to be updated/deleted, and for each tuple also
+ *		the result relation to perform the operation on
+ *
+ * While preprocess_targetlist() would have added junk attributes needed to
+ * identify rows to be updated/deleted based on whatever the root parent says
+ * they are, not all leaf result relations may be able to use the same junk
+ * attributes.  For example, in the case of leaf result relations that are
+ * foreign tables, junk attributes to use are determined by their FDW's
+ * AddForeignUpdateTargets().
+ *
+ * Even though leaf result relations are scanned at the bottom of the plan
+ * tree, any junk attributes needed must be present in the top-level tlist,
+ * so to add a junk attribute for a given leaf result relation really means
+ * adding corresponding column of the top parent relation to the top-level
+ * targetlist from where it will be propagated back down to the leaf result
+ * relation.  In some cases, a leaf relation's junk attribute may be such that
+ * no column of the root parent can be mapped to it, in which case we must add
+ * "fake" parent columns to the targetlist and set things up to map those
+ * columns' vars to desired junk attribute expressions in the reltargets of
+ * leaf result relation that need them.  This logic of how leaf-level junk
+ * attributes are mapped to top-level level vars and back is present in
+ * add_inherit_junk_var().
+ *
+ * The leaf-level junk attribute that is added to identify the leaf result
+ * relation for each tuple to be updated/deleted is really a Const node
+ * containing an integer value that gives the index of the leaf result
+ * relation in the subquery's list of result relations.  This adds an
+ * entry named "__result_index" to the top-level tlist which wraps a fake
+ * parent var that maps back to the Const node for each leaf result
+ * relation.
+ */
+static void
+add_child_junk_attrs(PlannerInfo *root,
+					 Index childRTindex, Relation childrelation,
+					 RelOptInfo *parentrelinfo, Relation parentrelation)
+{
+	AppendRelInfo  *appinfo = root->append_rel_array[childRTindex];
+	ListCell	   *lc;
+	List		   *child_junk_attrs = NIL;
+
+	/*
+	 * For a non-leaf child relation, we simply need to bubble up to its
+	 * parent any entries containing its vars that would be added for junk
+	 * attributes of its own children.
+	 */
+	if (childrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		foreach(lc, root->inherit_junk_tlist)
+		{
+			TargetEntry *tle = lfirst(lc);
+			Var   *var = (Var *) tle->expr;
+
+			if (var->varno == childRTindex)
+				child_junk_attrs = lappend(child_junk_attrs, tle);
+		}
+	}
+	else
+	{
+		/* Leaf child case. */
+		RangeTblEntry  *childrte = root->simple_rte_array[childRTindex];
+		Query			parsetree;
+		Node		   *childexpr;
+		TargetEntry	   *tle;
+
+		/* The "__result_index" column. */
+		childexpr = (Node *) makeConst(INT4OID, -1, InvalidOid, sizeof(int32),
+									   Int32GetDatum(root->lastResultRelIndex++),
+									   false, true),
+		tle = makeTargetEntry((Expr *) childexpr, 1, "__result_index", true);
+		child_junk_attrs = lappend(child_junk_attrs, tle);
+
+		/*
+		 * Now call rewriteTargetListUD() to add junk attributes into the
+		 * parsetree.  We pass a slightly altered version of the original
+		 * parsetree to show the child result relation as the main target
+		 * relation.  It is assumed here that rewriteTargetListUD and any
+		 * code downstream to it do not inspect the parsetree beside to
+		 * figure out the varno to assign to the Vars that will be added
+		 * to the targetlist.
+		 */
+		memcpy(&parsetree, root->parse, sizeof(Query));
+		parsetree.resultRelation = childRTindex;
+		parsetree.targetList = NIL;
+		rewriteTargetListUD(&parsetree, childrte, childrelation);
+		child_junk_attrs = list_concat(child_junk_attrs,
+									   parsetree.targetList);
+	}
+
+	/* Add parent vars for each of the child junk attributes. */
+	foreach(lc, child_junk_attrs)
+	{
+		TargetEntry *tle = lfirst(lc);
+
+		Assert(tle->resjunk);
+
+		add_inherit_junk_var(root, tle->resname, (Node *) tle->expr,
+							 appinfo, parentrelinfo, parentrelation);
+	}
+}
+
+/*
+ * add_inherit_junk_var
+ *		Checks if the query's top-level tlist (root->processed_tlist) or
+ *		root->inherit_junk_tlist contains an entry for a junk attribute
+ *		with given name and if the parent var therein translates to
+ *		given child junk expression
+ *
+ * If not, add the parent var to appropriate list -- top-level tlist if parent
+ * is top-level parent, root->inherit_junk_tlist otherwise.
+ *
+ * If the parent var found or added is not for a real column or is a "fake"
+ * var, which will be he case if no real column of the parent translates to
+ * provided child expression, then add mapping information in provided
+ * AppendRelInfo to translate such fake parent var to provided child
+ * expression.
+ */
+static void
+add_inherit_junk_var(PlannerInfo *root, char *attrname, Node *child_expr,
+					 AppendRelInfo *appinfo,
+					 RelOptInfo *parentrelinfo, Relation parentrelation)
+{
+	AttrNumber	max_parent_attno = RelationGetNumberOfAttributes(parentrelation);
+	AttrNumber	max_child_attno = appinfo->num_child_cols;
+	ListCell   *lc;
+	Var		   *parent_var = NULL;
+	Index		parent_varno = parentrelinfo->relid;
+	AttrNumber	parent_attno;
+
+	Assert(appinfo && parent_varno == appinfo->parent_relid);
+
+	/*
+	 * The way we decide if a given parent var found in the targetlist is the
+	 * one that will give the desired child var back upon translation is to
+	 * check whether the child var refers to an inherited user column or a
+	 * system column that is same as the one that the parent var refers to.
+	 * If the child var refers to a fake column, parent var must likewise
+	 * refer to a fake column itself.
+	 *
+	 * There is a special case where the desired child expression is a Const
+	 * node wrapped in an entry named "__result_index", in which case, simply
+	 * finding an entry with that name containing a parent's var suffices.
+	 *
+	 * If no such parent var is found, we will add one.
+	 */
+	foreach(lc, list_concat_copy(root->inherit_junk_tlist,
+								 root->processed_tlist))
+	{
+		TargetEntry *tle = lfirst(lc);
+		Var	   *var = (Var *) tle->expr;
+		Var	   *child_var = (Var *) child_expr;
+
+		if (!tle->resjunk)
+			continue;
+
+		/* Ignore RETURNING expressions in the top-level tlist. */
+		if (tle->resname == NULL)
+			continue;
+
+		if (strcmp(attrname, tle->resname) != 0)
+			continue;
+
+		if (!IsA(var,  Var))
+			elog(ERROR, "junk column \"%s\" is not a Var", attrname);
+
+		/* Ignore junk vars of other relations. */
+		if (var->varno != parent_varno)
+			continue;
+
+		/* special case */
+		if (strcmp(attrname, "__result_index") == 0)
+		{
+			/* The parent var had better not be a normal user column. */
+			Assert(var->varattno > max_parent_attno);
+			parent_var = var;
+			break;
+		}
+
+		if (!IsA(child_expr, Var))
+			elog(ERROR, "child junk column \"%s\" is not a Var", attrname);
+
+		/*
+		 * So we found parent var referring to the column that the child wants
+		 * added, but check if that's really the case.
+		 */
+		if (var->vartype == child_var->vartype &&
+			var->vartypmod == child_var->vartypmod &&
+
+			(/* child var refers to same system column as parent var */
+			 (child_var->varattno <= 0 &&
+			  child_var->varattno == var->varattno) ||
+
+			 /* child var refers to same user column as parent var */
+			 (child_var->varattno > 0 &&
+			  child_var->varattno <= max_child_attno &&
+			  var->varattno == appinfo->parent_colnos[child_var->varattno]) ||
+
+			 /* both child var and parent var refer to "fake" column */
+			 (child_var->varattno > max_child_attno &&
+			  var->varattno > max_parent_attno)))
+		{
+			parent_var = var;
+			break;
+		}
+
+		/*
+		 * Getting here means that did find a parent column with the given
+		 * name but it's not equivalent to the child column we're trying
+		 * to add to the targetlist.  Adding a second var with child's type
+		 * would not be correct.
+		 */
+		elog(ERROR, "child junk column \"%s\" conflicts with parent junk column with same name",
+			 attrname);
+	}
+
+	/*
+	 * If no parent column matching the child column found in the targetlist,
+	 * add.
+	 */
+	if (parent_var == NULL)
+	{
+		TargetEntry *tle;
+		bool		fake_column = true;
+		AttrNumber	resno;
+		Oid			parent_vartype = exprType((Node *) child_expr);
+		int32		parent_vartypmod = exprTypmod((Node *) child_expr);
+		Oid			parent_varcollid = exprCollation((Node *) child_expr);
+
+		/*
+		 * If the child expression is either an inherited user column, or
+		 * wholerow, or ctid, it can be mapped to a parent var.  If the child
+		 * expression does not refer to a column, or a column that parent does
+		 * not contain, then we will need to make a "fake" parent column to
+		 * stand for the child expression.  We will set things up below using
+		 * the child's AppendRelInfo such that when translated, the fake parent
+		 * column becomes the child expression.  Note that these fake columns
+		 * don't leave the planner, because the parent's reltarget is never
+		 * actually computed during execution (see set_dummy_tlist_references()
+		 * and how it applies to Append and similar plan nodes).
+		 */
+		if (IsA(child_expr, Var))
+		{
+			Var   *child_var = (Var *) child_expr;
+
+			if (child_var->varattno > 0 &&
+				child_var->varattno <= appinfo->num_child_cols &&
+				appinfo->parent_colnos[child_var->varattno] > 0)
+			{
+				/* A user-defined parent column. */
+				parent_attno = appinfo->parent_colnos[child_var->varattno];
+				fake_column = false;
+			}
+			else if (child_var->varattno == 0)
+			{
+				/* wholerow */
+				parent_attno = 0;
+				parent_vartype = parentrelation->rd_rel->reltype;
+				fake_column = false;
+			}
+			else if (child_var->varattno == SelfItemPointerAttributeNumber)
+			{
+				/* ctid */
+				parent_attno = SelfItemPointerAttributeNumber;
+				fake_column = false;
+			}
+		}
+
+		/*
+		 * A fake parent column is represented by a Var with fake varattno.
+		 * We use attribute numbers starting from parent's max_attr + 1.
+		 */
+		if (fake_column)
+		{
+			int		array_size;
+
+			parent_attno = parentrelinfo->max_attr + 1;
+
+			/* Must expand attr_needed array for the new fake Var. */
+			array_size = parentrelinfo->max_attr - parentrelinfo->min_attr + 1;
+			parentrelinfo->attr_needed = (Relids *)
+					repalloc(parentrelinfo->attr_needed,
+							 (array_size + 1) * sizeof(Relids));
+			parentrelinfo->attr_widths = (int32 *)
+					repalloc(parentrelinfo->attr_widths,
+							 (array_size + 1) * sizeof(int32));
+			parentrelinfo->attr_needed[array_size] = NULL;
+			parentrelinfo->attr_widths[array_size] = 0;
+			parentrelinfo->max_attr += 1;
+		}
+
+		parent_var = makeVar(parent_varno, parent_attno, parent_vartype,
+							 parent_vartypmod, parent_varcollid, 0);
+
+		/*
+		 * Only the top-level parent's vars will make it into the top-level
+		 * tlist, so choose resno likewise.  Other TLEs containing vars of
+		 * intermediate parents only serve as placeholders for remembering
+		 * child junk attribute names and expressions so as to avoid re-adding
+		 * duplicates as the code at the beginning of this function does, so
+		 * their resnos don't need to be correct.
+		 */
+		if (parent_varno == root->parse->resultRelation)
+			resno = list_length(root->processed_tlist) + 1;
+		else
+			resno = 1;
+		tle = makeTargetEntry((Expr *) parent_var, resno, attrname, true);
+
+		root->inherit_junk_tlist = lappend(root->inherit_junk_tlist, tle);
+		if (parent_varno == root->parse->resultRelation)
+			root->processed_tlist = lappend(root->processed_tlist, tle);
+	}
+
+	/*
+	 * While appinfo->translated_vars contains child column vars mapped from
+	 * real parent column vars, we maintain a list of child expressions that
+	 * are mapped from fake parent vars in appinfo->translated_fake_vars.
+	 */
+	parent_attno = parent_var->varattno;
+	if (parent_attno > max_parent_attno)
+	{
+		int		fake_var_offset = max_parent_attno - parent_attno - 1;
+
+		/*
+		 * For parent's fake columns with attribute number smaller than the
+		 * current fake attno, we assume that they are not mapped to any
+		 * expression of this child, which is indicated by having a NULL in
+		 * the map.
+		 */
+		if (fake_var_offset > 0)
+		{
+			int		offset;
+
+			Assert(list_length(appinfo->translated_fake_vars) > 0);
+			for (offset = 0; offset < fake_var_offset; offset++)
+			{
+				/*
+				 * Don't accidentally overwrite other expressions of this
+				 * child.
+				 */
+				if (list_nth(appinfo->translated_fake_vars, offset) != NULL)
+					continue;
+
+				appinfo->translated_fake_vars =
+					lappend(appinfo->translated_fake_vars, NULL);
+			}
+
+			if (list_nth(appinfo->translated_fake_vars, offset) != NULL)
+				elog(ERROR, "fake attno %u of parent %u already mapped",
+					 parent_var->varattno, parent_varno);
+		}
+
+		appinfo->translated_fake_vars = lappend(appinfo->translated_fake_vars,
+												child_expr);
+	}
+}
+
+/*
+ * translate_fake_parent_var
+ * 		For a "fake" parent var, return corresponding child expression in
+ * 		appinfo->translated_fake_vars if one has been added, NULL const node
+ * 		otherwise
+ */
+Node *
+translate_fake_parent_var(Var *var, AppendRelInfo *appinfo)
+{
+	int		max_parent_attno = list_length(appinfo->translated_vars);
+	int		offset = var->varattno - max_parent_attno - 1;
+	Node   *result = NULL;
+
+	if (offset < list_length(appinfo->translated_fake_vars))
+		result = (Node *) list_nth(appinfo->translated_fake_vars, offset);
+
+	/*
+	 * It's possible for some fake parent vars to map to a valid expression
+	 * in only some child relations but not in others.  In that case, we
+	 * return a NULL const node for those other relations.
+	 */
+	if (result == NULL)
+		return (Node *) makeNullConst(var->vartype, var->vartypmod,
+									  var->varcollid);
+
+	return result;
+}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 2fee5c5..2b5d5a6 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3545,8 +3545,6 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	double		total_size;
 	ListCell   *lc;
 
-	Assert(list_length(resultRelations) == list_length(subpaths));
-	Assert(list_length(resultRelations) == list_length(subroots));
 	Assert(withCheckOptionLists == NIL ||
 		   list_length(resultRelations) == list_length(withCheckOptionLists));
 	Assert(returningLists == NIL ||
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index f9d0d67..0a524b3 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1435,18 +1435,11 @@ relation_excluded_by_constraints(PlannerInfo *root,
 
 			/*
 			 * When constraint_exclusion is set to 'partition' we only handle
-			 * appendrel members.  Normally, they are RELOPT_OTHER_MEMBER_REL
-			 * relations, but we also consider inherited target relations as
-			 * appendrel members for the purposes of constraint exclusion
-			 * (since, indeed, they were appendrel members earlier in
-			 * inheritance_planner).
-			 *
-			 * In both cases, partition pruning was already applied, so there
-			 * is no need to consider the rel's partition constraints here.
+			 * appendrel members.  Partition pruning has already been applied,
+			 * so there is no need to consider the rel's partition constraints
+			 * here.
 			 */
-			if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
-				(rel->relid == root->parse->resultRelation &&
-				 root->inhTargetKind != INHKIND_NONE))
+			if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
 				break;			/* appendrel member, so process it */
 			return false;
 
@@ -1459,9 +1452,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
 			 * its partition constraints haven't been considered yet, so
 			 * include them in the processing here.
 			 */
-			if (rel->reloptkind == RELOPT_BASEREL &&
-				!(rel->relid == root->parse->resultRelation &&
-				  root->inhTargetKind != INHKIND_NONE))
+			if (rel->reloptkind == RELOPT_BASEREL)
 				include_partition = true;
 			break;				/* always try to exclude */
 	}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index a203e6f..ed9c477 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -183,6 +183,15 @@ expand_planner_arrays(PlannerInfo *root, int add_size)
 			palloc0(sizeof(AppendRelInfo *) * new_size);
 	}
 
+	if (root->inherit_result_rel_array)
+	{
+		root->inherit_result_rel_array = (InheritResultRelInfo **)
+			repalloc(root->inherit_result_rel_array,
+					 sizeof(InheritResultRelInfo *) * new_size);
+		MemSet(root->inherit_result_rel_array + root->simple_rel_array_size,
+			   0, sizeof(InheritResultRelInfo *) * add_size);
+	}
+
 	root->simple_rel_array_size = new_size;
 }
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60dd80c..c5d5049 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -4581,7 +4581,7 @@ set_deparse_plan(deparse_namespace *dpns, Plan *plan)
 	else if (IsA(plan, MergeAppend))
 		dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans);
 	else if (IsA(plan, ModifyTable))
-		dpns->outer_plan = linitial(((ModifyTable *) plan)->plans);
+		dpns->outer_plan = ((ModifyTable *) plan)->subplan;
 	else
 		dpns->outer_plan = outerPlan(plan);
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8926818..c90f20d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1176,19 +1176,25 @@ typedef struct ModifyTableState
 	CmdType		operation;		/* INSERT, UPDATE, or DELETE */
 	bool		canSetTag;		/* do we set the command tag/es_processed? */
 	bool		mt_done;		/* are we done? */
-	PlanState **mt_plans;		/* subplans (one per target rel) */
-	int			mt_nplans;		/* number of plans in the array */
-	int			mt_whichplan;	/* which one is being executed (0..n-1) */
-	TupleTableSlot **mt_scans;	/* input tuple corresponding to underlying
-								 * plans */
-	ResultRelInfo *resultRelInfo;	/* per-subplan target relations */
+	PlanState  *mt_subplan;		/* subplan state */
+	int			mt_nrels;		/* number of result rels in the arrays */
+	TupleTableSlot **mt_scans;  /* result relation specific tuple slots for
+								 * holding the subplan's output tuple */
+	ResultRelInfo *resultRelInfo;	/* Target relations */
 	ResultRelInfo *rootResultRelInfo;	/* root target relation (partitioned
 										 * table root) */
-	List	  **mt_arowmarks;	/* per-subplan ExecAuxRowMark lists */
+	List	   *mt_arowmarks;	/* ExecAuxRowMark list */
 	EPQState	mt_epqstate;	/* for evaluating EvalPlanQual rechecks */
 	bool		fireBSTriggers; /* do we need to fire stmt triggers? */
 
 	/*
+	 * For UPDATE and DELETE, resno of the TargetEntry corresponding to
+	 * the "__result_index" junk attribute present in the subplan's
+	 * targetlist.
+	 */
+	int			mt_resultIndexAttno;
+
+	/*
 	 * Slot for storing tuples in the root partitioned table's rowtype during
 	 * an UPDATE of a partitioned table.
 	 */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b..e6c96b6 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_InheritResultRelInfo,
 	T_PlaceHolderInfo,
 	T_MinMaxAggInfo,
 	T_PlannerParamItem,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 5ff40ef..72b7f96 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -81,18 +81,6 @@ typedef enum UpperRelationKind
 	/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
 } UpperRelationKind;
 
-/*
- * This enum identifies which type of relation is being planned through the
- * inheritance planner.  INHKIND_NONE indicates the inheritance planner
- * was not used.
- */
-typedef enum InheritanceKind
-{
-	INHKIND_NONE,
-	INHKIND_INHERITED,
-	INHKIND_PARTITIONED
-} InheritanceKind;
-
 /*----------
  * PlannerGlobal
  *		Global information for planning/optimization
@@ -219,6 +207,14 @@ struct PlannerInfo
 	struct AppendRelInfo **append_rel_array;
 
 	/*
+	 * Same length as other "simple" rel arrays and holds pointers to
+	 * InheritResultRelInfo for this subquery's result relations indexed by
+	 * RT index, or NULL if the rel is not a result relation.  The array is not
+	 * allocated unless the query is an inherited UPDATE/DELETE.
+	 */
+	struct InheritResultRelInfo **inherit_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
 	 * we need to form.  This is computed in make_one_rel, just before we
@@ -289,6 +285,8 @@ struct PlannerInfo
 	 */
 	List	   *append_rel_list;	/* list of AppendRelInfos */
 
+	List	   *inherit_result_rels;	/* List of InheritResultRelInfo */
+
 	List	   *rowMarks;		/* list of PlanRowMarks */
 
 	List	   *placeholder_list;	/* list of PlaceHolderInfos */
@@ -331,6 +329,9 @@ struct PlannerInfo
 	 */
 	List	   *update_tlist;
 
+	/* Scratch space for inherit.c: add_inherit_junk_var() */
+	List	   *inherit_junk_tlist;	/* List of TargetEntry */
+
 	/* Fields filled during create_plan() for use in setrefs.c */
 	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
 	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
@@ -346,9 +347,6 @@ struct PlannerInfo
 	Index		qual_security_level;	/* minimum security_level for quals */
 	/* Note: qual_security_level is zero if there are no securityQuals */
 
-	InheritanceKind inhTargetKind;	/* indicates if the target relation is an
-									 * inheritance child or partition or a
-									 * partitioned table */
 	bool		hasJoinRTEs;	/* true if any RTEs are RTE_JOIN kind */
 	bool		hasLateralRTEs; /* true if any RTEs are marked LATERAL */
 	bool		hasHavingQual;	/* true if havingQual was non-null */
@@ -369,6 +367,9 @@ struct PlannerInfo
 
 	/* Does this query modify any partition key columns? */
 	bool		partColsUpdated;
+
+	/* Highest result relation index assigned in this subquery */
+	int			lastResultRelIndex;
 };
 
 
@@ -2265,6 +2266,13 @@ typedef struct AppendRelInfo
 	List	   *translated_vars;	/* Expressions in the child's Vars */
 
 	/*
+	 * The following contains expressions that the child relation is expected
+	 * to output for each "fake" parent Var that add_inherit_junk_var() adds
+	 * to the parent's reltarget; also see translate_fake_parent_var().
+	 */
+	List	   *translated_fake_vars;
+
+	/*
 	 * This array simplifies translations in the reverse direction, from
 	 * child's column numbers to parent's.  The entry at [ccolno - 1] is the
 	 * 1-based parent column number for child column ccolno, or zero if that
@@ -2282,6 +2290,41 @@ typedef struct AppendRelInfo
 } AppendRelInfo;
 
 /*
+ * InheritResultRelInfo
+ *		Information about result relations of an inherited UPDATE/DELETE
+ *		operation
+ *
+ * If the main target relation is an inheritance parent, we build an
+ * InheritResultRelInfo for it and for every child result relation resulting
+ * from expanding it.  This is to store the information relevant to each
+ * result relation that must be added to the ModifyTable, such as its update
+ * targetlist, WITH CHECK OPTIONS, and RETURNING expression lists.  For the
+ * main result relation (root inheritance parent), that information is same
+ * as what's in Query and PlannerInfo.  For child result relations, we make
+ * copies of those expressions with appropriate translation of any Vars.
+ * Also, update_tlist for a given child relation has been made such that
+ * resnos of the TLEs contained in it match the child relation attribute
+ * numbers.
+ *
+ * While it's okay for the code outside of the core planner to look at
+ * update_tlist, processed_tlist is only kept around for internal planner use.
+ * For example, an FDW's PlanDirectModify() may look at update_tlist to check
+ * if the assigned expressions are pushable.
+ */
+typedef struct InheritResultRelInfo
+{
+	NodeTag		type;
+
+	Index		resultRelation;
+	List	   *withCheckOptions;
+	List	   *returningList;
+
+	/* Only valid for UPDATE. */
+	List	   *processed_tlist;
+	List	   *update_tlist;
+} InheritResultRelInfo;
+
+/*
  * 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 fba746e..bdb91b3 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -202,7 +202,7 @@ typedef struct ProjectSet
 
 /* ----------------
  *	 ModifyTable node -
- *		Apply rows produced by subplan(s) to result table(s),
+ *		Apply rows produced by subplan to result table(s),
  *		by inserting, updating, or deleting.
  *
  * If the originally named target table is a partitioned table, both
@@ -226,7 +226,7 @@ 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 */
+	Plan	   *subplan;		/* plan producing source data */
 	List	   *withCheckOptionLists;	/* per-target-table WCO lists */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *updateTargetLists;	/* per-target-table UPDATE tlists */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index d73be2a..5d43cf6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1362,13 +1362,15 @@ typedef struct InferenceElem
  * column for the item; so there may be missing or out-of-order resnos.
  * It is even legal to have duplicated resnos; consider
  *		UPDATE table SET arraycol[1] = ..., arraycol[2] = ..., ...
- * The two meanings come together in the executor, because the planner
- * transforms INSERT/UPDATE tlists into a normalized form with exactly
- * one entry for each column of the destination table.  Before that's
- * happened, however, it is risky to assume that resno == position.
- * Generally get_tle_by_resno() should be used rather than list_nth()
- * to fetch tlist entries by resno, and only in SELECT should you assume
- * that resno is a unique identifier.
+ * For INSERT, the planner normalizes the tlist by filling in NULL constants
+ * for any columns not assigned values in the original tlist.  For UPDATE, the
+ * possibility of multiple destination relations due to inheritance means that
+ * there’s no one unique set of attribute numbers to normalize the tlist with,
+ * so it’s left unchanged except reordering the items in to appear in root
+ * table's attribute numbers.  Given these irregularities, it is risky to
+ * assume that resno == position.  Generally get_tle_by_resno() should be used
+ * rather than list_nth() to fetch tlist entries by resno, and only in SELECT
+ * should you assume that resno is a unique identifier.
  *
  * resname is required to represent the correct column name in non-resjunk
  * entries of top-level SELECT targetlists, since it will be used as the
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index d6a27a6..08454ba 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -22,6 +22,8 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel,
 										   Index parentRTindex, Index childRTindex);
 extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
 									int nappinfos, AppendRelInfo **appinfos);
+extern Node *adjust_target_appendrel_attrs(PlannerInfo *root, Node *node,
+									AppendRelInfo *appinfo);
 extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 											   Relids child_relids,
 											   Relids top_parent_relids);
@@ -31,5 +33,6 @@ extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 											 Relids child_relids, Relids top_parent_relids);
 extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
 											   Relids relids, int *nappinfos);
+extern Node *translate_fake_parent_var(Var *var, AppendRelInfo *appinfo);
 
 #endif							/* APPENDINFO_H */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index f3cefe6..117efe4 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -115,4 +115,10 @@ extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
 extern void record_plan_type_dependency(PlannerInfo *root, Oid typid);
 extern bool extract_query_dependencies_walker(Node *node, PlannerInfo *root);
 
+/*
+ * prototype for plan/planner.c
+ */
+extern bool is_result_relation(Index relid, PlannerInfo *root);
+extern List *get_result_update_tlist(PlannerInfo *root, Index result_relation);
+
 #endif							/* PLANMAIN_H */
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index beb7dbb..02d66a0 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -58,4 +58,6 @@ extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
 
 extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
 
+
+
 #endif							/* PLANNER_H */
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 19c9230..01e5c54 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,6 +36,7 @@ extern Relids get_relids_for_join(Query *query, int joinrelid);
  * prototypes for preptlist.c
  */
 extern List *preprocess_targetlist(PlannerInfo *root);
+extern List *make_update_tlist(List *tlist, Index result_relation, Relation rel);
 
 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 94e43c3..d4b112c 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -545,27 +545,25 @@ create table some_tab_child () inherits (some_tab);
 insert into some_tab_child values(1,2);
 explain (verbose, costs off)
 update some_tab set a = a + 1 where false;
-           QUERY PLAN           
---------------------------------
+                   QUERY PLAN                    
+-------------------------------------------------
  Update on public.some_tab
-   Update on public.some_tab
    ->  Result
-         Output: (a + 1), ctid
+         Output: (some_tab.a + 1), some_tab.ctid
          One-Time Filter: false
-(5 rows)
+(4 rows)
 
 update some_tab set a = a + 1 where false;
 explain (verbose, costs off)
 update some_tab set a = a + 1 where false returning b, a;
-           QUERY PLAN           
---------------------------------
+                   QUERY PLAN                    
+-------------------------------------------------
  Update on public.some_tab
-   Output: b, a
-   Update on public.some_tab
+   Output: some_tab.b, some_tab.a
    ->  Result
-         Output: (a + 1), ctid
+         Output: (some_tab.a + 1), some_tab.ctid
          One-Time Filter: false
-(6 rows)
+(5 rows)
 
 update some_tab set a = a + 1 where false returning b, a;
  b | a 
@@ -670,7 +668,7 @@ explain update parted_tab set a = 2 where false;
                        QUERY PLAN                       
 --------------------------------------------------------
  Update on parted_tab  (cost=0.00..0.00 rows=0 width=0)
-   ->  Result  (cost=0.00..0.00 rows=0 width=0)
+   ->  Result  (cost=0.00..0.00 rows=0 width=10)
          One-Time Filter: false
 (3 rows)
 
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 1338b2b..50f3f62 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -214,7 +214,7 @@ explain (costs off, format json) insert into insertconflicttest values (0, 'Bilb
        "Plans": [                                                      +
          {                                                             +
            "Node Type": "Result",                                      +
-           "Parent Relationship": "Member",                            +
+           "Parent Relationship": "Source",                            +
            "Parallel Aware": false                                     +
          }                                                             +
        ]                                                               +
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 585e724..5c84d37 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -1926,37 +1926,27 @@ WHERE EXISTS (
     FROM int4_tbl,
          LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss
     WHERE prt1_l.c IS NULL);
-                          QUERY PLAN                           
----------------------------------------------------------------
+                        QUERY PLAN                        
+----------------------------------------------------------
  Delete on prt1_l
    Delete on prt1_l_p1 prt1_l_1
    Delete on prt1_l_p3_p1 prt1_l_2
    Delete on prt1_l_p3_p2 prt1_l_3
    ->  Nested Loop Semi Join
-         ->  Seq Scan on prt1_l_p1 prt1_l_1
-               Filter: (c IS NULL)
-         ->  Nested Loop
-               ->  Seq Scan on int4_tbl
-               ->  Subquery Scan on ss
-                     ->  Limit
-                           ->  Seq Scan on int8_tbl
-   ->  Nested Loop Semi Join
-         ->  Seq Scan on prt1_l_p3_p1 prt1_l_2
-               Filter: (c IS NULL)
-         ->  Nested Loop
-               ->  Seq Scan on int4_tbl
-               ->  Subquery Scan on ss_1
-                     ->  Limit
-                           ->  Seq Scan on int8_tbl int8_tbl_1
-   ->  Nested Loop Semi Join
-         ->  Seq Scan on prt1_l_p3_p2 prt1_l_3
-               Filter: (c IS NULL)
-         ->  Nested Loop
-               ->  Seq Scan on int4_tbl
-               ->  Subquery Scan on ss_2
-                     ->  Limit
-                           ->  Seq Scan on int8_tbl int8_tbl_2
-(28 rows)
+         ->  Append
+               ->  Seq Scan on prt1_l_p1 prt1_l_1
+                     Filter: (c IS NULL)
+               ->  Seq Scan on prt1_l_p3_p1 prt1_l_2
+                     Filter: (c IS NULL)
+               ->  Seq Scan on prt1_l_p3_p2 prt1_l_3
+                     Filter: (c IS NULL)
+         ->  Materialize
+               ->  Nested Loop
+                     ->  Seq Scan on int4_tbl
+                     ->  Subquery Scan on ss
+                           ->  Limit
+                                 ->  Seq Scan on int8_tbl
+(18 rows)
 
 --
 -- negative testcases
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 50d2a7e..8f1d36c 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2435,74 +2435,43 @@ deallocate ab_q6;
 insert into ab values (1,2);
 explain (analyze, costs off, summary off, timing off)
 update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
-                                     QUERY PLAN                                      
--------------------------------------------------------------------------------------
+                                        QUERY PLAN                                         
+-------------------------------------------------------------------------------------------
  Update on ab_a1 (actual rows=0 loops=1)
    Update on ab_a1_b1 ab_a1_1
    Update on ab_a1_b2 ab_a1_2
    Update on ab_a1_b3 ab_a1_3
-   ->  Nested Loop (actual rows=0 loops=1)
-         ->  Append (actual rows=1 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
-                     Recheck Cond: (a = 1)
-                     Heap Blocks: exact=1
-                     ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
-         ->  Materialize (actual rows=0 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
    ->  Nested Loop (actual rows=1 loops=1)
          ->  Append (actual rows=1 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+               ->  Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
                      Recheck Cond: (a = 1)
                      ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
                            Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
-                     Recheck Cond: (a = 1)
-                     Heap Blocks: exact=1
-                     ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-         ->  Materialize (actual rows=1 loops=1)
                ->  Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
                      Recheck Cond: (a = 1)
                      Heap Blocks: exact=1
                      ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
                            Index Cond: (a = 1)
-   ->  Nested Loop (actual rows=0 loops=1)
-         ->  Append (actual rows=1 loops=1)
-               ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
-                     Recheck Cond: (a = 1)
-                     Heap Blocks: exact=1
-                     ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
-                     Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
-                           Index Cond: (a = 1)
-         ->  Materialize (actual rows=0 loops=1)
                ->  Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
                      Recheck Cond: (a = 1)
                      ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
                            Index Cond: (a = 1)
-(65 rows)
+         ->  Materialize (actual rows=1 loops=1)
+               ->  Append (actual rows=1 loops=1)
+                     ->  Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
+                           Recheck Cond: (a = 1)
+                           ->  Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
+                                 Index Cond: (a = 1)
+                     ->  Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
+                           Recheck Cond: (a = 1)
+                           Heap Blocks: exact=1
+                           ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
+                                 Index Cond: (a = 1)
+                     ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+                           Recheck Cond: (a = 1)
+                           ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
+                                 Index Cond: (a = 1)
+(34 rows)
 
 table ab;
  a | b 
@@ -2523,29 +2492,12 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
    Update on ab_a1_b3 ab_a1_3
    InitPlan 1 (returns $0)
      ->  Result (actual rows=1 loops=1)
-   ->  Nested Loop (actual rows=1 loops=1)
-         ->  Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
-         ->  Materialize (actual rows=1 loops=1)
-               ->  Append (actual rows=1 loops=1)
-                     ->  Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
-                           Filter: (b = $0)
-   ->  Nested Loop (actual rows=1 loops=1)
-         ->  Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
-         ->  Materialize (actual rows=1 loops=1)
-               ->  Append (actual rows=1 loops=1)
-                     ->  Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
-                           Filter: (b = $0)
-                     ->  Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
-                           Filter: (b = $0)
-   ->  Nested Loop (actual rows=1 loops=1)
-         ->  Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
-         ->  Materialize (actual rows=1 loops=1)
+   ->  Nested Loop (actual rows=3 loops=1)
+         ->  Append (actual rows=3 loops=1)
+               ->  Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
+               ->  Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
+               ->  Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
+         ->  Materialize (actual rows=1 loops=3)
                ->  Append (actual rows=1 loops=1)
                      ->  Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
                            Filter: (b = $0)
@@ -2553,7 +2505,7 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
                            Filter: (b = $0)
                      ->  Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
                            Filter: (b = $0)
-(36 rows)
+(19 rows)
 
 select tableoid::regclass, * from ab;
  tableoid | a | b 
@@ -3392,28 +3344,30 @@ explain (costs off) select * from pp_lp where a = 1;
 (5 rows)
 
 explain (costs off) update pp_lp set value = 10 where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Update on pp_lp
    Update on pp_lp1 pp_lp_1
    Update on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 explain (costs off) delete from pp_lp where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Delete on pp_lp
    Delete on pp_lp1 pp_lp_1
    Delete on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 set constraint_exclusion = 'off'; -- this should not affect the result.
 explain (costs off) select * from pp_lp where a = 1;
@@ -3427,28 +3381,30 @@ explain (costs off) select * from pp_lp where a = 1;
 (5 rows)
 
 explain (costs off) update pp_lp set value = 10 where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Update on pp_lp
    Update on pp_lp1 pp_lp_1
    Update on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 explain (costs off) delete from pp_lp where a = 1;
-            QUERY PLAN            
-----------------------------------
+               QUERY PLAN               
+----------------------------------------
  Delete on pp_lp
    Delete on pp_lp1 pp_lp_1
    Delete on pp_lp2 pp_lp_2
-   ->  Seq Scan on pp_lp1 pp_lp_1
-         Filter: (a = 1)
-   ->  Seq Scan on pp_lp2 pp_lp_2
-         Filter: (a = 1)
-(7 rows)
+   ->  Append
+         ->  Seq Scan on pp_lp1 pp_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on pp_lp2 pp_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 drop table pp_lp;
 -- Ensure enable_partition_prune does not affect non-partitioned tables.
@@ -3472,28 +3428,31 @@ explain (costs off) select * from inh_lp where a = 1;
 (5 rows)
 
 explain (costs off) update inh_lp set value = 10 where a = 1;
-             QUERY PLAN             
-------------------------------------
+                   QUERY PLAN                   
+------------------------------------------------
  Update on inh_lp
-   Update on inh_lp
-   Update on inh_lp1 inh_lp_1
-   ->  Seq Scan on inh_lp
-         Filter: (a = 1)
-   ->  Seq Scan on inh_lp1 inh_lp_1
-         Filter: (a = 1)
-(7 rows)
+   Update on inh_lp inh_lp_1
+   Update on inh_lp1 inh_lp_2
+   ->  Result
+         ->  Append
+               ->  Seq Scan on inh_lp inh_lp_1
+                     Filter: (a = 1)
+               ->  Seq Scan on inh_lp1 inh_lp_2
+                     Filter: (a = 1)
+(9 rows)
 
 explain (costs off) delete from inh_lp where a = 1;
-             QUERY PLAN             
-------------------------------------
+                QUERY PLAN                
+------------------------------------------
  Delete on inh_lp
-   Delete on inh_lp
-   Delete on inh_lp1 inh_lp_1
-   ->  Seq Scan on inh_lp
-         Filter: (a = 1)
-   ->  Seq Scan on inh_lp1 inh_lp_1
-         Filter: (a = 1)
-(7 rows)
+   Delete on inh_lp inh_lp_1
+   Delete on inh_lp1 inh_lp_2
+   ->  Append
+         ->  Seq Scan on inh_lp inh_lp_1
+               Filter: (a = 1)
+         ->  Seq Scan on inh_lp1 inh_lp_2
+               Filter: (a = 1)
+(8 rows)
 
 -- Ensure we don't exclude normal relations when we only expect to exclude
 -- inheritance children
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 9506aae..b02a682 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1632,19 +1632,21 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2);
 --
 SET SESSION AUTHORIZATION regress_rls_bob;
 EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
-                  QUERY PLAN                   
------------------------------------------------
+                        QUERY PLAN                         
+-----------------------------------------------------------
  Update on t1
-   Update on t1
-   Update on t2 t1_1
-   Update on t3 t1_2
-   ->  Seq Scan on t1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t2 t1_1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t3 t1_2
-         Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+   Update on t1 t1_1
+   Update on t2 t1_2
+   Update on t3 t1_3
+   ->  Result
+         ->  Append
+               ->  Seq Scan on t1 t1_1
+                     Filter: (((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t2 t1_2
+                     Filter: (((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t3 t1_3
+                     Filter: (((a % 2) = 0) AND f_leak(b))
+(12 rows)
 
 UPDATE t1 SET b = b || b WHERE f_leak(b);
 NOTICE:  f_leak => bbb
@@ -1722,31 +1724,27 @@ NOTICE:  f_leak => cde
 NOTICE:  f_leak => yyyyyy
 EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
-                           QUERY PLAN                            
------------------------------------------------------------------
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
  Update on t1
-   Update on t1
-   Update on t2 t1_1
-   Update on t3 t1_2
-   ->  Nested Loop
-         ->  Seq Scan on t1
-               Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Seq Scan on t2
-               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-   ->  Nested Loop
-         ->  Seq Scan on t2 t1_1
-               Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Seq Scan on t2
-               Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+   Update on t1 t1_1
+   Update on t2 t1_2
+   Update on t3 t1_3
    ->  Nested Loop
-         ->  Seq Scan on t3 t1_2
-               Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
          ->  Seq Scan on t2
                Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-(19 rows)
+         ->  Append
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t2 t1_2
+                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+               ->  Seq Scan on t3 t1_3
+                     Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+(14 rows)
 
 UPDATE t1 SET b=t1.b FROM t2
 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+NOTICE:  f_leak => cde
 EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
 WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
                               QUERY PLAN                               
@@ -1795,46 +1793,30 @@ NOTICE:  f_leak => cde
 EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
 WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
 AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
-                              QUERY PLAN                               
------------------------------------------------------------------------
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
  Update on t1 t1_1
-   Update on t1 t1_1
-   Update on t2 t1_1_1
-   Update on t3 t1_1_2
+   Update on t1 t1_1_1
+   Update on t2 t1_1_2
+   Update on t3 t1_1_3
    ->  Nested Loop
          Join Filter: (t1_1.b = t1_2.b)
-         ->  Seq Scan on t1 t1_1
-               Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Append
-               ->  Seq Scan on t1 t1_2_1
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t2 t1_2_2
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t3 t1_2_3
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-   ->  Nested Loop
-         Join Filter: (t1_1_1.b = t1_2.b)
-         ->  Seq Scan on t2 t1_1_1
-               Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
          ->  Append
-               ->  Seq Scan on t1 t1_2_1
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t2 t1_2_2
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t3 t1_2_3
+               ->  Seq Scan on t1 t1_1_1
                      Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-   ->  Nested Loop
-         Join Filter: (t1_1_2.b = t1_2.b)
-         ->  Seq Scan on t3 t1_1_2
-               Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-         ->  Append
-               ->  Seq Scan on t1 t1_2_1
+               ->  Seq Scan on t2 t1_1_2
                      Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t2 t1_2_2
+               ->  Seq Scan on t3 t1_1_3
                      Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-               ->  Seq Scan on t3 t1_2_3
-                     Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-(37 rows)
+         ->  Materialize
+               ->  Append
+                     ->  Seq Scan on t1 t1_2_1
+                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+                     ->  Seq Scan on t2 t1_2_2
+                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+                     ->  Seq Scan on t3 t1_2_3
+                           Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(21 rows)
 
 UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
 WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
@@ -1843,8 +1825,6 @@ NOTICE:  f_leak => daddad_updt
 NOTICE:  f_leak => daddad_updt
 NOTICE:  f_leak => defdef
 NOTICE:  f_leak => defdef
-NOTICE:  f_leak => daddad_updt
-NOTICE:  f_leak => defdef
  id  | a |      b      | id  | a |      b      |        t1_1         |        t1_2         
 -----+---+-------------+-----+---+-------------+---------------------+---------------------
  104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt)
@@ -1880,19 +1860,20 @@ EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
 (3 rows)
 
 EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
-                  QUERY PLAN                   
------------------------------------------------
+                     QUERY PLAN                      
+-----------------------------------------------------
  Delete on t1
-   Delete on t1
-   Delete on t2 t1_1
-   Delete on t3 t1_2
-   ->  Seq Scan on t1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t2 t1_1
-         Filter: (((a % 2) = 0) AND f_leak(b))
-   ->  Seq Scan on t3 t1_2
-         Filter: (((a % 2) = 0) AND f_leak(b))
-(10 rows)
+   Delete on t1 t1_1
+   Delete on t2 t1_2
+   Delete on t3 t1_3
+   ->  Append
+         ->  Seq Scan on t1 t1_1
+               Filter: (((a % 2) = 0) AND f_leak(b))
+         ->  Seq Scan on t2 t1_2
+               Filter: (((a % 2) = 0) AND f_leak(b))
+         ->  Seq Scan on t3 t1_3
+               Filter: (((a % 2) = 0) AND f_leak(b))
+(11 rows)
 
 DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
 NOTICE:  f_leak => bbbbbb_updt
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 13d0d2a..8111cad 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1572,26 +1572,21 @@ UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
                                QUERY PLAN                                
 -------------------------------------------------------------------------
  Update on base_tbl_parent
-   Update on base_tbl_parent
-   Update on base_tbl_child base_tbl_parent_1
-   ->  Hash Join
-         Hash Cond: (other_tbl_parent.id = base_tbl_parent.a)
-         ->  Append
-               ->  Seq Scan on other_tbl_parent other_tbl_parent_1
-               ->  Seq Scan on other_tbl_child other_tbl_parent_2
-         ->  Hash
-               ->  Seq Scan on base_tbl_parent
+   Update on base_tbl_parent base_tbl_parent_1
+   Update on base_tbl_child base_tbl_parent_2
    ->  Merge Join
-         Merge Cond: (base_tbl_parent_1.a = other_tbl_parent.id)
+         Merge Cond: (base_tbl_parent.a = other_tbl_parent.id)
          ->  Sort
-               Sort Key: base_tbl_parent_1.a
-               ->  Seq Scan on base_tbl_child base_tbl_parent_1
+               Sort Key: base_tbl_parent.a
+               ->  Append
+                     ->  Seq Scan on base_tbl_parent base_tbl_parent_1
+                     ->  Seq Scan on base_tbl_child base_tbl_parent_2
          ->  Sort
                Sort Key: other_tbl_parent.id
                ->  Append
                      ->  Seq Scan on other_tbl_parent other_tbl_parent_1
                      ->  Seq Scan on other_tbl_child other_tbl_parent_2
-(20 rows)
+(15 rows)
 
 UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
 SELECT * FROM ONLY base_tbl_parent ORDER BY a;
@@ -2301,42 +2296,45 @@ SELECT * FROM v1 WHERE a=8;
 
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
-                                                        QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
+                                                              QUERY PLAN                                                               
+---------------------------------------------------------------------------------------------------------------------------------------
  Update on public.t1
-   Update on public.t1
-   Update on public.t11 t1_1
-   Update on public.t12 t1_2
-   Update on public.t111 t1_3
-   ->  Index Scan using t1_a_idx on public.t1
-         Output: 100, t1.ctid
-         Index Cond: ((t1.a > 5) AND (t1.a < 7))
-         Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
-         SubPlan 1
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_1
-                       Filter: (t12_1.a = t1.a)
-                 ->  Seq Scan on public.t111 t12_2
-                       Filter: (t12_2.a = t1.a)
-         SubPlan 2
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_4
-                       Output: t12_4.a
-                 ->  Seq Scan on public.t111 t12_5
-                       Output: t12_5.a
-   ->  Index Scan using t11_a_idx on public.t11 t1_1
-         Output: 100, t1_1.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.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.ctid
-         Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
-         Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+   Update on public.t1 t1_1
+   Update on public.t11 t1_2
+   Update on public.t12 t1_3
+   Update on public.t111 t1_4
+   ->  Result
+         Output: 100, t1.ctid, (0)
+         ->  Append
+               ->  Index Scan using t1_a_idx on public.t1 t1_1
+                     Output: t1_1.ctid, 0
+                     Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
+                     Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+                     SubPlan 1
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_1
+                                   Filter: (t12_1.a = t1_1.a)
+                             ->  Seq Scan on public.t111 t12_2
+                                   Filter: (t12_2.a = t1_1.a)
+                     SubPlan 2
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_4
+                                   Output: t12_4.a
+                             ->  Seq Scan on public.t111 t12_5
+                                   Output: t12_5.a
+               ->  Index Scan using t11_a_idx on public.t11 t1_2
+                     Output: t1_2.ctid, 1
+                     Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
+                     Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+               ->  Index Scan using t12_a_idx on public.t12 t1_3
+                     Output: t1_3.ctid, 2
+                     Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
+                     Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+               ->  Index Scan using t111_a_idx on public.t111 t1_4
+                     Output: t1_4.ctid, 3
+                     Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
+                     Filter: ((t1_4.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(36 rows)
 
 UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
 SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@@ -2351,42 +2349,45 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
 
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
-                                               QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
+                                                     QUERY PLAN                                                      
+---------------------------------------------------------------------------------------------------------------------
  Update on public.t1
-   Update on public.t1
-   Update on public.t11 t1_1
-   Update on public.t12 t1_2
-   Update on public.t111 t1_3
-   ->  Index Scan using t1_a_idx on public.t1
-         Output: (t1.a + 1), t1.ctid
-         Index Cond: ((t1.a > 5) AND (t1.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
-         SubPlan 1
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_1
-                       Filter: (t12_1.a = t1.a)
-                 ->  Seq Scan on public.t111 t12_2
-                       Filter: (t12_2.a = t1.a)
-         SubPlan 2
-           ->  Append
-                 ->  Seq Scan on public.t12 t12_4
-                       Output: t12_4.a
-                 ->  Seq Scan on public.t111 t12_5
-                       Output: t12_5.a
-   ->  Index Scan using t11_a_idx on public.t11 t1_1
-         Output: (t1_1.a + 1), t1_1.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.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.ctid
-         Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
-         Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
-(33 rows)
+   Update on public.t1 t1_1
+   Update on public.t11 t1_2
+   Update on public.t12 t1_3
+   Update on public.t111 t1_4
+   ->  Result
+         Output: (t1.a + 1), t1.ctid, (0)
+         ->  Append
+               ->  Index Scan using t1_a_idx on public.t1 t1_1
+                     Output: t1_1.a, t1_1.ctid, 0
+                     Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+                     SubPlan 1
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_1
+                                   Filter: (t12_1.a = t1_1.a)
+                             ->  Seq Scan on public.t111 t12_2
+                                   Filter: (t12_2.a = t1_1.a)
+                     SubPlan 2
+                       ->  Append
+                             ->  Seq Scan on public.t12 t12_4
+                                   Output: t12_4.a
+                             ->  Seq Scan on public.t111 t12_5
+                                   Output: t12_5.a
+               ->  Index Scan using t11_a_idx on public.t11 t1_2
+                     Output: t1_2.a, t1_2.ctid, 1
+                     Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+               ->  Index Scan using t12_a_idx on public.t12 t1_3
+                     Output: t1_3.a, t1_3.ctid, 2
+                     Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+               ->  Index Scan using t111_a_idx on public.t111 t1_4
+                     Output: t1_4.a, t1_4.ctid, 3
+                     Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
+                     Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_4.a) AND leakproof(t1_4.a))
+(36 rows)
 
 UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
 NOTICE:  snooped value: 8
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index dece036..dc34ac6 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -308,8 +308,8 @@ ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO
 
 -- The order of subplans should be in bound order
 EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
-                   QUERY PLAN                    
--------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
  Update on range_parted
    Update on part_a_1_a_10 range_parted_1
    Update on part_a_10_a_20 range_parted_2
@@ -318,21 +318,22 @@ EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
    Update on part_d_1_15 range_parted_5
    Update on part_d_15_20 range_parted_6
    Update on part_b_20_b_30 range_parted_7
-   ->  Seq Scan on part_a_1_a_10 range_parted_1
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_a_10_a_20 range_parted_2
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_b_1_b_10 range_parted_3
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_c_1_100 range_parted_4
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_d_1_15 range_parted_5
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_d_15_20 range_parted_6
-         Filter: (c > '97'::numeric)
-   ->  Seq Scan on part_b_20_b_30 range_parted_7
-         Filter: (c > '97'::numeric)
-(22 rows)
+   ->  Append
+         ->  Seq Scan on part_a_1_a_10 range_parted_1
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_a_10_a_20 range_parted_2
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_b_1_b_10 range_parted_3
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_c_1_100 range_parted_4
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_d_1_15 range_parted_5
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_d_15_20 range_parted_6
+               Filter: (c > '97'::numeric)
+         ->  Seq Scan on part_b_20_b_30 range_parted_7
+               Filter: (c > '97'::numeric)
+(23 rows)
 
 -- fail, row movement happens only within the partition subtree.
 UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105;
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 67eaeb4..471f06c 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -2184,44 +2184,32 @@ DELETE FROM a USING wcte WHERE aa = q2;
                      QUERY PLAN                     
 ----------------------------------------------------
  Delete on public.a
-   Delete on public.a
-   Delete on public.b a_1
-   Delete on public.c a_2
-   Delete on public.d a_3
+   Delete on public.a a_1
+   Delete on public.b a_2
+   Delete on public.c a_3
+   Delete on public.d a_4
    CTE wcte
      ->  Insert on public.int8_tbl
            Output: int8_tbl.q2
            ->  Result
                  Output: '42'::bigint, '47'::bigint
-   ->  Nested Loop
-         Output: a.ctid, wcte.*
-         Join Filter: (a.aa = wcte.q2)
-         ->  Seq Scan on public.a
-               Output: a.ctid, a.aa
-         ->  CTE Scan on wcte
+   ->  Hash Join
+         Output: a.ctid, wcte.*, (0)
+         Hash Cond: (a.aa = wcte.q2)
+         ->  Append
+               ->  Seq Scan on public.a a_1
+                     Output: a_1.ctid, a_1.aa, 0
+               ->  Seq Scan on public.b a_2
+                     Output: a_2.ctid, a_2.aa, 1
+               ->  Seq Scan on public.c a_3
+                     Output: a_3.ctid, a_3.aa, 2
+               ->  Seq Scan on public.d a_4
+                     Output: a_4.ctid, a_4.aa, 3
+         ->  Hash
                Output: wcte.*, wcte.q2
-   ->  Nested Loop
-         Output: a_1.ctid, wcte.*
-         Join Filter: (a_1.aa = wcte.q2)
-         ->  Seq Scan on public.b a_1
-               Output: a_1.ctid, a_1.aa
-         ->  CTE Scan on wcte
-               Output: wcte.*, wcte.q2
-   ->  Nested Loop
-         Output: a_2.ctid, wcte.*
-         Join Filter: (a_2.aa = wcte.q2)
-         ->  Seq Scan on public.c a_2
-               Output: a_2.ctid, a_2.aa
-         ->  CTE Scan on wcte
-               Output: wcte.*, wcte.q2
-   ->  Nested Loop
-         Output: a_3.ctid, wcte.*
-         Join Filter: (a_3.aa = wcte.q2)
-         ->  Seq Scan on public.d a_3
-               Output: a_3.ctid, a_3.aa
-         ->  CTE Scan on wcte
-               Output: wcte.*, wcte.q2
-(38 rows)
+               ->  CTE Scan on wcte
+                     Output: wcte.*, wcte.q2
+(26 rows)
 
 -- error cases
 -- data-modifying WITH tries to use its own output
-- 
1.8.3.1

