tuple-routing-to-foreign-partitions-v2.patch

text/plain

Filename: tuple-routing-to-foreign-partitions-v2.patch
Type: text/plain
Part: 0
Message: Re: Add support for tuple routing to foreign partitions

Patch

Format: context
Series: patch v2
File+
contrib/file_fdw/output/file_fdw.source 2 0
contrib/postgres_fdw/expected/postgres_fdw.out 138 0
contrib/postgres_fdw/sql/postgres_fdw.sql 40 0
src/backend/commands/copy.c 46 0
src/backend/commands/explain.c 113 0
src/backend/commands/tablecmds.c 1 0
src/backend/executor/execMain.c 82 0
src/backend/executor/nodeModifyTable.c 75 0
src/backend/nodes/copyfuncs.c 2 0
src/backend/nodes/outfuncs.c 2 0
src/backend/nodes/readfuncs.c 2 0
src/backend/optimizer/plan/createplan.c 97 0
src/backend/optimizer/plan/planner.c 4 0
src/backend/optimizer/plan/setrefs.c 4 0
src/backend/optimizer/prep/preptlist.c 3 3
src/backend/optimizer/prep/prepunion.c 4 0
src/backend/parser/analyze.c 7 0
src/backend/replication/logical/worker.c 1 0
src/backend/rewrite/rewriteHandler.c 0 0
src/include/executor/executor.h 8 0
src/include/nodes/execnodes.h 6 0
src/include/nodes/plannodes.h 4 0
*** 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;
***************
*** 342,348 **** SELECT tableoid::regclass, * FROM p2;
  (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   
--- 342,348 ----
  (2 rows)
  
  INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR
! ERROR:  cannot route inserted tuples to foreign table "p1"
  INSERT INTO pt VALUES (2, 'xyzzy');
  SELECT tableoid::regclass, * FROM pt;
   tableoid | a |   b   
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 7029,7034 **** NOTICE:  drop cascades to foreign table bar2
--- 7029,7172 ----
  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
***************
*** 1662,1667 **** drop table loct1;
--- 1662,1707 ----
  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/copy.c
--- b/src/backend/commands/copy.c
***************
*** 2417,2422 **** CopyFrom(CopyState cstate)
--- 2417,2423 ----
  					  cstate->rel,
  					  1,		/* dummy rangetable index */
  					  NULL,
+ 					  0,		/* dummy rangetable index */
  					  0);
  
  	ExecOpenIndices(resultRelInfo, false);
***************
*** 2446,2461 **** CopyFrom(CopyState cstate)
  	if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
  	{
  		PartitionDispatch *partition_dispatch_info;
  		ResultRelInfo *partitions;
  		TupleConversionMap **partition_tupconv_maps;
  		TupleTableSlot *partition_tuple_slot;
  		int			num_parted,
  					num_partitions;
  
  		ExecSetupPartitionTupleRouting(cstate->rel,
- 									   1,
- 									   estate,
  									   &partition_dispatch_info,
  									   &partitions,
  									   &partition_tupconv_maps,
  									   &partition_tuple_slot,
--- 2447,2465 ----
  	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,
***************
*** 2467,2472 **** CopyFrom(CopyState cstate)
--- 2471,2508 ----
  		cstate->partition_tupconv_maps = partition_tupconv_maps;
  		cstate->partition_tuple_slot = partition_tuple_slot;
  
+ 		partRelInfo = partitions;
+ 		i = 0;
+ 		foreach(l, partition_oids)
+ 		{
+ 			Oid			partOid = lfirst_oid(l);
+ 			Relation	partrel;
+ 
+ 			/* Prepare map and ResultRelInfo for the partition */
+ 			ExecInitPartition(estate,
+ 							  partOid,
+ 							  0,		/* dummy rangetable index */
+ 							  resultRelInfo,
+ 							  partRelInfo,
+ 							  &partition_tupconv_maps[i]);
+ 
+ 			/* 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
+ 			{
+ 				/* 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
***************
*** 2618,2628 **** 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
--- 2654,2669 ----
  			saved_resultRelInfo = resultRelInfo;
  			resultRelInfo = cstate->partitions + leaf_part_index;
  
! 			/* We do not yet have a way to copy into a foreign partition */
! 			if (!resultRelInfo->ri_PartitionIsValid)
! 			{
! 				/* Should be foreign */
! 				Assert(resultRelInfo->ri_FdwRoutine);
! 
  				ereport(ERROR,
  						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 						 errmsg("cannot route copied tuples to a foreign table")));
! 			}
  
  			/*
  			 * For ExecInsertIndexTuples() to work on the partition's indexes
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 116,121 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
--- 116,125 ----
  static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
  static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
  					  ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ 				   ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ 				   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,
***************
*** 834,839 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 838,854 ----
  			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;
***************
*** 2856,2916 **** 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);
  		}
  	}
  
--- 2871,2919 ----
  	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++)
! 	{
! 		ResultRelInfo *resultRelInfo = mtstate->mt_partitions + j;
! 		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
! 		if (resultRelInfo->ri_PartitionIsValid)
  		{
! 			Oid			partOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
! 			int			k;
! 			ListCell   *cell;
! 			bool		found;
  
! 			/* First, find the subplan index for the partition */
! 			found = false;
! 			k = 0;
! 			foreach(cell, node->partition_rels)
! 			{
! 				Index		partRTindex = lfirst_int(cell);
  
! 				if (getrelid(partRTindex, es->rtable) == partOid)
! 				{
! 					found = true;
! 					break;
! 				}
! 				k++;
! 			}
! 			if (!found)
! 				elog(ERROR, "failed to find subplan index for relation %u", partOid);
  
! 			show_actual_target(mtstate, node, resultRelInfo, fdwroutine, false, k,
! 							   fdwroutine ? foperation : operation, true, es);
  		}
  	}
  
***************
*** 2968,2973 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
--- 2971,3042 ----
  }
  
  /*
+  * 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/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 1403,1408 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1403,1409 ----
  						  rel,
  						  0,	/* dummy rangetable index */
  						  NULL,
+ 						  0,	/* dummy rangetable index */
  						  0);
  		resultRelInfo++;
  	}
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 114,120 **** static void ExecPartitionCheck(ResultRelInfo *resultRelInfo,
   * to be changed, however.
   */
  #define GetInsertedColumns(relinfo, estate) \
! 	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
  #define GetUpdatedColumns(relinfo, estate) \
  	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
  
--- 114,123 ----
   * to be changed, however.
   */
  #define GetInsertedColumns(relinfo, estate) \
! 	(rt_fetch((relinfo)->ri_PartitionRoot ? \
! 			  (relinfo)->ri_PartitionRootRTindex : \
! 			  (relinfo)->ri_RangeTableIndex, \
! 			  (estate)->es_range_table)->insertedCols)
  #define GetUpdatedColumns(relinfo, estate) \
  	(rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
  
***************
*** 854,859 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 857,863 ----
  							  resultRelation,
  							  resultRelationIndex,
  							  NULL,
+ 							  0,		/* dummy rangetable index */
  							  estate->es_instrument);
  			resultRelInfo++;
  		}
***************
*** 893,898 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 897,903 ----
  								  resultRelDesc,
  								  lfirst_int(l),
  								  NULL,
+ 								  0,		/* dummy rangetable index */
  								  estate->es_instrument);
  				resultRelInfo++;
  			}
***************
*** 1102,1113 **** CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
--- 1107,1121 ----
  	Relation	resultRel = resultRelInfo->ri_RelationDesc;
  	TriggerDesc *trigDesc = resultRel->trigdesc;
  	FdwRoutine *fdwroutine;
+ 	bool		is_valid;
  
  	switch (resultRel->rd_rel->relkind)
  	{
  		case RELKIND_RELATION:
  		case RELKIND_PARTITIONED_TABLE:
  			CheckCmdReplicaIdentity(resultRel, operation);
+ 			if (resultRelInfo->ri_PartitionRoot && operation == CMD_INSERT)
+ 				resultRelInfo->ri_PartitionIsValid = true;
  			break;
  		case RELKIND_SEQUENCE:
  			ereport(ERROR,
***************
*** 1174,1196 **** 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)
--- 1182,1209 ----
  			switch (operation)
  			{
  				case CMD_INSERT:
! 					is_valid = true;
  					if (fdwroutine->ExecForeignInsert == NULL)
! 					{
! 						if (!resultRelInfo->ri_PartitionRoot)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 									 errmsg("cannot insert into foreign table \"%s\"",
! 											RelationGetRelationName(resultRel))));
! 						is_valid = false;
! 					}
  					if (fdwroutine->IsForeignRelUpdatable != NULL &&
  						(fdwroutine->IsForeignRelUpdatable(resultRel) & (1 << CMD_INSERT)) == 0)
! 					{
! 						if (!resultRelInfo->ri_PartitionRoot)
! 							ereport(ERROR,
! 									(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! 									 errmsg("foreign table \"%s\" does not allow inserts",
! 											RelationGetRelationName(resultRel))));
! 						is_valid = false;
! 					}
! 					if (resultRelInfo->ri_PartitionRoot)
! 						resultRelInfo->ri_PartitionIsValid = is_valid;
  					break;
  				case CMD_UPDATE:
  					if (fdwroutine->ExecForeignUpdate == NULL)
***************
*** 1308,1313 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1321,1327 ----
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
  				  Relation partition_root,
+ 				  Index partition_root_rtindex,
  				  int instrument_options)
  {
  	List	   *partition_check = NIL;
***************
*** 1365,1370 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1379,1386 ----
  
  	resultRelInfo->ri_PartitionCheck = partition_check;
  	resultRelInfo->ri_PartitionRoot = partition_root;
+ 	resultRelInfo->ri_PartitionRootRTindex = partition_root_rtindex;
+ 	resultRelInfo->ri_PartitionIsValid = false;
  }
  
  /*
***************
*** 1447,1452 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
--- 1463,1469 ----
  					  rel,
  					  0,		/* dummy rangetable index */
  					  NULL,
+ 					  0,		/* dummy rangetable index */
  					  estate->es_instrument);
  	estate->es_trig_target_relations =
  		lappend(estate->es_trig_target_relations, rInfo);
***************
*** 3245,3250 **** EvalPlanQualEnd(EPQState *epqstate)
--- 3262,3269 ----
   * 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
***************
*** 3265,3291 **** EvalPlanQualEnd(EPQState *epqstate)
   */
  void
  ExecSetupPartitionTupleRouting(Relation rel,
- 							   Index resultRTindex,
- 							   EState *estate,
  							   PartitionDispatch **pd,
  							   ResultRelInfo **partitions,
  							   TupleConversionMap ***tup_conv_maps,
  							   TupleTableSlot **partition_tuple_slot,
  							   int *num_parted, int *num_partitions)
  {
- 	TupleDesc	tupDesc = RelationGetDescr(rel);
- 	List	   *leaf_parts;
- 	ListCell   *cell;
- 	int			i;
- 	ResultRelInfo *leaf_part_rri;
- 
  	/*
  	 * Get the information about the partition tree after locking all the
  	 * partitions.
  	 */
  	(void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
! 	*pd = RelationGetPartitionDispatchInfo(rel, num_parted, &leaf_parts);
! 	*num_partitions = list_length(leaf_parts);
  	*partitions = (ResultRelInfo *) palloc(*num_partitions *
  										   sizeof(ResultRelInfo));
  	*tup_conv_maps = (TupleConversionMap **) palloc0(*num_partitions *
--- 3284,3303 ----
   */
  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 *
***************
*** 3298,3352 **** ExecSetupPartitionTupleRouting(Relation rel,
  	 * processing.
  	 */
  	*partition_tuple_slot = MakeTupleTableSlot();
  
! 	leaf_part_rri = *partitions;
! 	i = 0;
! 	foreach(cell, leaf_parts)
! 	{
! 		Relation	partrel;
! 		TupleDesc	part_tupdesc;
! 
! 		/*
! 		 * We locked all the partitions above including the leaf partitions.
! 		 * Note that each of the relations in *partitions are eventually
! 		 * closed by the caller.
! 		 */
! 		partrel = heap_open(lfirst_oid(cell), NoLock);
! 		part_tupdesc = RelationGetDescr(partrel);
! 
! 		/*
! 		 * Save a tuple conversion map to convert a tuple routed to this
! 		 * partition from the parent's type to the partition's.
! 		 */
! 		(*tup_conv_maps)[i] = convert_tuples_by_name(tupDesc, part_tupdesc,
! 													 gettext_noop("could not convert row type"));
  
! 		InitResultRelInfo(leaf_part_rri,
! 						  partrel,
! 						  resultRTindex,
! 						  rel,
! 						  estate->es_instrument);
  
! 		/*
! 		 * Verify result relation is a valid target for INSERT.
! 		 */
! 		CheckValidResultRel(leaf_part_rri, CMD_INSERT);
  
! 		/*
! 		 * Open partition indices (remember we do not support ON CONFLICT in
! 		 * case of partitioned tables, so we do not need support information
! 		 * for speculative insertion)
! 		 */
! 		if (leaf_part_rri->ri_RelationDesc->rd_rel->relhasindex &&
! 			leaf_part_rri->ri_IndexRelationDescs == NULL)
! 			ExecOpenIndices(leaf_part_rri, false);
  
! 		estate->es_leaf_result_relations =
! 			lappend(estate->es_leaf_result_relations, leaf_part_rri);
  
! 		leaf_part_rri++;
! 		i++;
! 	}
  }
  
  /*
--- 3310,3366 ----
  	 * processing.
  	 */
  	*partition_tuple_slot = MakeTupleTableSlot();
+ }
  
! /*
!  * ExecInitPartition -- Prepare tuple conversion map and ResultRelInfo for
!  * the partition with OID 'partOid'
!  */
! void
! ExecInitPartition(EState *estate,
! 				  Oid partOid,
! 				  Index partRTindex,
! 				  ResultRelInfo *rootRelInfo,
! 				  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 a tuple 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"));
  
! 	/* Save a ResultRelInfo data for the partition. */
! 	InitResultRelInfo(partRelInfo,
! 					  partrel,
! 					  partRTindex,
! 					  rootrel,
! 					  rootRelInfo->ri_RangeTableIndex,
! 					  estate->es_instrument);
  
! 	/*
! 	 * Open partition indices (remember we do not support ON CONFLICT in
! 	 * case of partitioned tables, so we do not need support information
! 	 * for speculative insertion)
! 	 */
! 	if (partRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
! 		partRelInfo->ri_IndexRelationDescs == NULL)
! 		ExecOpenIndices(partRelInfo, false);
  
! 	estate->es_leaf_result_relations =
! 		lappend(estate->es_leaf_result_relations, partRelInfo);
  }
  
  /*
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 305,315 **** ExecInsert(ModifyTableState *mtstate,
  		saved_resultRelInfo = resultRelInfo;
  		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
  
! 		/* We do not yet have a way to insert into a foreign partition */
! 		if (resultRelInfo->ri_FdwRoutine)
  			ereport(ERROR,
  					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("cannot route inserted tuples to a foreign table")));
  
  		/* For ExecInsertIndexTuples() to work on the partition's indexes */
  		estate->es_result_relation_info = resultRelInfo;
--- 305,321 ----
  		saved_resultRelInfo = resultRelInfo;
  		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
  
! 		if (!resultRelInfo->ri_PartitionIsValid)
! 		{
! 			/* 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;
***************
*** 1912,1927 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
  		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
  	{
  		PartitionDispatch *partition_dispatch_info;
  		ResultRelInfo *partitions;
  		TupleConversionMap **partition_tupconv_maps;
  		TupleTableSlot *partition_tuple_slot;
  		int			num_parted,
  					num_partitions;
  
  		ExecSetupPartitionTupleRouting(rel,
- 									   node->nominalRelation,
- 									   estate,
  									   &partition_dispatch_info,
  									   &partitions,
  									   &partition_tupconv_maps,
  									   &partition_tuple_slot,
--- 1918,1935 ----
  		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;
+ 		Index		rootRTindex;
+ 		ResultRelInfo *partRelInfo;
  
  		ExecSetupPartitionTupleRouting(rel,
  									   &partition_dispatch_info,
+ 									   &partition_oids,
  									   &partitions,
  									   &partition_tupconv_maps,
  									   &partition_tuple_slot,
***************
*** 1932,1937 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1940,2002 ----
  		mtstate->mt_num_partitions = num_partitions;
  		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
  		mtstate->mt_partition_tuple_slot = partition_tuple_slot;
+ 
+ 		rootRTindex = mtstate->resultRelInfo->ri_RangeTableIndex;
+ 		partRelInfo = partitions;
+ 		i = 0;
+ 		foreach(l, partition_oids)
+ 		{
+ 			Oid			partOid = lfirst_oid(l);
+ 			Index		partRTindex;
+ 			int			j;
+ 			ListCell   *cell;
+ 			bool		found;
+ 
+ 			/* First, find the RT index for the partition */
+ 			found = false;
+ 			j = 0;
+ 			foreach(cell, node->partition_rels)
+ 			{
+ 				partRTindex = lfirst_int(cell);
+ 
+ 				if (getrelid(partRTindex, estate->es_range_table) == partOid)
+ 				{
+ 					found = true;
+ 					break;
+ 				}
+ 				j++;
+ 			}
+ 			if (!found)
+ 				elog(ERROR, "failed to find range table index for relation %u", partOid);
+ 
+ 			/* Prepare map and ResultRelInfo for the partition */
+ 			ExecInitPartition(estate,
+ 							  partOid,
+ 							  partRTindex,
+ 							  mtstate->resultRelInfo,
+ 							  partRelInfo,
+ 							  &partition_tupconv_maps[i]);
+ 
+ 			/* Verify the partition is a valid target for INSERT */
+ 			CheckValidResultRel(partRelInfo, CMD_INSERT);
+ 
+ 			/* If so, let the FDW 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, j);
+ 
+ 				partRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ 															   partRelInfo,
+ 															   fdw_private,
+ 															   0,
+ 															   eflags);
+ 			}
+ 
+ 			partRelInfo++;
+ 			i++;
+ 		}
  	}
  
  	/* Build state for collecting transition tuples */
***************
*** 2361,2366 **** ExecEndModifyTable(ModifyTableState *node)
--- 2426,2436 ----
  	{
  		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
***************
*** 367,372 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 367,373 ----
  	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);
***************
*** 383,388 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 384,390 ----
  	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
***************
*** 1562,1567 **** _readModifyTable(void)
--- 1562,1568 ----
  	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);
***************
*** 1578,1583 **** _readModifyTable(void)
--- 1579,1585 ----
  	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"
***************
*** 6412,6417 **** make_modifytable(PlannerInfo *root,
--- 6413,6419 ----
  	ModifyTable *node = makeNode(ModifyTable);
  	List	   *fdw_private_list;
  	Bitmapset  *direct_modify_plans;
+ 	List	   *partition_rels;
  	ListCell   *lc;
  	int			i;
  
***************
*** 6535,6540 **** make_modifytable(PlannerInfo *root,
--- 6537,6637 ----
  	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)
+ 		{
+ 			Query	   *saved_query = root->parse;
+ 			Index		saved_nominalRelation = node->nominalRelation;
+ 			List	   *saved_resultRelations = node->resultRelations;
+ 			List	   *saved_withCheckOptionLists = node->withCheckOptionLists;
+ 			List	   *saved_returningLists = node->returningLists;
+ 			Plan	   *subplan = (Plan *) linitial(node->plans);
+ 			List	   *saved_tlist = subplan->targetlist;
+ 
+ 			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)
+ 				{
+ 					List	   *tlist;
+ 
+ 					/*
+ 					 * Replace the Query node with the modified one that has
+ 					 * this partition as target.
+ 					 */
+ 					root->parse = (Query *)
+ 						adjust_appendrel_attrs(root,
+ 											   (Node *) root->parse,
+ 											   1, &appinfo);
+ 
+ 					/*
+ 					 * Likewise for the ModifyTable node.
+ 					 */
+ 					node->nominalRelation = part_rti;
+ 					node->resultRelations = list_make1_int(part_rti);
+ 					node->withCheckOptionLists =
+ 						list_make1(root->parse->withCheckOptions);
+ 					node->returningLists =
+ 						list_make1(root->parse->returningList);
+ 
+ 					/*
+ 					 * Adjust the subplan's tlist because the column list of
+ 					 * this partition might have a different column order
+ 					 * and/or a different set of dropped columns than the
+ 					 * partitioned table root.
+ 					 */
+ 					tlist = preprocess_targetlist(root,
+ 												  root->parse->targetList);
+ 					subplan->targetlist = tlist;
+ 
+ 					fdw_private = fdwroutine->PlanForeignModify(root,
+ 																node,
+ 																part_rti,
+ 																0);
+ 				}
+ 
+ 				partition_rels = lappend_int(partition_rels, part_rti);
+ 				fdw_private_list = lappend(fdw_private_list, fdw_private);
+ 			}
+ 
+ 			root->parse = saved_query;
+ 			node->nominalRelation = saved_nominalRelation;
+ 			node->resultRelations = saved_resultRelations;
+ 			node->withCheckOptionLists = saved_withCheckOptionLists;
+ 			node->returningLists = saved_returningLists;
+ 			subplan->targetlist = saved_tlist;
+ 		}
+ 	}
+ 	node->partition_rels = partition_rels;
+ 	node->fdwPartitionPrivLists = fdw_private_list;
+ 
  	return node;
  }
  
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 840,848 **** 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
--- 840,850 ----
  
  	/*
  	 * Do the main planning.  If we have an inherited target relation, that
! 	 * needs special processing except for INSERT cases, 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/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 22,30 ****
   * The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column
   * position, which expand_targetlist depends on, violates the above comment
   * because the sorting is only valid for the parent relation.  In inherited
!  * UPDATE cases, adjust_inherited_tlist runs in between to take care of fixing
!  * the tlists for child tables to keep expand_targetlist happy.  We do it like
!  * that because it's faster in typical non-inherited cases.
   *
   *
   * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
--- 22,30 ----
   * The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column
   * position, which expand_targetlist depends on, violates the above comment
   * because the sorting is only valid for the parent relation.  In inherited
!  * INSERT/UPDATE cases, adjust_inherited_tlist runs in between to take care of
!  * fixing the tlists for child tables to keep expand_targetlist happy.  We do
!  * it like that because it's faster in typical non-inherited cases.
   *
   *
   * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1929,1936 **** 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);
--- 1929,1937 ----
  			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);
***************
*** 2227,2233 **** adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
  }
  
  /*
!  * 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
--- 2228,2234 ----
  }
  
  /*
!  * 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
***************
*** 2239,2246 **** adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
   * 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)
--- 2240,2245 ----
*** 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/replication/logical/worker.c
--- b/src/backend/replication/logical/worker.c
***************
*** 198,204 **** create_estate_for_relation(LogicalRepRelMapEntry *rel)
  	estate->es_range_table = list_make1(rte);
  
  	resultRelInfo = makeNode(ResultRelInfo);
! 	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0);
  
  	estate->es_result_relations = resultRelInfo;
  	estate->es_num_result_relations = 1;
--- 198,204 ----
  	estate->es_range_table = list_make1(rte);
  
  	resultRelInfo = makeNode(ResultRelInfo);
! 	InitResultRelInfo(resultRelInfo, rel->localrel, 1, NULL, 0, 0);
  
  	estate->es_result_relations = resultRelInfo;
  	estate->es_num_result_relations = 1;
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 2890,2902 **** rewriteTargetView(Query *parsetree, Relation view)
  	new_rt_index = list_length(parsetree->rtable);
  
  	/*
- 	 * INSERTs never inherit.  For UPDATE/DELETE, we use the view query's
- 	 * inheritance flag for the base relation.
- 	 */
- 	if (parsetree->commandType == CMD_INSERT)
- 		new_rte->inh = false;
- 
- 	/*
  	 * Adjust the view's targetlist Vars to reference the new target RTE, ie
  	 * make their varnos be new_rt_index instead of base_rt_index.  There can
  	 * be no Vars for other rels in the tlist, so this is sufficient to pull
--- 2890,2895 ----
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 182,187 **** extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 182,188 ----
  				  Relation resultRelationDesc,
  				  Index resultRelationIndex,
  				  Relation partition_root,
+ 				  Index partition_root_rtindex,
  				  int instrument_options);
  extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
  extern void ExecCleanUpTriggerState(EState *estate);
***************
*** 207,219 **** extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
  					 HeapTuple tuple);
  extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
  extern void ExecSetupPartitionTupleRouting(Relation rel,
- 							   Index resultRTindex,
- 							   EState *estate,
  							   PartitionDispatch **pd,
  							   ResultRelInfo **partitions,
  							   TupleConversionMap ***tup_conv_maps,
  							   TupleTableSlot **partition_tuple_slot,
  							   int *num_parted, int *num_partitions);
  extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
  				  PartitionDispatch *pd,
  				  TupleTableSlot *slot,
--- 208,225 ----
  					 HeapTuple tuple);
  extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
  extern void ExecSetupPartitionTupleRouting(Relation rel,
  							   PartitionDispatch **pd,
+ 							   List **leaf_parts,
  							   ResultRelInfo **partitions,
  							   TupleConversionMap ***tup_conv_maps,
  							   TupleTableSlot **partition_tuple_slot,
  							   int *num_parted, int *num_partitions);
+ extern void ExecInitPartition(EState *estate,
+ 				  Oid partOid,
+ 				  Index partRTindex,
+ 				  ResultRelInfo *rootRelInfo,
+ 				  ResultRelInfo *partRelInfo,
+ 				  TupleConversionMap **partTupConvMap);
  extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
  				  PartitionDispatch *pd,
  				  TupleTableSlot *slot,
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 412,417 **** typedef struct ResultRelInfo
--- 412,423 ----
  
  	/* relation descriptor for root partitioned table */
  	Relation	ri_PartitionRoot;
+ 
+ 	/* range table index for root partitioned table */
+ 	Index		ri_PartitionRootRTindex;
+ 
+ 	/* 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;
  
  /* ----------------