*** 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
***************
*** 7088,7093 **** NOTICE:  drop cascades to foreign table bar2
--- 7088,7295 ----
  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
***************
*** 1680,1685 **** drop table loct1;
--- 1680,1748 ----
  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
***************
*** 2998,3008 **** 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">
--- 2998,3006 ----
     </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
***************
*** 2473,2489 **** 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(NULL,
! 									   cstate->rel,
! 									   1,
! 									   estate,
  									   &partition_dispatch_info,
  									   &partitions,
  									   &partition_tupconv_maps,
  									   &partition_tuple_slot,
--- 2473,2491 ----
  	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,
***************
*** 2495,2500 **** CopyFrom(CopyState cstate)
--- 2497,2537 ----
  		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(NULL,
+ 							  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
***************
*** 2643,2653 **** 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
--- 2680,2695 ----
  			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
***************
*** 117,122 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
--- 117,126 ----
  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,
***************
*** 829,834 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 833,849 ----
  			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;
***************
*** 2917,2973 **** 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 */
--- 2932,2958 ----
  		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 */
***************
*** 3024,3029 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
--- 3009,3086 ----
  }
  
  /*
+  * 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
***************
*** 1100,1111 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
--- 1100,1114 ----
  	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,
***************
*** 1172,1195 **** 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)
--- 1175,1202 ----
  			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)
***************
*** 1306,1312 **** void
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
! 				  Relation partition_root,
  				  int instrument_options)
  {
  	List	   *partition_check = NIL;
--- 1313,1319 ----
  InitResultRelInfo(ResultRelInfo *resultRelInfo,
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
! 				  ResultRelInfo *partition_root,
  				  int instrument_options)
  {
  	List	   *partition_check = NIL;
***************
*** 1362,1369 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
  	 */
  	partition_check = RelationGetPartitionQual(resultRelationDesc);
  
- 	resultRelInfo->ri_PartitionCheck = partition_check;
  	resultRelInfo->ri_PartitionRoot = partition_root;
  }
  
  /*
--- 1369,1377 ----
  	 */
  	partition_check = RelationGetPartitionQual(resultRelationDesc);
  
  	resultRelInfo->ri_PartitionRoot = partition_root;
+ 	resultRelInfo->ri_PartitionCheck = partition_check;
+ 	resultRelInfo->ri_PartitionIsValid = false;
  }
  
  /*
***************
*** 1890,1904 **** 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,
--- 1898,1913 ----
  	{
  		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,
***************
*** 1909,1918 **** 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,
--- 1918,1932 ----
  				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,
***************
*** 1964,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1978,1984 ----
  				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
***************
*** 1972,1983 **** 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,
--- 1987,1998 ----
  				 * 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,
***************
*** 1988,1997 **** 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,
--- 2003,2017 ----
  						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,
***************
*** 2017,2031 **** 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,
--- 2037,2052 ----
  		{
  			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,
***************
*** 2036,2045 **** 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,
--- 2057,2071 ----
  					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,
***************
*** 2111,2116 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
--- 2137,2143 ----
  		 */
  		if (!ExecQual(wcoExpr, econtext))
  		{
+ 			ResultRelInfo *rootRelInfo = resultRelInfo->ri_PartitionRoot;
  			char	   *val_desc;
  			Bitmapset  *modifiedCols;
  			Bitmapset  *insertedCols;
***************
*** 2129,2141 **** 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,
--- 2156,2168 ----
  					 */
  				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,
***************
*** 2146,2155 **** 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,
--- 2173,2187 ----
  							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,
*** a/src/backend/executor/execPartition.c
--- b/src/backend/executor/execPartition.c
***************
*** 44,49 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
--- 44,51 ----
   * 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
***************
*** 63,91 **** static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
   * RowExclusiveLock mode upon return from this function.
   */
  void
! ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
! 							   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 *
--- 65,85 ----
   * RowExclusiveLock mode upon return from this function.
   */
  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 *
***************
*** 98,157 **** ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
  	 * 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.  The user may have asked to check for
! 		 * conflicts within this leaf partition and do "nothing" instead of
! 		 * throwing an error.  Be prepared in that case by initializing the
! 		 * index information needed by ExecInsert() to perform speculative
! 		 * insertions.
! 		 */
! 		if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
! 			leaf_part_rri->ri_IndexRelationDescs == NULL)
! 			ExecOpenIndices(leaf_part_rri,
! 							mtstate != NULL &&
! 							mtstate->mt_onconflict != ONCONFLICT_NONE);
  
! 		estate->es_leaf_result_relations =
! 			lappend(estate->es_leaf_result_relations, leaf_part_rri);
  
! 		(*partitions)[i] = leaf_part_rri++;
! 		i++;
! 	}
  }
  
  /*
--- 92,152 ----
  	 * processing.
  	 */
  	*partition_tuple_slot = MakeTupleTableSlot();
+ }
  
! /*
!  * ExecInitPartition -- Prepare ResultRelInfo and tuple conversion map for
!  * the partition with OID 'partOid'
!  */
! void
! ExecInitPartition(ModifyTableState *mtstate,
! 				  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.  The user may have asked to check for conflicts
! 	 * within this leaf partition and do "nothing" instead of throwing an
! 	 * error.  Be prepared in that case by initializing the index information
! 	 * needed by ExecInsert() to perform speculative insertions.
! 	 */
! 	if (partRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
! 		partRelInfo->ri_IndexRelationDescs == NULL)
! 		ExecOpenIndices(partRelInfo,
! 						mtstate != NULL &&
! 						mtstate->mt_onconflict != ONCONFLICT_NONE);
  
! 	/* 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
***************
*** 306,316 **** 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;
--- 306,322 ----
  		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;
***************
*** 1947,1963 **** 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(mtstate,
! 									   rel,
! 									   node->nominalRelation,
! 									   estate,
  									   &partition_dispatch_info,
  									   &partitions,
  									   &partition_tupconv_maps,
  									   &partition_tuple_slot,
--- 1953,1969 ----
  		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,
***************
*** 1968,1973 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1974,2018 ----
  		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(mtstate,
+ 							  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++;
+ 		}
  	}
  
  	/*
***************
*** 2393,2398 **** ExecEndModifyTable(ModifyTableState *node)
--- 2438,2448 ----
  	{
  		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"
***************
*** 6439,6444 **** make_modifytable(PlannerInfo *root,
--- 6440,6446 ----
  	ModifyTable *node = makeNode(ModifyTable);
  	List	   *fdw_private_list;
  	Bitmapset  *direct_modify_plans;
+ 	List	   *partition_rels;
  	ListCell   *lc;
  	int			i;
  
***************
*** 6562,6567 **** make_modifytable(PlannerInfo *root,
--- 6564,6698 ----
  	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;
+ 
+ 				/* Replace the root query with the child query. */
+ 				root->parse = child_parse;
+ 
+ 				/* 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);
+ 				subplan->targetlist = tlist;
+ 
+ 				fdw_private = fdwroutine->PlanForeignModify(root,
+ 															node,
+ 															child_rti,
+ 															0);
+ 
+ 				subplan->targetlist = saved_tlist;
+ 				node->nominalRelation = nominalRelation;
+ 				node->resultRelations = list_make1_int(nominalRelation);
+ 				node->withCheckOptionLists = saved_withCheckOptionLists;
+ 				node->returningLists = saved_returningLists;
+ 				root->parse = parent_parses[nominalRelation];
+ 			}
+ 
+ 			partition_rels = lappend_int(partition_rels, child_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
***************
*** 879,888 **** 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
--- 879,890 ----
  		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
***************
*** 858,863 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 858,867 ----
  				{
  					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
***************
*** 1975,1982 **** 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);
--- 1975,1983 ----
  			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);
***************
*** 2326,2332 **** 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
--- 2327,2333 ----
  }
  
  /*
!  * 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
***************
*** 2338,2345 **** 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)
--- 2339,2344 ----
*** 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
***************
*** 2899,2911 **** 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
--- 2899,2904 ----
*** a/src/include/executor/execPartition.h
--- b/src/include/executor/execPartition.h
***************
*** 49,63 **** typedef struct PartitionDispatchData
  
  typedef struct PartitionDispatchData *PartitionDispatch;
  
! extern void ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
! 							   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,
--- 49,68 ----
  
  typedef struct PartitionDispatchData *PartitionDispatch;
  
! 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(ModifyTableState *mtstate,
+ 				  EState *estate,
+ 				  ResultRelInfo *rootRelInfo,
+ 				  Oid partOid,
+ 				  Index partRTindex,
+ 				  ResultRelInfo *partRelInfo,
+ 				  TupleConversionMap **partTupConvMap);
  extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
  				  PartitionDispatch *pd,
  				  TupleTableSlot *slot,
*** 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);
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 405,418 **** 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;
  
  /* ----------------
--- 405,421 ----
  	/* 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;
  
  /* ----------------
