Hi,

Here is a patch for $subject. This is based on Amit's original patch (patch '0007-Tuple-routing-for-partitioned-tables-15.patch' in [1]). Main changes are:

* I like Amit's idea that for each partition that is a foreign table, the FDW performs query planning in the same way as in non-partition cases, but the changes to make_modifytable() in the original patch that creates a working-copy of Query to pass to PlanForeignModify() seem unacceptable. So, I modified the changes so that we create more-valid-looking copies of Query and ModifyTable with the foreign partition as target, as I said before. In relation to this, I also allowed expand_inherited_rtentry() to build an RTE and AppendRelInfo for each partition of a partitioned table that is an INSERT target, as mentioned by Amit in [1], by modifying transformInsertStmt() so that we set the inh flag for the target table's RTE to true when the target table is a partitioned table. The partition RTEs are not only referenced by FDWs, but used in selecting relation aliases for EXPLAIN (see below) as well as extracting plan dependencies in setref.c so that we force rebuilding of the plan on any change to partitions. (We do replanning on FDW table-option changes to foreign partitions, currently. See regression tests in the attached patch.)

* The original patch added tuple routing to foreign partitions in COPY FROM, but I'm not sure the changes to BeginCopy() in the patch are the right way to go. For example, the patch passes a dummy empty Query to PlanForeignModify() to make FDWs perform query planning, but I think FDWs would be surprised by the Query. Currently, we don't support COPY to foreign tables, so ISTM that it's better to revisit this issue when adding support for COPY to foreign tables. So, I dropped the COPY part.

* I modified explain.c so that EXPLAIN for an INSERT into a partitioned table displays partitions just below the ModifyTable node, and shows remote queries for foreign partitions in the same way as EXPLAIN for UPDATE/DELETE cases. Here is an example:

postgres=# explain verbose insert into pt values (1), (2);
                            QUERY PLAN
-------------------------------------------------------------------
 Insert on public.pt  (cost=0.00..0.03 rows=2 width=4)
   Foreign Insert on public.fp1
     Remote SQL: INSERT INTO public.t1(a) VALUES ($1)
   Foreign Insert on public.fp2
     Remote SQL: INSERT INTO public.t2(a) VALUES ($1)
   ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=4)
         Output: "*VALUES*".column1
(7 rows)

Comments are welcome!  Anyway, I'll add this to the next commitfest.

Best regards,
Etsuro Fujita

[1] https://www.postgresql.org/message-id/aa874eaf-cd3b-0f75-c647-6c0ea823781e%40lab.ntt.co.jp
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 6931,6936 **** NOTICE:  drop cascades to foreign table bar2
--- 6931,7074 ----
  drop table loct1;
  drop table loct2;
  -- ===================================================================
+ -- test tuple routing to 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;
+ -- ===================================================================
  -- test IMPORT FOREIGN SCHEMA
  -- ===================================================================
  CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1615,1620 **** drop table loct1;
--- 1615,1660 ----
  drop table loct2;
  
  -- ===================================================================
+ -- test tuple routing to 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;
+ 
+ -- ===================================================================
  -- test IMPORT FOREIGN SCHEMA
  -- ===================================================================
  
*** 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,
+                                  bool main_target, int subplan_index,
+                                  const char *operation, bool labeltarget, 
ExplainState *es);
  static void ExplainMemberNodes(List *plans, PlanState **planstates,
                                   List *ancestors, ExplainState *es);
  static void ExplainSubPlans(List *plans, List *ancestors,
***************
*** 821,826 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 825,841 ----
                        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;
***************
*** 2792,2853 **** show_modifytable_info(ModifyTableState *mtstate, List 
*ancestors,
        if (labeltargets)
                ExplainOpenGroup("Target Tables", "Target Tables", false, es);
  
        for (j = 0; j < mtstate->mt_nplans; j++)
        {
                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 */
--- 2807,2835 ----
        if (labeltargets)
                ExplainOpenGroup("Target Tables", "Target Tables", false, es);
  
+       /* Print main target(s) */
        for (j = 0; j < mtstate->mt_nplans; j++)
        {
                ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
                FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
!               show_actual_target(mtstate, node, resultRelInfo, fdwroutine, 
true, j,
!                                                  fdwroutine ? foperation : 
operation, labeltargets,
!                                                  es);
!       }
  
!       /* If this is an INSERT into a partitioned table, print partitions */
!       for (j = 0; j < mtstate->mt_num_partitions; j++)
!       {
!               /*
!                * We arrange mt_partitions in the plan's partition_rels list 
order,
!                * which is the same order as for UPDATE/DELETE cases.
!                */
!               ResultRelInfo *resultRelInfo = mtstate->mt_partitions + 
mtstate->mt_partition_indexes[j];
!               FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
!               show_actual_target(mtstate, node, resultRelInfo, fdwroutine, 
false, j,
!                                                  fdwroutine ? foperation : 
operation, true, es);
        }
  
        /* Gather names of ON CONFLICT arbiter indexes */
***************
*** 2904,2909 **** show_modifytable_info(ModifyTableState *mtstate, List 
*ancestors,
--- 2886,2957 ----
  }
  
  /*
+  * Show an actual target relation
+  */
+ static void
+ show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+                                  ResultRelInfo *resultRelInfo, FdwRoutine 
*fdwroutine,
+                                  bool main_target, int subplan_index,
+                                  const char *operation, bool labeltarget, 
ExplainState *es)
+ {
+       if (labeltarget)
+       {
+               /* 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, 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 */
+               ExplainCloseGroup("Target 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
***************
*** 92,97 **** static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
--- 92,99 ----
                                                  Bitmapset *modifiedCols,
                                                  AclMode requiredPerms);
  static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
+ static ResultRelInfo *ExecFindResultRelInfo(EState *estate, Oid relid,
+                                                                               
        bool missing_ok);
  static char *ExecBuildSlotValueDescription(Oid reloid,
                                                          TupleTableSlot *slot,
                                                          TupleDesc tupdesc,
***************
*** 1360,1365 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1362,1392 ----
  }
  
  /*
+  * ExecFindResultRelInfo -- find the ResultRelInfo struct for given relation 
OID
+  *
+  * If no such struct, either return NULL or throw error depending on 
missing_ok
+  */
+ static ResultRelInfo *
+ ExecFindResultRelInfo(EState *estate, Oid relid, bool missing_ok)
+ {
+       ResultRelInfo *rInfo;
+       int                     nr;
+ 
+       rInfo = estate->es_result_relations;
+       nr = estate->es_num_result_relations;
+       while (nr > 0)
+       {
+               if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+                       return rInfo;
+               rInfo++;
+               nr--;
+       }
+       if (!missing_ok)
+               elog(ERROR, "failed to find ResultRelInfo for relation %u", 
relid);
+       return NULL;
+ }
+ 
+ /*
   *            ExecGetTriggerResultRel
   *
   * Get a ResultRelInfo for a trigger target relation.  Most of the time,
***************
*** 1379,1399 **** ResultRelInfo *
  ExecGetTriggerResultRel(EState *estate, Oid relid)
  {
        ResultRelInfo *rInfo;
-       int                     nr;
        ListCell   *l;
        Relation        rel;
        MemoryContext oldcontext;
  
        /* First, search through the query result relations */
!       rInfo = estate->es_result_relations;
!       nr = estate->es_num_result_relations;
!       while (nr > 0)
!       {
!               if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
!                       return rInfo;
!               rInfo++;
!               nr--;
!       }
        /* Nope, but maybe we already made an extra ResultRelInfo for it */
        foreach(l, estate->es_trig_target_relations)
        {
--- 1406,1419 ----
  ExecGetTriggerResultRel(EState *estate, Oid relid)
  {
        ResultRelInfo *rInfo;
        ListCell   *l;
        Relation        rel;
        MemoryContext oldcontext;
  
        /* First, search through the query result relations */
!       rInfo = ExecFindResultRelInfo(estate, relid, true);
!       if (rInfo)
!               return rInfo;
        /* Nope, but maybe we already made an extra ResultRelInfo for it */
        foreach(l, estate->es_trig_target_relations)
        {
***************
*** 1828,1833 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, 
TupleTableSlot *slot,
--- 1848,1854 ----
                                   EState *estate)
  {
        Relation        rel = resultRelInfo->ri_RelationDesc;
+       Oid                     relid = RelationGetRelid(rel);
        TupleDesc       tupdesc = RelationGetDescr(rel);
        Bitmapset  *modifiedCols;
        Bitmapset  *insertedCols;
***************
*** 1870,1877 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, 
TupleTableSlot *slot,
--- 1891,1900 ----
                        HeapTuple       tuple = ExecFetchSlotTuple(slot);
                        TupleDesc       old_tupdesc = RelationGetDescr(rel);
                        TupleConversionMap *map;
+                       ResultRelInfo *rInfo;
  
                        rel = resultRelInfo->ri_PartitionRoot;
+                       relid = RelationGetRelid(rel);
                        tupdesc = RelationGetDescr(rel);
                        /* a reverse map */
                        map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1881,1892 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, 
TupleTableSlot *slot,
                                tuple = do_convert_tuple(tuple, map);
                                ExecStoreTuple(tuple, slot, InvalidBuffer, 
false);
                        }
                }
- 
-               insertedCols = GetInsertedColumns(resultRelInfo, estate);
-               updatedCols = GetUpdatedColumns(resultRelInfo, estate);
                modifiedCols = bms_union(insertedCols, updatedCols);
!               val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                 slot,
                                                                                
                 tupdesc,
                                                                                
                 modifiedCols,
--- 1904,1921 ----
                                tuple = do_convert_tuple(tuple, map);
                                ExecStoreTuple(tuple, slot, InvalidBuffer, 
false);
                        }
+                       /* Find the root table's ResultRelInfo */
+                       rInfo = ExecFindResultRelInfo(estate, relid, false);
+                       insertedCols = GetInsertedColumns(rInfo, estate);
+                       updatedCols = GetUpdatedColumns(rInfo, estate);
+               }
+               else
+               {
+                       insertedCols = GetInsertedColumns(resultRelInfo, 
estate);
+                       updatedCols = GetUpdatedColumns(resultRelInfo, estate);
                }
                modifiedCols = bms_union(insertedCols, updatedCols);
!               val_desc = ExecBuildSlotValueDescription(relid,
                                                                                
                 slot,
                                                                                
                 tupdesc,
                                                                                
                 modifiedCols,
***************
*** 1914,1919 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1943,1949 ----
                                TupleTableSlot *slot, EState *estate)
  {
        Relation        rel = resultRelInfo->ri_RelationDesc;
+       Oid                     relid = RelationGetRelid(rel);
        TupleDesc       tupdesc = RelationGetDescr(rel);
        TupleConstr *constr = tupdesc->constr;
        Bitmapset  *modifiedCols;
***************
*** 1947,1954 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1977,1986 ----
                                {
                                        HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                        TupleConversionMap *map;
+                                       ResultRelInfo *rInfo;
  
                                        rel = resultRelInfo->ri_PartitionRoot;
+                                       relid = RelationGetRelid(rel);
                                        tupdesc = RelationGetDescr(rel);
                                        /* a reverse map */
                                        map = 
convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1958,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
                                                tuple = do_convert_tuple(tuple, 
map);
                                                ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                        }
                                }
- 
-                               insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
-                               updatedCols = GetUpdatedColumns(resultRelInfo, 
estate);
                                modifiedCols = bms_union(insertedCols, 
updatedCols);
!                               val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                                 slot,
                                                                                
                                 tupdesc,
                                                                                
                                 modifiedCols,
--- 1990,2007 ----
                                                tuple = do_convert_tuple(tuple, 
map);
                                                ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                        }
+                                       /* Find the root table's ResultRelInfo 
*/
+                                       rInfo = ExecFindResultRelInfo(estate, 
relid, false);
+                                       insertedCols = 
GetInsertedColumns(rInfo, estate);
+                                       updatedCols = GetUpdatedColumns(rInfo, 
estate);
+                               }
+                               else
+                               {
+                                       insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
+                                       updatedCols = 
GetUpdatedColumns(resultRelInfo, estate);
                                }
                                modifiedCols = bms_union(insertedCols, 
updatedCols);
!                               val_desc = ExecBuildSlotValueDescription(relid,
                                                                                
                                 slot,
                                                                                
                                 tupdesc,
                                                                                
                                 modifiedCols,
***************
*** 1987,1992 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 2025,2031 ----
                {
                        char       *val_desc;
                        Relation        orig_rel = rel;
+                       Oid                     relid = RelationGetRelid(rel);
  
                        /* See the comment above. */
                        if (resultRelInfo->ri_PartitionRoot)
***************
*** 1994,2001 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 2033,2042 ----
                                HeapTuple       tuple = 
ExecFetchSlotTuple(slot);
                                TupleDesc       old_tupdesc = 
RelationGetDescr(rel);
                                TupleConversionMap *map;
+                               ResultRelInfo *rInfo;
  
                                rel = resultRelInfo->ri_PartitionRoot;
+                               relid = RelationGetRelid(rel);
                                tupdesc = RelationGetDescr(rel);
                                /* a reverse map */
                                map = convert_tuples_by_name(old_tupdesc, 
tupdesc,
***************
*** 2005,2016 **** ExecConstraints(ResultRelInfo *resultRelInfo,
                                        tuple = do_convert_tuple(tuple, map);
                                        ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                }
                        }
- 
-                       insertedCols = GetInsertedColumns(resultRelInfo, 
estate);
-                       updatedCols = GetUpdatedColumns(resultRelInfo, estate);
                        modifiedCols = bms_union(insertedCols, updatedCols);
!                       val_desc = 
ExecBuildSlotValueDescription(RelationGetRelid(rel),
                                                                                
                         slot,
                                                                                
                         tupdesc,
                                                                                
                         modifiedCols,
--- 2046,2063 ----
                                        tuple = do_convert_tuple(tuple, map);
                                        ExecStoreTuple(tuple, slot, 
InvalidBuffer, false);
                                }
+                               /* Find the root table's ResultRelInfo */
+                               rInfo = ExecFindResultRelInfo(estate, relid, 
false);
+                               insertedCols = GetInsertedColumns(rInfo, 
estate);
+                               updatedCols = GetUpdatedColumns(rInfo, estate);
+                       }
+                       else
+                       {
+                               insertedCols = 
GetInsertedColumns(resultRelInfo, estate);
+                               updatedCols = GetUpdatedColumns(resultRelInfo, 
estate);
                        }
                        modifiedCols = bms_union(insertedCols, updatedCols);
!                       val_desc = ExecBuildSlotValueDescription(relid,
                                                                                
                         slot,
                                                                                
                         tupdesc,
                                                                                
                         modifiedCols,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 304,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;
  
--- 304,309 ----
***************
*** 1925,1930 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int 
eflags)
--- 1919,1970 ----
                mtstate->mt_num_partitions = num_partitions;
                mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
                mtstate->mt_partition_tuple_slot = partition_tuple_slot;
+ 
+               mtstate->mt_partition_indexes = (int *)
+                       palloc0(num_partitions * sizeof(int));
+               i = 0;
+               foreach(l, node->partition_rels)
+               {
+                       Index           rti = lfirst_int(l);
+                       Oid                     relid = getrelid(rti, 
estate->es_range_table);
+                       bool            found;
+                       int                     j;
+ 
+                       /* First, find the ResultRelInfo for the partition */
+                       found = false;
+                       for (j = 0; j < mtstate->mt_num_partitions; j++)
+                       {
+                               resultRelInfo = partitions + j;
+ 
+                               if 
(RelationGetRelid(resultRelInfo->ri_RelationDesc) == relid)
+                               {
+                                       Assert(mtstate->mt_partition_indexes[i] 
== 0);
+                                       mtstate->mt_partition_indexes[i] = j;
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       if (!found)
+                               elog(ERROR, "failed to find ResultRelInfo for 
rangetable index %u", rti);
+ 
+                       /* Fix the rangetable index of the ResultRelInfo */
+                       resultRelInfo->ri_RangeTableIndex = rti;
+ 
+                       /* Let FDWs init themselves for foreign-table result 
rels */
+                       if (resultRelInfo->ri_FdwRoutine != NULL &&
+                               
resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+                       {
+                               List       *fdw_private = (List *) 
list_nth(node->fdwPartitionPrivLists, i);
+ 
+                               
resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+                                                                               
                                                 resultRelInfo,
+                                                                               
                                                 fdw_private,
+                                                                               
                                                 0,
+                                                                               
                                                 eflags);
+                       }
+ 
+                       i++;
+               }
        }
  
        /* Build state for collecting transition tuples */
***************
*** 2345,2350 **** ExecEndModifyTable(ModifyTableState *node)
--- 2385,2395 ----
        {
                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
***************
*** 349,354 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 349,355 ----
        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);
***************
*** 365,370 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 366,372 ----
        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
***************
*** 1547,1552 **** _readModifyTable(void)
--- 1547,1553 ----
        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);
***************
*** 1563,1568 **** _readModifyTable(void)
--- 1564,1570 ----
        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"
***************
*** 6405,6410 **** make_modifytable(PlannerInfo *root,
--- 6406,6412 ----
        ModifyTable *node = makeNode(ModifyTable);
        List       *fdw_private_list;
        Bitmapset  *direct_modify_plans;
+       List       *partition_rels;
        ListCell   *lc;
        int                     i;
  
***************
*** 6525,6530 **** make_modifytable(PlannerInfo *root,
--- 6527,6600 ----
        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 partition that is a foreign 
table,
+        * allow the FDW to construct private plan data and accumulate it all 
into
+        * another list.
+        */
+       partition_rels = NIL;
+       fdw_private_list = NIL;
+       if (operation == CMD_INSERT)
+       {
+               Index           rti = linitial_int(resultRelations);
+ 
+               if (planner_rt_fetch(rti, root)->relkind == 
RELKIND_PARTITIONED_TABLE)
+               {
+                       foreach(lc, root->append_rel_list)
+                       {
+                               AppendRelInfo *appinfo = (AppendRelInfo *) 
lfirst(lc);
+                               Index           part_rti = appinfo->child_relid;
+                               RangeTblEntry *part_rte;
+                               FdwRoutine *fdwroutine = NULL;
+                               List       *fdw_private = NIL;
+ 
+                               /* append_rel_list contains all append rels; 
ignore others */
+                               if (appinfo->parent_relid != rti)
+                                       continue;
+ 
+                               part_rte = planner_rt_fetch(part_rti, root);
+                               Assert(part_rte->rtekind == RTE_RELATION);
+ 
+                               if (part_rte->relkind == RELKIND_FOREIGN_TABLE)
+                                       fdwroutine = 
GetFdwRoutineByRelId(part_rte->relid);
+ 
+                               if (fdwroutine != NULL &&
+                                       fdwroutine->PlanForeignModify != NULL)
+                               {
+                                       PlannerInfo *part_root;
+                                       ModifyTable *part_node;
+ 
+                                       /* Create a working copy of the 
PlannerInfo */
+                                       part_root = makeNode(PlannerInfo);
+                                       memcpy(part_root, root, 
sizeof(PlannerInfo));
+                                       /* Generate modified query with this 
partition as target */
+                                       part_root->parse = (Query *)
+                                               adjust_appendrel_attrs(root,
+                                                                               
           (Node *) root->parse,
+                                                                               
           appinfo);
+ 
+                                       /* Create a working copy of the 
ModifyTable */
+                                       part_node = copyObject(node);
+                                       part_node->nominalRelation = part_rti;
+                                       part_node->resultRelations = 
list_make1_int(part_rti);
+                                       part_node->returningLists =
+                                               
list_make1(part_root->parse->returningList);
+ 
+                                       fdw_private = 
fdwroutine->PlanForeignModify(part_root,
+                                                                               
                                                part_node,
+                                                                               
                                                part_rti,
+                                                                               
                                                0);
+                               }
+ 
+                               partition_rels = lappend_int(partition_rels, 
part_rti);
+                               fdw_private_list = lappend(fdw_private_list, 
fdw_private);
+                       }
+               }
+       }
+       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
***************
*** 828,834 **** subquery_planner(PlannerGlobal *glob, Query *parse,
         * 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
--- 828,835 ----
         * Do the main planning.  If we have an inherited 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
***************
*** 850,855 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 850,859 ----
                                {
                                        lfirst_int(l) += rtoffset;
                                }
+                               foreach(l, splan->partition_rels)
+                               {
+                                       lfirst_int(l) += rtoffset;
+                               }
                                foreach(l, splan->resultRelations)
                                {
                                        lfirst_int(l) += rtoffset;
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 542,547 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 542,554 ----
        qry->resultRelation = setTargetTable(pstate, stmt->relation,
                                                                                
 false, false, targetPerms);
  
+       /*
+        * If the target table is a partitioned table, reset the inh flag to 
true.
+        */
+       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
***************
*** 2859,2871 **** 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
--- 2859,2864 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 963,968 **** typedef struct ModifyTableState
--- 963,970 ----
        TupleConversionMap **mt_partition_tupconv_maps;
        /* Per partition tuple conversion map */
        TupleTableSlot *mt_partition_tuple_slot;
+       int                *mt_partition_indexes;       /* indexes into 
mt_partitions for use
+                                                                               
 * in explain.c */
        struct TransitionCaptureState *mt_transition_capture;
                                                                        /* 
controls transition table population */
        TupleConversionMap **mt_transition_tupconv_maps;
*** 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