On 2017/10/26 16:40, Etsuro Fujita wrote:
Other changes I made to the executor are: (1) currently, we set the RT index for the root partitioned table to ri_RangeTableIndex of partitions' ResultRelInfos, but the proposed EXPLAIN requires that the partition's ri_RangeTableIndex is set to the RT index for that partition's RTE, to show that partition info in the output.  So, I made that change.

One thing I forgot to mention is: that would be also required to call BeginForeignModify, ExecForeignInsert, and EndForeignModify with the partition's ResultRelInfo.

I updated docs in doc/src/sgml/ddl.sgml the same way as [1]. (I used only the ddl.sgml change proposed by [1], not all the changes.) I did some cleanup as well. Please find attached an updated version of the patch.

Best regards,
Etsuro Fujita

[1] https://www.postgresql.org/message-id/b19a8e2b-e000-f592-3e0b-3e90ba0fa816%40lab.ntt.co.jp
*** a/contrib/file_fdw/input/file_fdw.source
--- b/contrib/file_fdw/input/file_fdw.source
***************
*** 176,182 **** COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', 
delimiter ',');
--- 176,188 ----
  SELECT tableoid::regclass, * FROM pt;
  SELECT tableoid::regclass, * FROM p1;
  SELECT tableoid::regclass, * FROM p2;
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy');
+ \t off
  INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy');
+ \t off
  INSERT INTO pt VALUES (2, 'xyzzy');
  SELECT tableoid::regclass, * FROM pt;
  SELECT tableoid::regclass, * FROM p1;
*** a/contrib/file_fdw/output/file_fdw.source
--- b/contrib/file_fdw/output/file_fdw.source
***************
*** 315,321 **** SELECT tableoid::regclass, * FROM p2;
  (0 rows)
  
  COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter 
','); -- ERROR
! ERROR:  cannot route inserted tuples to a foreign table
  CONTEXT:  COPY pt, line 2: "1,qux"
  COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
  SELECT tableoid::regclass, * FROM pt;
--- 315,321 ----
  (0 rows)
  
  COPY pt FROM '@abs_srcdir@/data/list2.bad' with (format 'csv', delimiter 
','); -- ERROR
! ERROR:  cannot route copied tuples to a foreign table
  CONTEXT:  COPY pt, line 2: "1,qux"
  COPY pt FROM '@abs_srcdir@/data/list2.csv' with (format 'csv', delimiter ',');
  SELECT tableoid::regclass, * FROM pt;
***************
*** 341,348 **** SELECT tableoid::regclass, * FROM p2;
   p2       | 2 | qux
  (2 rows)
  
  INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR:  cannot route inserted tuples to a foreign table
  INSERT INTO pt VALUES (2, 'xyzzy');
  SELECT tableoid::regclass, * FROM pt;
   tableoid | a |   b   
--- 341,366 ----
   p2       | 2 | qux
  (2 rows)
  
+ \t on
+ EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (1, 'xyzzy');
+  Insert on public.pt
+    Foreign Insert on public.p1
+    Insert on public.p2
+    ->  Result
+          Output: 1, 'xyzzy'::text
+ 
+ \t off
  INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR:  cannot route inserted tuples to foreign table "p1"
! \t on
! EXPLAIN (VERBOSE, COSTS FALSE) INSERT INTO pt VALUES (2, 'xyzzy');
!  Insert on public.pt
!    Foreign Insert on public.p1
!    Insert on public.p2
!    ->  Result
!          Output: 2, 'xyzzy'::text
! 
! \t off
  INSERT INTO pt VALUES (2, 'xyzzy');
  SELECT tableoid::regclass, * FROM pt;
   tableoid | a |   b   
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7029,7034 **** NOTICE:  drop cascades to foreign table bar2
--- 7029,7236 ----
  drop table loct1;
  drop table loct2;
  -- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback 
options (table_name 'locfoo');
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Insert on public.pt
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+ 
+ insert into pt values (1, 1), (2, 1);
+ select tableoid::regclass, * FROM pt;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  remp     | 2 | 1
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM locp;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+ (1 row)
+ 
+ select tableoid::regclass, * FROM remp;
+  tableoid | a | b 
+ ----------+---+---
+  remp     | 2 | 1
+ (1 row)
+ 
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+                                    QUERY PLAN                                 
  
+ 
--------------------------------------------------------------------------------
+  Insert on public.pt
+    Output: pt.a, pt.b
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) RETURNING a, 
b
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (7 rows)
+ 
+ insert into pt values (1, 2), (2, 2) returning *;
+  a | b 
+ ---+---
+  1 | 2
+  2 | 2
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM pt;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+  remp     | 2 | 1
+  remp     | 2 | 2
+ (4 rows)
+ 
+ select tableoid::regclass, * FROM locp;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM remp;
+  tableoid | a | b 
+ ----------+---+---
+  remp     | 2 | 1
+  remp     | 2 | 2
+ (2 rows)
+ 
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Insert on public.pt
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+ 
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Insert on public.pt
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locbar(a, b) VALUES ($1, $2)
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+ 
+ execute q1;
+ select tableoid::regclass, * FROM pt;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+  locp     | 1 | 3
+  remp     | 2 | 1
+  remp     | 2 | 2
+  remp     | 2 | 3
+ (6 rows)
+ 
+ select tableoid::regclass, * FROM locp;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+  locp     | 1 | 3
+ (3 rows)
+ 
+ select tableoid::regclass, * FROM remp;
+  tableoid | a | b 
+ ----------+---+---
+  remp     | 2 | 1
+  remp     | 2 | 2
+  remp     | 2 | 3
+ (3 rows)
+ 
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) 
partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 
100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 
200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to 
(200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to 
(300) server loopback options (table_name 'locbar');
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+                                         QUERY PLAN                            
            
+ 
------------------------------------------------------------------------------------------
+  Insert on public.mlpt
+    Output: mlpt.a, mlpt.b, mlpt.c
+    Foreign Insert on public.mlptp1p1
+      Remote SQL: INSERT INTO public.locfoo(a, b, c) VALUES ($1, $2, $3) 
RETURNING a, b, c
+    Foreign Insert on public.mlptp1p2
+      Remote SQL: INSERT INTO public.locbar(a, b, c) VALUES ($1, $2, $3) 
RETURNING a, b, c
+    Insert on public.mlptp2
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2, "*VALUES*".column3
+ (9 rows)
+ 
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+   a  |  b  | c 
+ -----+-----+---
+  101 | 101 | x
+  101 | 201 | y
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM mlpt;
+  tableoid |  a  |  b  | c 
+ ----------+-----+-----+---
+  mlptp1p1 | 101 | 101 | x
+  mlptp1p2 | 101 | 201 | y
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM mlptp1;
+  tableoid |  a  |  b  | c 
+ ----------+-----+-----+---
+  mlptp1p1 | 101 | 101 | x
+  mlptp1p2 | 101 | 201 | y
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM mlptp2;
+  tableoid | a | b | c 
+ ----------+---+---+---
+ (0 rows)
+ 
+ select tableoid::regclass, * FROM mlptp1p1;
+  tableoid |  a  |  b  | c 
+ ----------+-----+-----+---
+  mlptp1p1 | 101 | 101 | x
+ (1 row)
+ 
+ select tableoid::regclass, * FROM mlptp1p2;
+  tableoid |  a  |  b  | c 
+ ----------+-----+-----+---
+  mlptp1p2 | 101 | 201 | y
+ (1 row)
+ 
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+ -- ===================================================================
  -- test IMPORT FOREIGN SCHEMA
  -- ===================================================================
  CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1662,1667 **** drop table loct1;
--- 1662,1730 ----
  drop table loct2;
  
  -- ===================================================================
+ -- test tuple routing for foreign-table partitions
+ -- ===================================================================
+ 
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback 
options (table_name 'locfoo');
+ 
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ insert into pt values (1, 1), (2, 1);
+ 
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+ 
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ insert into pt values (1, 2), (2, 2) returning *;
+ 
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+ 
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ execute q1;
+ 
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+ 
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+ 
+ -- Check INSERT into a multi-level partitioned table
+ create table mlpt (a int, b int, c varchar) partition by range (a);
+ create table mlptp1 partition of mlpt for values from (100) to (200) 
partition by range(b);
+ create table mlptp2 partition of mlpt for values from (200) to (300);
+ create table locfoo (a int check (a >= 100 and a < 200), b int check (b >= 
100 and b < 200), c varchar);
+ create table locbar (a int check (a >= 100 and a < 200), b int check (b >= 
200 and b < 300), c varchar);
+ create foreign table mlptp1p1 partition of mlptp1 for values from (100) to 
(200) server loopback options (table_name 'locfoo');
+ create foreign table mlptp1p2 partition of mlptp1 for values from (200) to 
(300) server loopback options (table_name 'locbar');
+ 
+ explain (verbose, costs off)
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ insert into mlpt values (101, 101, 'x'), (101, 201, 'y') returning *;
+ 
+ select tableoid::regclass, * FROM mlpt;
+ select tableoid::regclass, * FROM mlptp1;
+ select tableoid::regclass, * FROM mlptp2;
+ select tableoid::regclass, * FROM mlptp1p1;
+ select tableoid::regclass, * FROM mlptp1p2;
+ 
+ drop table mlpt;
+ drop table locfoo;
+ drop table locbar;
+ 
+ -- ===================================================================
  -- test IMPORT FOREIGN SCHEMA
  -- ===================================================================
  
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 2986,2996 **** VALUES ('Albany', NULL, NULL, 'NY');
     </para>
  
     <para>
!     Partitions can also be foreign tables
!     (see <xref linkend="sql-createforeigntable">),
!     although these have some limitations that normal tables do not.  For
!     example, data inserted into the partitioned table is not routed to
!     foreign table partitions.
     </para>
  
     <sect3 id="ddl-partitioning-declarative-example">
--- 2986,2994 ----
     </para>
  
     <para>
!     Partitions can also be foreign tables, although they have some limitations
!     that normal tables do not; see <xref linkend="sql-createforeigntable"> for
!     more information.
     </para>
  
     <sect3 id="ddl-partitioning-declarative-example">
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2459,2474 **** CopyFrom(CopyState cstate)
        if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
        {
                PartitionDispatch *partition_dispatch_info;
                ResultRelInfo **partitions;
                TupleConversionMap **partition_tupconv_maps;
                TupleTableSlot *partition_tuple_slot;
                int                     num_parted,
                                        num_partitions;
  
                ExecSetupPartitionTupleRouting(cstate->rel,
-                                                                          1,
-                                                                          
estate,
                                                                           
&partition_dispatch_info,
                                                                           
&partitions,
                                                                           
&partition_tupconv_maps,
                                                                           
&partition_tuple_slot,
--- 2459,2477 ----
        if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
        {
                PartitionDispatch *partition_dispatch_info;
+               List       *partition_oids;
                ResultRelInfo **partitions;
                TupleConversionMap **partition_tupconv_maps;
                TupleTableSlot *partition_tuple_slot;
                int                     num_parted,
                                        num_partitions;
+               ResultRelInfo *partRelInfo;
+               int                     i;
+               ListCell   *l;
  
                ExecSetupPartitionTupleRouting(cstate->rel,
                                                                           
&partition_dispatch_info,
+                                                                          
&partition_oids,
                                                                           
&partitions,
                                                                           
&partition_tupconv_maps,
                                                                           
&partition_tuple_slot,
***************
*** 2480,2485 **** CopyFrom(CopyState cstate)
--- 2483,2522 ----
                cstate->partition_tupconv_maps = partition_tupconv_maps;
                cstate->partition_tuple_slot = partition_tuple_slot;
  
+               partRelInfo = (ResultRelInfo *) palloc0(num_partitions *
+                                                                               
                sizeof(ResultRelInfo));
+               i = 0;
+               foreach(l, partition_oids)
+               {
+                       Oid                     partOid = lfirst_oid(l);
+                       Relation        partrel;
+ 
+                       /* Prepare ResultRelInfo and map for the partition */
+                       ExecInitPartition(estate,
+                                                         resultRelInfo,
+                                                         partOid,
+                                                         0,            /* 
dummy rangetable index */
+                                                         partRelInfo,
+                                                         
&partition_tupconv_maps[i]);
+                       partitions[i] = partRelInfo;
+ 
+                       /* Verify the partition is a valid target for COPY */
+                       partrel = partRelInfo->ri_RelationDesc;
+                       if (partrel->rd_rel->relkind == RELKIND_RELATION)
+                               partRelInfo->ri_PartitionIsValid = true;
+                       else
+                       {
+                               /* The partition should be foreign */
+                               Assert(partrel->rd_rel->relkind == 
RELKIND_FOREIGN_TABLE);
+ 
+                               /* We do not yet have a way to copy into a 
foreign partition */
+                               partRelInfo->ri_PartitionIsValid = false;
+                       }
+ 
+                       partRelInfo++;
+                       i++;
+               }
+ 
                /*
                 * If we are capturing transition tuples, they may need to be
                 * converted from partition format back to partitioned table 
format
***************
*** 2628,2638 **** CopyFrom(CopyState cstate)
                        saved_resultRelInfo = resultRelInfo;
                        resultRelInfo = cstate->partitions[leaf_part_index];
  
!                       /* We do not yet have a way to insert into a foreign 
partition */
!                       if (resultRelInfo->ri_FdwRoutine)
                                ereport(ERROR,
                                                
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                                                errmsg("cannot route inserted 
tuples to a foreign table")));
  
                        /*
                         * For ExecInsertIndexTuples() to work on the 
partition's indexes
--- 2665,2680 ----
                        saved_resultRelInfo = resultRelInfo;
                        resultRelInfo = cstate->partitions[leaf_part_index];
  
!                       if (!resultRelInfo->ri_PartitionIsValid)
!                       {
!                               /* The partition should be foreign */
!                               Assert(resultRelInfo->ri_FdwRoutine);
! 
!                               /* We do not yet have a way to copy into a 
foreign partition */
                                ereport(ERROR,
                                                
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                                                errmsg("cannot route copied 
tuples to a foreign table")));
!                       }
  
                        /*
                         * For ExecInsertIndexTuples() to work on the 
partition's indexes
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 116,121 **** static void ExplainModifyTarget(ModifyTable *plan, 
ExplainState *es);
--- 116,125 ----
  static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
  static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
                                          ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+                                  ResultRelInfo *resultRelInfo, FdwRoutine 
*fdwroutine,
+                                  const char *operation, bool labeltarget,
+                                  bool main_target, int subplan_index, 
ExplainState *es);
  static void ExplainMemberNodes(List *plans, PlanState **planstates,
                                   List *ancestors, ExplainState *es);
  static void ExplainSubPlans(List *plans, List *ancestors,
***************
*** 828,833 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 832,848 ----
                        if (((ModifyTable *) plan)->exclRelRTI)
                                *rels_used = bms_add_member(*rels_used,
                                                                                
        ((ModifyTable *) plan)->exclRelRTI);
+                       if (((ModifyTable *) plan)->partition_rels)
+                       {
+                               ListCell   *lc;
+ 
+                               foreach(lc, ((ModifyTable *) 
plan)->partition_rels)
+                               {
+                                       Index           rti = lfirst_int(lc);
+ 
+                                       *rels_used = bms_add_member(*rels_used, 
rti);
+                               }
+                       }
                        break;
                default:
                        break;
***************
*** 2855,2911 **** show_modifytable_info(ModifyTableState *mtstate, List 
*ancestors,
                ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
                FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
!               if (labeltargets)
!               {
!                       /* Open a group for this target */
!                       ExplainOpenGroup("Target Table", NULL, true, es);
! 
!                       /*
!                        * In text mode, decorate each target with operation 
type, so that
!                        * ExplainTargetRel's output of " on foo" will read 
nicely.
!                        */
!                       if (es->format == EXPLAIN_FORMAT_TEXT)
!                       {
!                               appendStringInfoSpaces(es->str, es->indent * 2);
!                               appendStringInfoString(es->str,
!                                                                          
fdwroutine ? foperation : operation);
!                       }
! 
!                       /* Identify target */
!                       ExplainTargetRel((Plan *) node,
!                                                        
resultRelInfo->ri_RangeTableIndex,
!                                                        es);
  
!                       if (es->format == EXPLAIN_FORMAT_TEXT)
!                       {
!                               appendStringInfoChar(es->str, '\n');
!                               es->indent++;
!                       }
!               }
  
!               /* Give FDW a chance if needed */
!               if (!resultRelInfo->ri_usesFdwDirectModify &&
!                       fdwroutine != NULL &&
!                       fdwroutine->ExplainForeignModify != NULL)
                {
!                       List       *fdw_private = (List *) 
list_nth(node->fdwPrivLists, j);
  
!                       fdwroutine->ExplainForeignModify(mtstate,
!                                                                               
         resultRelInfo,
!                                                                               
         fdw_private,
!                                                                               
         j,
!                                                                               
         es);
                }
  
!               if (labeltargets)
!               {
!                       /* Undo the indentation we added in text format */
!                       if (es->format == EXPLAIN_FORMAT_TEXT)
!                               es->indent--;
! 
!                       /* Close the group */
!                       ExplainCloseGroup("Target Table", NULL, true, es);
!               }
        }
  
        /* Gather names of ON CONFLICT arbiter indexes */
--- 2870,2896 ----
                ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
                FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
!               show_actual_target(mtstate, node, resultRelInfo, fdwroutine,
!                                                  fdwroutine ? foperation : 
operation,
!                                                  labeltargets, true, j, es);
!       }
  
!       /* Print partition tables if needed */
!       if (mtstate->mt_num_partitions > 0)
!       {
!               ExplainOpenGroup("Partition Tables", "Partition Tables", false, 
es);
  
!               for (j = 0; j < mtstate->mt_num_partitions; j++)
                {
!                       ResultRelInfo *resultRelInfo = 
mtstate->mt_partitions[j];
!                       FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
!                       show_actual_target(mtstate, node, resultRelInfo, 
fdwroutine,
!                                                          fdwroutine ? 
foperation : operation,
!                                                          true, false, j, es);
                }
  
!               ExplainCloseGroup("Partition Tables", "Partition Tables", 
false, es);
        }
  
        /* Gather names of ON CONFLICT arbiter indexes */
***************
*** 2962,2967 **** show_modifytable_info(ModifyTableState *mtstate, List 
*ancestors,
--- 2947,3024 ----
  }
  
  /*
+  * Show an actual target relation
+  */
+ static void
+ show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+                                  ResultRelInfo *resultRelInfo, FdwRoutine 
*fdwroutine,
+                                  const char *operation, bool labeltarget,
+                                  bool main_target, int subplan_index, 
ExplainState *es)
+ {
+       if (labeltarget)
+       {
+               /* Open a group for this target */
+               if (main_target)
+                       ExplainOpenGroup("Target Table", NULL, true, es);
+               else
+                       ExplainOpenGroup("Partition Table", NULL, true, es);
+ 
+               /*
+                * In text mode, decorate each target with operation type, so 
that
+                * ExplainTargetRel's output of " on foo" will read nicely.
+                */
+               if (es->format == EXPLAIN_FORMAT_TEXT)
+               {
+                       appendStringInfoSpaces(es->str, es->indent * 2);
+                       appendStringInfoString(es->str, operation);
+               }
+ 
+               /* Identify target */
+               ExplainTargetRel((Plan *) node,
+                                                
resultRelInfo->ri_RangeTableIndex,
+                                                es);
+ 
+               if (es->format == EXPLAIN_FORMAT_TEXT)
+               {
+                       appendStringInfoChar(es->str, '\n');
+                       es->indent++;
+               }
+       }
+ 
+       /* Give FDW a chance if needed */
+       if (fdwroutine != NULL &&
+               fdwroutine->ExplainForeignModify != NULL &&
+               !resultRelInfo->ri_usesFdwDirectModify)
+       {
+               List       *fdw_private;
+ 
+               if (main_target)
+                       fdw_private = (List *) list_nth(node->fdwPrivLists, 
subplan_index);
+               else
+                       fdw_private = (List *) 
list_nth(node->fdwPartitionPrivLists, subplan_index);
+ 
+               fdwroutine->ExplainForeignModify(mtstate,
+                                                                               
 resultRelInfo,
+                                                                               
 fdw_private,
+                                                                               
 main_target ? subplan_index : 0,
+                                                                               
 es);
+       }
+ 
+       if (labeltarget)
+       {
+               /* Undo the indentation we added in text format */
+               if (es->format == EXPLAIN_FORMAT_TEXT)
+                       es->indent--;
+ 
+               /* Close the group */
+               if (main_target)
+                       ExplainCloseGroup("Target Table", NULL, true, es);
+               else
+                       ExplainCloseGroup("Partition Table", NULL, true, es);
+       }
+ }
+ 
+ /*
   * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
   * BitmapAnd, or BitmapOr node.
   *
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1102,1113 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType 
operation)
--- 1102,1116 ----
        Relation        resultRel = resultRelInfo->ri_RelationDesc;
        TriggerDesc *trigDesc = resultRel->trigdesc;
        FdwRoutine *fdwroutine;
+       bool            is_valid;
  
        switch (resultRel->rd_rel->relkind)
        {
                case RELKIND_RELATION:
                case RELKIND_PARTITIONED_TABLE:
                        CheckCmdReplicaIdentity(resultRel, operation);
+                       if (resultRelInfo->ri_PartitionRoot && operation == 
CMD_INSERT)
+                               resultRelInfo->ri_PartitionIsValid = true;
                        break;
                case RELKIND_SEQUENCE:
                        ereport(ERROR,
***************
*** 1174,1197 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType 
operation)
                        switch (operation)
                        {
                                case CMD_INSERT:
! 
!                                       /*
!                                        * If foreign partition to do 
tuple-routing for, skip the
!                                        * check; it's disallowed elsewhere.
!                                        */
!                                       if (resultRelInfo->ri_PartitionRoot)
!                                               break;
                                        if (fdwroutine->ExecForeignInsert == 
NULL)
!                                               ereport(ERROR,
!                                                               
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                                                                errmsg("cannot 
insert into foreign table \"%s\"",
!                                                                               
RelationGetRelationName(resultRel))));
                                        if (fdwroutine->IsForeignRelUpdatable 
!= NULL &&
                                                
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
!                                               ereport(ERROR,
!                                                               
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!                                                                
errmsg("foreign table \"%s\" does not allow inserts",
!                                                                               
RelationGetRelationName(resultRel))));
                                        break;
                                case CMD_UPDATE:
                                        if (fdwroutine->ExecForeignUpdate == 
NULL)
--- 1177,1204 ----
                        switch (operation)
                        {
                                case CMD_INSERT:
!                                       is_valid = true;
                                        if (fdwroutine->ExecForeignInsert == 
NULL)
!                                       {
!                                               if 
(!resultRelInfo->ri_PartitionRoot)
!                                                       ereport(ERROR,
!                                                                       
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                                                                        
errmsg("cannot insert into foreign table \"%s\"",
!                                                                               
        RelationGetRelationName(resultRel))));
!                                               is_valid = false;
!                                       }
                                        if (fdwroutine->IsForeignRelUpdatable 
!= NULL &&
                                                
(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
!                                       {
!                                               if 
(!resultRelInfo->ri_PartitionRoot)
!                                                       ereport(ERROR,
!                                                                       
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
!                                                                        
errmsg("foreign table \"%s\" does not allow inserts",
!                                                                               
        RelationGetRelationName(resultRel))));
!                                               is_valid = false;
!                                       }
!                                       if (resultRelInfo->ri_PartitionRoot)
!                                               
resultRelInfo->ri_PartitionIsValid = is_valid;
                                        break;
                                case CMD_UPDATE:
                                        if (fdwroutine->ExecForeignUpdate == 
NULL)
***************
*** 1308,1314 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
                                  Relation resultRelationDesc,
                                  Index resultRelationIndex,
!                                 Relation partition_root,
                                  int instrument_options)
  {
        List       *partition_check = NIL;
--- 1315,1321 ----
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
                                  Relation resultRelationDesc,
                                  Index resultRelationIndex,
!                                 ResultRelInfo *partition_root,
                                  int instrument_options)
  {
        List       *partition_check = NIL;
***************
*** 1364,1371 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
         */
        partition_check = RelationGetPartitionQual(resultRelationDesc);
  
-       resultRelInfo->ri_PartitionCheck = partition_check;
        resultRelInfo->ri_PartitionRoot = partition_root;
  }
  
  /*
--- 1371,1379 ----
         */
        partition_check = RelationGetPartitionQual(resultRelationDesc);
  
        resultRelInfo->ri_PartitionRoot = partition_root;
+       resultRelInfo->ri_PartitionCheck = partition_check;
+       resultRelInfo->ri_PartitionIsValid = false;
  }
  
  /*
***************
*** 1889,1903 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, 
TupleTableSlot *slot,
        {
                char       *val_desc;
                Relation        orig_rel = rel;
  
                /* See the comment above. */
!               if (resultRelInfo->ri_PartitionRoot)
                {
                        HeapTuple       tuple = ExecFetchSlotTuple(slot);
                        TupleDesc       old_tupdesc = RelationGetDescr(rel);
                        TupleConversionMap *map;
  
!                       rel = resultRelInfo->ri_PartitionRoot;
                        tupdesc = RelationGetDescr(rel);
                        /* a reverse map */
                        map = convert_tuples_by_name(old_tupdesc, tupdesc,
--- 1897,1912 ----
        {
                char       *val_desc;
                Relation        orig_rel = rel;
+               ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
  
                /* See the comment above. */
!               if (rootRelInfo)
                {
                        HeapTuple       tuple = ExecFetchSlotTuple(slot);
                        TupleDesc       old_tupdesc = RelationGetDescr(rel);
                        TupleConversionMap *map;
  
!                       rel = rootRelInfo->ri_RelationDesc;
                        tupdesc = RelationGetDescr(rel);
                        /* a reverse map */
                        map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1908,1917 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, 
TupleTableSlot *slot,
                                ExecSetSlotDescriptor(slot, tupdesc);
                                ExecStoreTuple(tuple, slot, InvalidBuffer, 
false);
                        }
-               }
  
!               insertedCols = GetInsertedColumns(resultRelInfo, estate);
!               updatedCols = GetUpdatedColumns(resultRelInfo, estate);
                modifiedCols = bms_union(insertedCols, updatedCols);
                val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                 slot,
--- 1917,1931 ----
                                ExecSetSlotDescriptor(slot, tupdesc);
                                ExecStoreTuple(tuple, slot, InvalidBuffer, 
false);
                        }
  
!                       insertedCols = GetInsertedColumns(rootRelInfo, estate);
!                       updatedCols = GetUpdatedColumns(rootRelInfo, estate);
!               }
!               else
!               {
!                       insertedCols = GetInsertedColumns(resultRelInfo, 
estate);
!                       updatedCols = GetUpdatedColumns(resultRelInfo, estate);
!               }
                modifiedCols = bms_union(insertedCols, updatedCols);
                val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                 slot,
***************
*** 1963,1968 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1977,1983 ----
                                char       *val_desc;
                                Relation        orig_rel = rel;
                                TupleDesc       orig_tupdesc = 
RelationGetDescr(rel);
+                               ResultRelInfo *rootRelInfo = 
resultRelInfo->ri_PartitionRoot;
  
                                /*
                                 * If the tuple has been routed, it's been 
converted to the
***************
*** 1971,1982 **** ExecConstraints(ResultRelInfo *resultRelInfo,
                                 * rowtype so that val_desc shown error message 
matches the
                                 * input tuple.
                                 */
!                               if (resultRelInfo->ri_PartitionRoot)
                                {
                                        HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                        TupleConversionMap *map;
  
!                                       rel = resultRelInfo->ri_PartitionRoot;
                                        tupdesc = RelationGetDescr(rel);
                                        /* a reverse map */
                                        map = 
convert_tuples_by_name(orig_tupdesc, tupdesc,
--- 1986,1997 ----
                                 * rowtype so that val_desc shown error message 
matches the
                                 * input tuple.
                                 */
!                               if (rootRelInfo)
                                {
                                        HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                        TupleConversionMap *map;
  
!                                       rel = rootRelInfo->ri_RelationDesc;
                                        tupdesc = RelationGetDescr(rel);
                                        /* a reverse map */
                                        map = 
convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1987,1996 **** ExecConstraints(ResultRelInfo *resultRelInfo,
                                                ExecSetSlotDescriptor(slot, 
tupdesc);
                                                ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                        }
-                               }
  
!                               insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
!                               updatedCols = GetUpdatedColumns(resultRelInfo, 
estate);
                                modifiedCols = bms_union(insertedCols, 
updatedCols);
                                val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                                 slot,
--- 2002,2016 ----
                                                ExecSetSlotDescriptor(slot, 
tupdesc);
                                                ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                        }
  
!                                       insertedCols = 
GetInsertedColumns(rootRelInfo, estate);
!                                       updatedCols = 
GetUpdatedColumns(rootRelInfo, estate);
!                               }
!                               else
!                               {
!                                       insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
!                                       updatedCols = 
GetUpdatedColumns(resultRelInfo, estate);
!                               }
                                modifiedCols = bms_union(insertedCols, 
updatedCols);
                                val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                                 slot,
***************
*** 2016,2030 **** ExecConstraints(ResultRelInfo *resultRelInfo,
                {
                        char       *val_desc;
                        Relation        orig_rel = rel;
  
                        /* See the comment above. */
!                       if (resultRelInfo->ri_PartitionRoot)
                        {
                                HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                TupleDesc       old_tupdesc = 
RelationGetDescr(rel);
                                TupleConversionMap *map;
  
!                               rel = resultRelInfo->ri_PartitionRoot;
                                tupdesc = RelationGetDescr(rel);
                                /* a reverse map */
                                map = convert_tuples_by_name(old_tupdesc, 
tupdesc,
--- 2036,2051 ----
                {
                        char       *val_desc;
                        Relation        orig_rel = rel;
+                       ResultRelInfo *rootRelInfo = 
resultRelInfo->ri_PartitionRoot;
  
                        /* See the comment above. */
!                       if (rootRelInfo)
                        {
                                HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                TupleDesc       old_tupdesc = 
RelationGetDescr(rel);
                                TupleConversionMap *map;
  
!                               rel = rootRelInfo->ri_RelationDesc;
                                tupdesc = RelationGetDescr(rel);
                                /* a reverse map */
                                map = convert_tuples_by_name(old_tupdesc, 
tupdesc,
***************
*** 2035,2044 **** ExecConstraints(ResultRelInfo *resultRelInfo,
                                        ExecSetSlotDescriptor(slot, tupdesc);
                                        ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                }
-                       }
  
!                       insertedCols = GetInsertedColumns(resultRelInfo, 
estate);
!                       updatedCols = GetUpdatedColumns(resultRelInfo, estate);
                        modifiedCols = bms_union(insertedCols, updatedCols);
                        val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                         slot,
--- 2056,2070 ----
                                        ExecSetSlotDescriptor(slot, tupdesc);
                                        ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                }
  
!                               insertedCols = GetInsertedColumns(rootRelInfo, 
estate);
!                               updatedCols = GetUpdatedColumns(rootRelInfo, 
estate);
!                       }
!                       else
!                       {
!                               insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
!                               updatedCols = GetUpdatedColumns(resultRelInfo, 
estate);
!                       }
                        modifiedCols = bms_union(insertedCols, updatedCols);
                        val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                         slot,
***************
*** 2110,2115 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo 
*resultRelInfo,
--- 2136,2142 ----
                 */
                if (!ExecQual(wcoExpr, econtext))
                {
+                       ResultRelInfo *rootRelInfo = 
resultRelInfo->ri_PartitionRoot;
                        char       *val_desc;
                        Bitmapset  *modifiedCols;
                        Bitmapset  *insertedCols;
***************
*** 2128,2140 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo 
*resultRelInfo,
                                         */
                                case WCO_VIEW_CHECK:
                                        /* See the comment in 
ExecConstraints(). */
!                                       if (resultRelInfo->ri_PartitionRoot)
                                        {
                                                HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                                TupleDesc       old_tupdesc = 
RelationGetDescr(rel);
                                                TupleConversionMap *map;
  
!                                               rel = 
resultRelInfo->ri_PartitionRoot;
                                                tupdesc = RelationGetDescr(rel);
                                                /* a reverse map */
                                                map = 
convert_tuples_by_name(old_tupdesc, tupdesc,
--- 2155,2167 ----
                                         */
                                case WCO_VIEW_CHECK:
                                        /* See the comment in 
ExecConstraints(). */
!                                       if (rootRelInfo)
                                        {
                                                HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                                TupleDesc       old_tupdesc = 
RelationGetDescr(rel);
                                                TupleConversionMap *map;
  
!                                               rel = 
rootRelInfo->ri_RelationDesc;
                                                tupdesc = RelationGetDescr(rel);
                                                /* a reverse map */
                                                map = 
convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2145,2154 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo 
*resultRelInfo,
                                                        
ExecSetSlotDescriptor(slot, tupdesc);
                                                        ExecStoreTuple(tuple, 
slot, InvalidBuffer, false);
                                                }
-                                       }
  
!                                       insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
!                                       updatedCols = 
GetUpdatedColumns(resultRelInfo, estate);
                                        modifiedCols = bms_union(insertedCols, 
updatedCols);
                                        val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                                         slot,
--- 2172,2186 ----
                                                        
ExecSetSlotDescriptor(slot, tupdesc);
                                                        ExecStoreTuple(tuple, 
slot, InvalidBuffer, false);
                                                }
  
!                                               insertedCols = 
GetInsertedColumns(rootRelInfo, estate);
!                                               updatedCols = 
GetUpdatedColumns(rootRelInfo, estate);
!                                       }
!                                       else
!                                       {
!                                               insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
!                                               updatedCols = 
GetUpdatedColumns(resultRelInfo, estate);
!                                       }
                                        modifiedCols = bms_union(insertedCols, 
updatedCols);
                                        val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                                         slot,
***************
*** 3242,3247 **** EvalPlanQualEnd(EPQState *epqstate)
--- 3274,3281 ----
   * Output arguments:
   * 'pd' receives an array of PartitionDispatch objects with one entry for
   *            every partitioned table in the partition tree
+  * 'leaf_parts' receives a list of relation OIDs with one entry for every leaf
+  *            partition in the partition tree
   * 'partitions' receives an array of ResultRelInfo* objects with one entry for
   *            every leaf partition in the partition tree
   * 'tup_conv_maps' receives an array of TupleConversionMap objects with one
***************
*** 3262,3288 **** EvalPlanQualEnd(EPQState *epqstate)
   */
  void
  ExecSetupPartitionTupleRouting(Relation rel,
-                                                          Index resultRTindex,
-                                                          EState *estate,
                                                           PartitionDispatch 
**pd,
                                                           ResultRelInfo 
***partitions,
                                                           TupleConversionMap 
***tup_conv_maps,
                                                           TupleTableSlot 
**partition_tuple_slot,
                                                           int *num_parted, int 
*num_partitions)
  {
-       TupleDesc       tupDesc = RelationGetDescr(rel);
-       List       *leaf_parts;
-       ListCell   *cell;
-       int                     i;
-       ResultRelInfo *leaf_part_rri;
- 
        /*
         * Get the information about the partition tree after locking all the
         * partitions.
         */
        (void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, 
NULL);
!       *pd = RelationGetPartitionDispatchInfo(rel, num_parted, &leaf_parts);
!       *num_partitions = list_length(leaf_parts);
        *partitions = (ResultRelInfo **) palloc(*num_partitions *
                                                                                
        sizeof(ResultRelInfo *));
        *tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
--- 3296,3315 ----
   */
  void
  ExecSetupPartitionTupleRouting(Relation rel,
                                                           PartitionDispatch 
**pd,
+                                                          List **leaf_parts,
                                                           ResultRelInfo 
***partitions,
                                                           TupleConversionMap 
***tup_conv_maps,
                                                           TupleTableSlot 
**partition_tuple_slot,
                                                           int *num_parted, int 
*num_partitions)
  {
        /*
         * Get the information about the partition tree after locking all the
         * partitions.
         */
        (void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, 
NULL);
!       *pd = RelationGetPartitionDispatchInfo(rel, num_parted, leaf_parts);
!       *num_partitions = list_length(*leaf_parts);
        *partitions = (ResultRelInfo **) palloc(*num_partitions *
                                                                                
        sizeof(ResultRelInfo *));
        *tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
***************
*** 3295,3350 **** ExecSetupPartitionTupleRouting(Relation rel,
         * processing.
         */
        *partition_tuple_slot = MakeTupleTableSlot();
  
!       leaf_part_rri = (ResultRelInfo *) palloc0(*num_partitions *
!                                                                               
          sizeof(ResultRelInfo));
!       i = 0;
!       foreach(cell, leaf_parts)
!       {
!               Relation        partrel;
!               TupleDesc       part_tupdesc;
! 
!               /*
!                * We locked all the partitions above including the leaf 
partitions.
!                * Note that each of the relations in *partitions are eventually
!                * closed by the caller.
!                */
!               partrel = heap_open(lfirst_oid(cell), NoLock);
!               part_tupdesc = RelationGetDescr(partrel);
! 
!               /*
!                * Save a tuple conversion map to convert a tuple routed to this
!                * partition from the parent's type to the partition's.
!                */
!               (*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, 
part_tupdesc,
!                                                                               
                         gettext_noop("could not convert row type"));
  
!               InitResultRelInfo(leaf_part_rri,
!                                                 partrel,
!                                                 resultRTindex,
!                                                 rel,
!                                                 estate->es_instrument);
  
!               /*
!                * Verify result relation is a valid target for INSERT.
!                */
!               CheckValidResultRel(leaf_part_rri, CMD_INSERT);
  
!               /*
!                * Open partition indices (remember we do not support ON 
CONFLICT in
!                * case of partitioned tables, so we do not need support 
information
!                * for speculative insertion)
!                */
!               if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
!                       leaf_part_rri->ri_IndexRelationDescs == NULL)
!                       ExecOpenIndices(leaf_part_rri, false);
  
!               estate->es_leaf_result_relations =
!                       lappend(estate->es_leaf_result_relations, 
leaf_part_rri);
  
!               (*partitions)[i] = leaf_part_rri++;
!               i++;
!       }
  }
  
  /*
--- 3322,3378 ----
         * processing.
         */
        *partition_tuple_slot = MakeTupleTableSlot();
+ }
  
! /*
!  * ExecInitPartition -- Prepare ResultRelInfo and tuple conversion map for
!  * the partition with OID 'partOid'
!  */
! void
! ExecInitPartition(EState *estate,
!                                 ResultRelInfo *rootRelInfo,
!                                 Oid partOid,
!                                 Index partRTindex,
!                                 ResultRelInfo *partRelInfo,
!                                 TupleConversionMap **partTupConvMap)
! {
!       Relation        rootrel = rootRelInfo->ri_RelationDesc;
!       Relation        partrel;
  
!       /*
!        * We assume that ExecSetupPartitionTupleRouting() already locked the
!        * partition, so we need no lock here.  The partition must be closed
!        * by the caller.
!        */
!       partrel = heap_open(partOid, NoLock);
  
!       /* Save ResultRelInfo data for the partition. */
!       InitResultRelInfo(partRelInfo,
!                                         partrel,
!                                         partRTindex,
!                                         rootRelInfo,
!                                         estate->es_instrument);
  
!       /*
!        * Open partition indices (remember we do not support ON CONFLICT in
!        * case of partitioned tables, so we do not need support information
!        * for speculative insertion)
!        */
!       if (partRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
!               partRelInfo->ri_IndexRelationDescs == NULL)
!               ExecOpenIndices(partRelInfo, false);
  
!       /* Store ResultRelInfo in *estate. */
!       estate->es_leaf_result_relations =
!               lappend(estate->es_leaf_result_relations, partRelInfo);
  
!       /*
!        * Save conversion map to convert a tuple routed to the partition from
!        * the parent's type to the partition's.
!        */
!       *partTupConvMap = convert_tuples_by_name(RelationGetDescr(rootrel),
!                                                                               
         RelationGetDescr(partrel),
!                                                                               
         gettext_noop("could not convert row type"));
  }
  
  /*
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 305,315 **** ExecInsert(ModifyTableState *mtstate,
                saved_resultRelInfo = resultRelInfo;
                resultRelInfo = mtstate->mt_partitions[leaf_part_index];
  
!               /* We do not yet have a way to insert into a foreign partition 
*/
!               if (resultRelInfo->ri_FdwRoutine)
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                                        errmsg("cannot route inserted tuples 
to a foreign table")));
  
                /* For ExecInsertIndexTuples() to work on the partition's 
indexes */
                estate->es_result_relation_info = resultRelInfo;
--- 305,321 ----
                saved_resultRelInfo = resultRelInfo;
                resultRelInfo = mtstate->mt_partitions[leaf_part_index];
  
!               if (!resultRelInfo->ri_PartitionIsValid)
!               {
!                       /* The partition should be foreign */
!                       Assert(resultRelInfo->ri_FdwRoutine);
! 
!                       /* We cannot insert into this foreign partition */
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                                        errmsg("cannot route inserted tuples 
to foreign table \"%s\"",
!                                                       
RelationGetRelationName(resultRelInfo->ri_RelationDesc))));
!               }
  
                /* For ExecInsertIndexTuples() to work on the partition's 
indexes */
                estate->es_result_relation_info = resultRelInfo;
***************
*** 1945,1960 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int 
eflags)
                rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
        {
                PartitionDispatch *partition_dispatch_info;
                ResultRelInfo **partitions;
                TupleConversionMap **partition_tupconv_maps;
                TupleTableSlot *partition_tuple_slot;
                int                     num_parted,
                                        num_partitions;
  
                ExecSetupPartitionTupleRouting(rel,
-                                                                          
node->nominalRelation,
-                                                                          
estate,
                                                                           
&partition_dispatch_info,
                                                                           
&partitions,
                                                                           
&partition_tupconv_maps,
                                                                           
&partition_tuple_slot,
--- 1951,1967 ----
                rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
        {
                PartitionDispatch *partition_dispatch_info;
+               List       *partition_oids;
                ResultRelInfo **partitions;
                TupleConversionMap **partition_tupconv_maps;
                TupleTableSlot *partition_tuple_slot;
                int                     num_parted,
                                        num_partitions;
+               ResultRelInfo *partRelInfo;
  
                ExecSetupPartitionTupleRouting(rel,
                                                                           
&partition_dispatch_info,
+                                                                          
&partition_oids,
                                                                           
&partitions,
                                                                           
&partition_tupconv_maps,
                                                                           
&partition_tuple_slot,
***************
*** 1965,1970 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int 
eflags)
--- 1972,2015 ----
                mtstate->mt_num_partitions = num_partitions;
                mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
                mtstate->mt_partition_tuple_slot = partition_tuple_slot;
+ 
+               partRelInfo = (ResultRelInfo *) palloc0(num_partitions *
+                                                                               
                sizeof(ResultRelInfo));
+               i = 0;
+               foreach(l, partition_oids)
+               {
+                       Oid                     partOid = lfirst_oid(l);
+                       Index           partRTindex = 
list_nth_int(node->partition_rels, i);
+ 
+                       /* Prepare ResultRelInfo and map for the partition */
+                       ExecInitPartition(estate,
+                                                         
mtstate->resultRelInfo,
+                                                         partOid,
+                                                         partRTindex,
+                                                         partRelInfo,
+                                                         
&partition_tupconv_maps[i]);
+                       partitions[i] = partRelInfo;
+ 
+                       /* Verify the partition is a valid target for INSERT */
+                       CheckValidResultRel(partRelInfo, CMD_INSERT);
+ 
+                       /* If so, allow the FDW to init itself for the 
partition */
+                       if (partRelInfo->ri_PartitionIsValid &&
+                               partRelInfo->ri_FdwRoutine != NULL &&
+                               partRelInfo->ri_FdwRoutine->BeginForeignModify 
!= NULL)
+                       {
+                               List       *fdw_private = (List *) 
list_nth(node->fdwPartitionPrivLists, i);
+ 
+                               
partRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+                                                                               
                                           partRelInfo,
+                                                                               
                                           fdw_private,
+                                                                               
                                           0,
+                                                                               
                                           eflags);
+                       }
+ 
+                       partRelInfo++;
+                       i++;
+               }
        }
  
        /*
***************
*** 2390,2395 **** ExecEndModifyTable(ModifyTableState *node)
--- 2435,2445 ----
        {
                ResultRelInfo *resultRelInfo = node->mt_partitions[i];
  
+               if (resultRelInfo->ri_FdwRoutine != NULL &&
+                       resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+                       
resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+                                                                               
                                   resultRelInfo);
+ 
                ExecCloseIndices(resultRelInfo);
                heap_close(resultRelInfo->ri_RelationDesc, NoLock);
        }
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 204,209 **** _copyModifyTable(const ModifyTable *from)
--- 204,210 ----
        COPY_SCALAR_FIELD(canSetTag);
        COPY_SCALAR_FIELD(nominalRelation);
        COPY_NODE_FIELD(partitioned_rels);
+       COPY_NODE_FIELD(partition_rels);
        COPY_NODE_FIELD(resultRelations);
        COPY_SCALAR_FIELD(resultRelIndex);
        COPY_SCALAR_FIELD(rootResultRelIndex);
***************
*** 220,225 **** _copyModifyTable(const ModifyTable *from)
--- 221,227 ----
        COPY_NODE_FIELD(onConflictWhere);
        COPY_SCALAR_FIELD(exclRelRTI);
        COPY_NODE_FIELD(exclRelTlist);
+       COPY_NODE_FIELD(fdwPartitionPrivLists);
  
        return newnode;
  }
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 372,377 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 372,378 ----
        WRITE_BOOL_FIELD(canSetTag);
        WRITE_UINT_FIELD(nominalRelation);
        WRITE_NODE_FIELD(partitioned_rels);
+       WRITE_NODE_FIELD(partition_rels);
        WRITE_NODE_FIELD(resultRelations);
        WRITE_INT_FIELD(resultRelIndex);
        WRITE_INT_FIELD(rootResultRelIndex);
***************
*** 388,393 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 389,395 ----
        WRITE_NODE_FIELD(onConflictWhere);
        WRITE_UINT_FIELD(exclRelRTI);
        WRITE_NODE_FIELD(exclRelTlist);
+       WRITE_NODE_FIELD(fdwPartitionPrivLists);
  }
  
  static void
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1568,1573 **** _readModifyTable(void)
--- 1568,1574 ----
        READ_BOOL_FIELD(canSetTag);
        READ_UINT_FIELD(nominalRelation);
        READ_NODE_FIELD(partitioned_rels);
+       READ_NODE_FIELD(partition_rels);
        READ_NODE_FIELD(resultRelations);
        READ_INT_FIELD(resultRelIndex);
        READ_INT_FIELD(rootResultRelIndex);
***************
*** 1584,1589 **** _readModifyTable(void)
--- 1585,1591 ----
        READ_NODE_FIELD(onConflictWhere);
        READ_UINT_FIELD(exclRelRTI);
        READ_NODE_FIELD(exclRelTlist);
+       READ_NODE_FIELD(fdwPartitionPrivLists);
  
        READ_DONE();
  }
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 35,40 ****
--- 35,41 ----
  #include "optimizer/planmain.h"
  #include "optimizer/planner.h"
  #include "optimizer/predtest.h"
+ #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
  #include "optimizer/subselect.h"
  #include "optimizer/tlist.h"
***************
*** 6425,6430 **** make_modifytable(PlannerInfo *root,
--- 6426,6432 ----
        ModifyTable *node = makeNode(ModifyTable);
        List       *fdw_private_list;
        Bitmapset  *direct_modify_plans;
+       List       *partition_rels;
        ListCell   *lc;
        int                     i;
  
***************
*** 6548,6553 **** make_modifytable(PlannerInfo *root,
--- 6550,6681 ----
        node->fdwPrivLists = fdw_private_list;
        node->fdwDirectModifyPlans = direct_modify_plans;
  
+       /*
+        * Also, if this is an INSERT into a partitioned table, build a list of
+        * RT indexes of partitions, and for each foreign partition, allow the 
FDW
+        * to construct private plan data and accumulate it all into another 
list.
+        *
+        * Note: ExecSetupPartitionTupleRouting() will expand partitions in the
+        * same order as these lists.
+        */
+       partition_rels = NIL;
+       fdw_private_list = NIL;
+       if (operation == CMD_INSERT &&
+               planner_rt_fetch(nominalRelation, root)->relkind == 
RELKIND_PARTITIONED_TABLE)
+       {
+               List       *saved_withCheckOptionLists = 
node->withCheckOptionLists;
+               List       *saved_returningLists = node->returningLists;
+               Plan       *subplan = (Plan *) linitial(node->plans);
+               List       *saved_tlist = subplan->targetlist;
+               Query      **parent_parses;
+               Query      *parent_parse;
+               Bitmapset  *parent_relids;
+ 
+               /*
+                * Similarly to inheritance_planner(), we generate a modified 
query
+                * with each child as target by applying 
adjust_appendrel_attrs() to
+                * the query for its immediate parent, so build an array to 
store in
+                * the query for each parent.
+                */
+               parent_parses = (Query **)
+                       palloc0((list_length(root->parse->rtable) + 1) * 
sizeof(Query *));
+ 
+               parent_parses[nominalRelation] = root->parse;
+               parent_relids = bms_make_singleton(nominalRelation);
+ 
+               foreach(lc, root->append_rel_list)
+               {
+                       AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+                       Index           parent_rti = appinfo->parent_relid;
+                       Index           child_rti = appinfo->child_relid;
+                       RangeTblEntry *child_rte;
+                       Query      *child_parse;
+                       FdwRoutine *fdwroutine = NULL;
+                       List       *fdw_private = NIL;
+ 
+                       /* append_rel_list contains all append rels; ignore 
others */
+                       if (!bms_is_member(parent_rti, parent_relids))
+                               continue;
+ 
+                       child_rte = planner_rt_fetch(child_rti, root);
+                       Assert(child_rte->rtekind == RTE_RELATION);
+ 
+                       /* No work if the child is a plain table */
+                       if (child_rte->relkind == RELKIND_RELATION)
+                       {
+                               partition_rels = lappend_int(partition_rels, 
child_rti);
+                               fdw_private_list = lappend(fdw_private_list, 
NIL);
+                               continue;
+                       }
+ 
+                       /*
+                        * expand_inherited_rtentry() always processes a parent 
before any
+                        * of that parent's children, so the query for its 
parent should
+                        * already be available.
+                        */
+                       parent_parse = parent_parses[parent_rti];
+                       Assert(parent_parse);
+ 
+                       /* Generate the query with the child as target */
+                       child_parse = (Query *)
+                               adjust_appendrel_attrs(root,
+                                                                          
(Node *) parent_parse,
+                                                                          1, 
&appinfo);
+ 
+                       /* Store the query if the child is a partitioned table 
*/
+                       if (child_rte->relkind == RELKIND_PARTITIONED_TABLE)
+                       {
+                               parent_parses[child_rti] = child_parse;
+                               parent_relids = bms_add_member(parent_relids, 
child_rti);
+                               continue;
+                       }
+ 
+                       /* The child should be a foreign table */
+                       Assert(child_rte->relkind == RELKIND_FOREIGN_TABLE);
+ 
+                       fdwroutine = GetFdwRoutineByRelId(child_rte->relid);
+                       Assert(fdwroutine != NULL);
+ 
+                       if (fdwroutine->PlanForeignModify != NULL)
+                       {
+                               List       *tlist;
+ 
+                               /* Adjust the plan node to refer to the child 
as target. */
+                               node->nominalRelation = child_rti;
+                               node->resultRelations = 
list_make1_int(child_rti);
+                               node->withCheckOptionLists =
+                                       
list_make1(child_parse->withCheckOptions);
+                               node->returningLists =
+                                       list_make1(child_parse->returningList);
+ 
+                               /*
+                                * The column list of the child might have a 
different column
+                                * order and/or a different set of dropped 
columns than that
+                                * of its parent, so adjust the subplan's tlist 
as well.
+                                */
+                               tlist = preprocess_targetlist(root,
+                                                                               
          child_parse->targetList);
+                               subplan->targetlist = tlist;
+ 
+                               fdw_private = 
fdwroutine->PlanForeignModify(root,
+                                                                               
                                        node,
+                                                                               
                                        child_rti,
+                                                                               
                                        0);
+                       }
+ 
+                       partition_rels = lappend_int(partition_rels, child_rti);
+                       fdw_private_list = lappend(fdw_private_list, 
fdw_private);
+               }
+ 
+               node->nominalRelation = nominalRelation;
+               node->resultRelations = list_make1_int(nominalRelation);
+               node->withCheckOptionLists = saved_withCheckOptionLists;
+               node->returningLists = saved_returningLists;
+               subplan->targetlist = saved_tlist;
+       }
+       node->partition_rels = partition_rels;
+       node->fdwPartitionPrivLists = fdw_private_list;
+ 
        return node;
  }
  
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 870,879 **** subquery_planner(PlannerGlobal *glob, Query *parse,
                reduce_outer_joins(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
--- 870,881 ----
                reduce_outer_joins(root);
  
        /*
!        * Do the main planning.  If we have an inherited UPDATE/DELETE target
!        * relation, that needs special processing, else go straight to
!        * grouping_planner.
         */
!       if ((parse->commandType == CMD_UPDATE ||
!                parse->commandType == CMD_DELETE) &&
                rt_fetch(parse->resultRelation, parse->rtable)->inh)
                inheritance_planner(root);
        else
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 854,859 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 854,863 ----
                                {
                                        lfirst_int(l) += rtoffset;
                                }
+                               foreach(l, splan->partition_rels)
+                               {
+                                       lfirst_int(l) += rtoffset;
+                               }
                                foreach(l, splan->resultRelations)
                                {
                                        lfirst_int(l) += rtoffset;
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1972,1979 **** adjust_appendrel_attrs(PlannerInfo *root, Node *node, int 
nappinfos,
                        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);
--- 1972,1980 ----
                        if (newnode->resultRelation == appinfo->parent_relid)
                        {
                                newnode->resultRelation = appinfo->child_relid;
!                               /* Fix tlist resnos too, if it's inherited 
INSERT/UPDATE */
!                               if (newnode->commandType == CMD_INSERT ||
!                                       newnode->commandType == CMD_UPDATE)
                                        newnode->targetList =
                                                
adjust_inherited_tlist(newnode->targetList,
                                                                                
           appinfo);
***************
*** 2323,2329 **** 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
--- 2324,2330 ----
  }
  
  /*
!  * Adjust the targetlist entries of an inherited INSERT/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
***************
*** 2335,2342 **** adjust_child_relids_multilevel(PlannerInfo *root, Relids 
relids,
   * 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)
--- 2336,2341 ----
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 542,547 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 542,552 ----
        qry->resultRelation = setTargetTable(pstate, stmt->relation,
                                                                                
 false, false, targetPerms);
  
+       /* Set the inh flag to true if the target table is partitioned */
+       rte = pstate->p_target_rangetblentry;
+       if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+               rte->inh = true;
+ 
        /* Validate stmt->cols list, or build default list if no list given */
        icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
        Assert(list_length(icolumns) == list_length(attrnos));
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 2890,2902 **** rewriteTargetView(Query *parsetree, Relation view)
        new_rt_index = list_length(parsetree->rtable);
  
        /*
-        * INSERTs never inherit.  For UPDATE/DELETE, we use the view query's
-        * inheritance flag for the base relation.
-        */
-       if (parsetree->commandType == CMD_INSERT)
-               new_rte->inh = false;
- 
-       /*
         * Adjust the view's targetlist Vars to reference the new target RTE, ie
         * make their varnos be new_rt_index instead of base_rt_index.  There 
can
         * be no Vars for other rels in the tlist, so this is sufficient to pull
--- 2890,2895 ----
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 181,187 **** extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, 
CmdType operation)
  extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
                                  Relation resultRelationDesc,
                                  Index resultRelationIndex,
!                                 Relation partition_root,
                                  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern void ExecCleanUpTriggerState(EState *estate);
--- 181,187 ----
  extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
                                  Relation resultRelationDesc,
                                  Index resultRelationIndex,
!                                 ResultRelInfo *partition_root,
                                  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern void ExecCleanUpTriggerState(EState *estate);
***************
*** 207,219 **** extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
                                         HeapTuple tuple);
  extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
  extern void ExecSetupPartitionTupleRouting(Relation rel,
-                                                          Index resultRTindex,
-                                                          EState *estate,
                                                           PartitionDispatch 
**pd,
                                                           ResultRelInfo 
***partitions,
                                                           TupleConversionMap 
***tup_conv_maps,
                                                           TupleTableSlot 
**partition_tuple_slot,
                                                           int *num_parted, int 
*num_partitions);
  extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
                                  PartitionDispatch *pd,
                                  TupleTableSlot *slot,
--- 207,224 ----
                                         HeapTuple tuple);
  extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
  extern void ExecSetupPartitionTupleRouting(Relation rel,
                                                           PartitionDispatch 
**pd,
+                                                          List **leaf_parts,
                                                           ResultRelInfo 
***partitions,
                                                           TupleConversionMap 
***tup_conv_maps,
                                                           TupleTableSlot 
**partition_tuple_slot,
                                                           int *num_parted, int 
*num_partitions);
+ extern void ExecInitPartition(EState *estate,
+                                 ResultRelInfo *rootRelInfo,
+                                 Oid partOid,
+                                 Index partRTindex,
+                                 ResultRelInfo *partRelInfo,
+                                 TupleConversionMap **partTupConvMap);
  extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
                                  PartitionDispatch *pd,
                                  TupleTableSlot *slot,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 404,417 **** typedef struct ResultRelInfo
        /* list of ON CONFLICT DO UPDATE exprs (qual) */
        ExprState  *ri_onConflictSetWhere;
  
        /* partition check expression */
        List       *ri_PartitionCheck;
  
        /* partition check expression state */
        ExprState  *ri_PartitionCheckExpr;
  
!       /* relation descriptor for root partitioned table */
!       Relation        ri_PartitionRoot;
  } ResultRelInfo;
  
  /* ----------------
--- 404,420 ----
        /* list of ON CONFLICT DO UPDATE exprs (qual) */
        ExprState  *ri_onConflictSetWhere;
  
+       /* root partitioned table */
+       struct ResultRelInfo *ri_PartitionRoot;
+ 
        /* partition check expression */
        List       *ri_PartitionCheck;
  
        /* partition check expression state */
        ExprState  *ri_PartitionCheckExpr;
  
!       /* true when partition is legal for tuple-routing */
!       bool            ri_PartitionIsValid;
  } ResultRelInfo;
  
  /* ----------------
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 219,224 **** typedef struct ModifyTable
--- 219,226 ----
        Index           nominalRelation;        /* Parent RT index for use of 
EXPLAIN */
        /* RT indexes of non-leaf tables in a partition tree */
        List       *partitioned_rels;
+       List       *partition_rels;     /* RT indexes of leaf tables in a 
partition
+                                                                * tree */
        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 */
***************
*** 235,240 **** typedef struct ModifyTable
--- 237,244 ----
        Node       *onConflictWhere;    /* WHERE for ON CONFLICT UPDATE */
        Index           exclRelRTI;             /* RTI of the EXCLUDED pseudo 
relation */
        List       *exclRelTlist;       /* tlist of the EXCLUDED pseudo 
relation */
+       List       *fdwPartitionPrivLists;      /* per-partition FDW private 
data
+                                                                               
 * lists */
  } ModifyTable;
  
  /* ----------------
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to