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

text/plain

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

Patch

Format: context
Series: patch v1
File+
contrib/postgres_fdw/expected/postgres_fdw.out 138 0
contrib/postgres_fdw/sql/postgres_fdw.sql 40 0
src/backend/commands/explain.c 97 0
src/backend/executor/execMain.c 69 0
src/backend/executor/nodeModifyTable.c 51 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 70 0
src/backend/optimizer/plan/planner.c 2 0
src/backend/optimizer/plan/setrefs.c 4 0
src/backend/parser/analyze.c 7 0
src/backend/rewrite/rewriteHandler.c 0 0
src/include/nodes/execnodes.h 2 0
src/include/nodes/plannodes.h 4 0
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 6931,6936 **** NOTICE:  drop cascades to foreign table bar2
--- 6931,7074 ----
  drop table loct1;
  drop table loct2;
  -- ===================================================================
+ -- test tuple routing to foreign-table partitions
+ -- ===================================================================
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Insert on public.pt
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+ 
+ insert into pt values (1, 1), (2, 1);
+ select tableoid::regclass, * FROM pt;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  remp     | 2 | 1
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM locp;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+ (1 row)
+ 
+ select tableoid::regclass, * FROM remp;
+  tableoid | a | b 
+ ----------+---+---
+  remp     | 2 | 1
+ (1 row)
+ 
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+                                    QUERY PLAN                                   
+ --------------------------------------------------------------------------------
+  Insert on public.pt
+    Output: pt.a, pt.b
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2) RETURNING a, b
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (7 rows)
+ 
+ insert into pt values (1, 2), (2, 2) returning *;
+  a | b 
+ ---+---
+  1 | 2
+  2 | 2
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM pt;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+  remp     | 2 | 1
+  remp     | 2 | 2
+ (4 rows)
+ 
+ select tableoid::regclass, * FROM locp;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+ (2 rows)
+ 
+ select tableoid::regclass, * FROM remp;
+  tableoid | a | b 
+ ----------+---+---
+  remp     | 2 | 1
+  remp     | 2 | 2
+ (2 rows)
+ 
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Insert on public.pt
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locfoo(a, b) VALUES ($1, $2)
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+ 
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Insert on public.pt
+    Insert on public.locp
+    Foreign Insert on public.remp
+      Remote SQL: INSERT INTO public.locbar(a, b) VALUES ($1, $2)
+    ->  Values Scan on "*VALUES*"
+          Output: "*VALUES*".column1, "*VALUES*".column2
+ (6 rows)
+ 
+ execute q1;
+ select tableoid::regclass, * FROM pt;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+  locp     | 1 | 3
+  remp     | 2 | 1
+  remp     | 2 | 2
+  remp     | 2 | 3
+ (6 rows)
+ 
+ select tableoid::regclass, * FROM locp;
+  tableoid | a | b 
+ ----------+---+---
+  locp     | 1 | 1
+  locp     | 1 | 2
+  locp     | 1 | 3
+ (3 rows)
+ 
+ select tableoid::regclass, * FROM remp;
+  tableoid | a | b 
+ ----------+---+---
+  remp     | 2 | 1
+  remp     | 2 | 2
+  remp     | 2 | 3
+ (3 rows)
+ 
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+ -- ===================================================================
  -- test IMPORT FOREIGN SCHEMA
  -- ===================================================================
  CREATE SCHEMA import_source;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1615,1620 **** drop table loct1;
--- 1615,1660 ----
  drop table loct2;
  
  -- ===================================================================
+ -- test tuple routing to foreign-table partitions
+ -- ===================================================================
+ 
+ create table pt (a int, b int) partition by list (a);
+ create table locp partition of pt for values in (1);
+ create table locfoo (a int check (a in (2)), b int);
+ create foreign table remp partition of pt for values in (2) server loopback options (table_name 'locfoo');
+ 
+ explain (verbose, costs off)
+ insert into pt values (1, 1), (2, 1);
+ insert into pt values (1, 1), (2, 1);
+ 
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+ 
+ explain (verbose, costs off)
+ insert into pt values (1, 2), (2, 2) returning *;
+ insert into pt values (1, 2), (2, 2) returning *;
+ 
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+ 
+ prepare q1 as insert into pt values (1, 3), (2, 3);
+ explain (verbose, costs off) execute q1;
+ alter table locfoo rename to locbar;
+ alter foreign table remp options (set table_name 'locbar');
+ explain (verbose, costs off) execute q1;
+ execute q1;
+ 
+ select tableoid::regclass, * FROM pt;
+ select tableoid::regclass, * FROM locp;
+ select tableoid::regclass, * FROM remp;
+ 
+ deallocate q1;
+ drop table pt;
+ drop table locbar;
+ 
+ -- ===================================================================
  -- test IMPORT FOREIGN SCHEMA
  -- ===================================================================
  
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 116,121 **** static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
--- 116,125 ----
  static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
  static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
  					  ExplainState *es);
+ static void show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ 				   ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ 				   bool main_target, int subplan_index,
+ 				   const char *operation, bool labeltarget, ExplainState *es);
  static void ExplainMemberNodes(List *plans, PlanState **planstates,
  				   List *ancestors, ExplainState *es);
  static void ExplainSubPlans(List *plans, List *ancestors,
***************
*** 821,826 **** ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
--- 825,841 ----
  			if (((ModifyTable *) plan)->exclRelRTI)
  				*rels_used = bms_add_member(*rels_used,
  											((ModifyTable *) plan)->exclRelRTI);
+ 			if (((ModifyTable *) plan)->partition_rels)
+ 			{
+ 				ListCell   *lc;
+ 
+ 				foreach(lc, ((ModifyTable *) plan)->partition_rels)
+ 				{
+ 					Index		rti = lfirst_int(lc);
+ 
+ 					*rels_used = bms_add_member(*rels_used, rti);
+ 				}
+ 			}
  			break;
  		default:
  			break;
***************
*** 2792,2853 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
  	if (labeltargets)
  		ExplainOpenGroup("Target Tables", "Target Tables", false, es);
  
  	for (j = 0; j < mtstate->mt_nplans; j++)
  	{
  		ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
  		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
! 		if (labeltargets)
! 		{
! 			/* Open a group for this target */
! 			ExplainOpenGroup("Target Table", NULL, true, es);
! 
! 			/*
! 			 * In text mode, decorate each target with operation type, so that
! 			 * ExplainTargetRel's output of " on foo" will read nicely.
! 			 */
! 			if (es->format == EXPLAIN_FORMAT_TEXT)
! 			{
! 				appendStringInfoSpaces(es->str, es->indent * 2);
! 				appendStringInfoString(es->str,
! 									   fdwroutine ? foperation : operation);
! 			}
! 
! 			/* Identify target */
! 			ExplainTargetRel((Plan *) node,
! 							 resultRelInfo->ri_RangeTableIndex,
! 							 es);
! 
! 			if (es->format == EXPLAIN_FORMAT_TEXT)
! 			{
! 				appendStringInfoChar(es->str, '\n');
! 				es->indent++;
! 			}
! 		}
! 
! 		/* Give FDW a chance if needed */
! 		if (!resultRelInfo->ri_usesFdwDirectModify &&
! 			fdwroutine != NULL &&
! 			fdwroutine->ExplainForeignModify != NULL)
! 		{
! 			List	   *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
! 
! 			fdwroutine->ExplainForeignModify(mtstate,
! 											 resultRelInfo,
! 											 fdw_private,
! 											 j,
! 											 es);
! 		}
  
! 		if (labeltargets)
! 		{
! 			/* Undo the indentation we added in text format */
! 			if (es->format == EXPLAIN_FORMAT_TEXT)
! 				es->indent--;
  
! 			/* Close the group */
! 			ExplainCloseGroup("Target Table", NULL, true, es);
! 		}
  	}
  
  	/* Gather names of ON CONFLICT arbiter indexes */
--- 2807,2835 ----
  	if (labeltargets)
  		ExplainOpenGroup("Target Tables", "Target Tables", false, es);
  
+ 	/* Print main target(s) */
  	for (j = 0; j < mtstate->mt_nplans; j++)
  	{
  		ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
  		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
! 		show_actual_target(mtstate, node, resultRelInfo, fdwroutine, true, j,
! 						   fdwroutine ? foperation : operation, labeltargets,
! 						   es);
! 	}
  
! 	/* If this is an INSERT into a partitioned table, print partitions */
! 	for (j = 0; j < mtstate->mt_num_partitions; j++)
! 	{
! 		/*
! 		 * We arrange mt_partitions in the plan's partition_rels list order,
! 		 * which is the same order as for UPDATE/DELETE cases.
! 		 */
! 		ResultRelInfo *resultRelInfo = mtstate->mt_partitions + mtstate->mt_partition_indexes[j];
! 		FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
  
! 		show_actual_target(mtstate, node, resultRelInfo, fdwroutine, false, j,
! 						   fdwroutine ? foperation : operation, true, es);
  	}
  
  	/* Gather names of ON CONFLICT arbiter indexes */
***************
*** 2904,2909 **** show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
--- 2886,2957 ----
  }
  
  /*
+  * Show an actual target relation
+  */
+ static void
+ show_actual_target(ModifyTableState *mtstate, ModifyTable *node,
+ 				   ResultRelInfo *resultRelInfo, FdwRoutine *fdwroutine,
+ 				   bool main_target, int subplan_index,
+ 				   const char *operation, bool labeltarget, ExplainState *es)
+ {
+ 	if (labeltarget)
+ 	{
+ 		/* Open a group for this target */
+ 		ExplainOpenGroup("Target Table", NULL, true, es);
+ 
+ 		/*
+ 		 * In text mode, decorate each target with operation type, so that
+ 		 * ExplainTargetRel's output of " on foo" will read nicely.
+ 		 */
+ 		if (es->format == EXPLAIN_FORMAT_TEXT)
+ 		{
+ 			appendStringInfoSpaces(es->str, es->indent * 2);
+ 			appendStringInfoString(es->str, operation);
+ 		}
+ 
+ 		/* Identify target */
+ 		ExplainTargetRel((Plan *) node,
+ 						 resultRelInfo->ri_RangeTableIndex,
+ 						 es);
+ 
+ 		if (es->format == EXPLAIN_FORMAT_TEXT)
+ 		{
+ 			appendStringInfoChar(es->str, '\n');
+ 			es->indent++;
+ 		}
+ 	}
+ 
+ 	/* Give FDW a chance if needed */
+ 	if (fdwroutine != NULL &&
+ 		fdwroutine->ExplainForeignModify != NULL &&
+ 		!resultRelInfo->ri_usesFdwDirectModify)
+ 	{
+ 		List	   *fdw_private;
+ 
+ 		if (main_target)
+ 			fdw_private = (List *) list_nth(node->fdwPrivLists, subplan_index);
+ 		else
+ 			fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, subplan_index);
+ 
+ 		fdwroutine->ExplainForeignModify(mtstate,
+ 										 resultRelInfo,
+ 										 fdw_private,
+ 										 main_target ? subplan_index : 0,
+ 										 es);
+ 	}
+ 
+ 	if (labeltarget)
+ 	{
+ 		/* Undo the indentation we added in text format */
+ 		if (es->format == EXPLAIN_FORMAT_TEXT)
+ 			es->indent--;
+ 
+ 		/* Close the group */
+ 		ExplainCloseGroup("Target Table", NULL, true, es);
+ 	}
+ }
+ 
+ /*
   * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
   * BitmapAnd, or BitmapOr node.
   *
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 92,97 **** static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
--- 92,99 ----
  						  Bitmapset *modifiedCols,
  						  AclMode requiredPerms);
  static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
+ static ResultRelInfo *ExecFindResultRelInfo(EState *estate, Oid relid,
+ 											bool missing_ok);
  static char *ExecBuildSlotValueDescription(Oid reloid,
  							  TupleTableSlot *slot,
  							  TupleDesc tupdesc,
***************
*** 1360,1365 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1362,1392 ----
  }
  
  /*
+  * ExecFindResultRelInfo -- find the ResultRelInfo struct for given relation OID
+  *
+  * If no such struct, either return NULL or throw error depending on missing_ok
+  */
+ static ResultRelInfo *
+ ExecFindResultRelInfo(EState *estate, Oid relid, bool missing_ok)
+ {
+ 	ResultRelInfo *rInfo;
+ 	int			nr;
+ 
+ 	rInfo = estate->es_result_relations;
+ 	nr = estate->es_num_result_relations;
+ 	while (nr > 0)
+ 	{
+ 		if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ 			return rInfo;
+ 		rInfo++;
+ 		nr--;
+ 	}
+ 	if (!missing_ok)
+ 		elog(ERROR, "failed to find ResultRelInfo for relation %u", relid);
+ 	return NULL;
+ }
+ 
+ /*
   *		ExecGetTriggerResultRel
   *
   * Get a ResultRelInfo for a trigger target relation.  Most of the time,
***************
*** 1379,1399 **** ResultRelInfo *
  ExecGetTriggerResultRel(EState *estate, Oid relid)
  {
  	ResultRelInfo *rInfo;
- 	int			nr;
  	ListCell   *l;
  	Relation	rel;
  	MemoryContext oldcontext;
  
  	/* First, search through the query result relations */
! 	rInfo = estate->es_result_relations;
! 	nr = estate->es_num_result_relations;
! 	while (nr > 0)
! 	{
! 		if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
! 			return rInfo;
! 		rInfo++;
! 		nr--;
! 	}
  	/* Nope, but maybe we already made an extra ResultRelInfo for it */
  	foreach(l, estate->es_trig_target_relations)
  	{
--- 1406,1419 ----
  ExecGetTriggerResultRel(EState *estate, Oid relid)
  {
  	ResultRelInfo *rInfo;
  	ListCell   *l;
  	Relation	rel;
  	MemoryContext oldcontext;
  
  	/* First, search through the query result relations */
! 	rInfo = ExecFindResultRelInfo(estate, relid, true);
! 	if (rInfo)
! 		return rInfo;
  	/* Nope, but maybe we already made an extra ResultRelInfo for it */
  	foreach(l, estate->es_trig_target_relations)
  	{
***************
*** 1828,1833 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
--- 1848,1854 ----
  				   EState *estate)
  {
  	Relation	rel = resultRelInfo->ri_RelationDesc;
+ 	Oid			relid = RelationGetRelid(rel);
  	TupleDesc	tupdesc = RelationGetDescr(rel);
  	Bitmapset  *modifiedCols;
  	Bitmapset  *insertedCols;
***************
*** 1870,1877 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
--- 1891,1900 ----
  			HeapTuple	tuple = ExecFetchSlotTuple(slot);
  			TupleDesc	old_tupdesc = RelationGetDescr(rel);
  			TupleConversionMap *map;
+ 			ResultRelInfo *rInfo;
  
  			rel = resultRelInfo->ri_PartitionRoot;
+ 			relid = RelationGetRelid(rel);
  			tupdesc = RelationGetDescr(rel);
  			/* a reverse map */
  			map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1881,1892 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
  				tuple = do_convert_tuple(tuple, map);
  				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  			}
  		}
- 
- 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
- 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  		modifiedCols = bms_union(insertedCols, updatedCols);
! 		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
  												 slot,
  												 tupdesc,
  												 modifiedCols,
--- 1904,1921 ----
  				tuple = do_convert_tuple(tuple, map);
  				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  			}
+ 			/* Find the root table's ResultRelInfo */
+ 			rInfo = ExecFindResultRelInfo(estate, relid, false);
+ 			insertedCols = GetInsertedColumns(rInfo, estate);
+ 			updatedCols = GetUpdatedColumns(rInfo, estate);
+ 		}
+ 		else
+ 		{
+ 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  		}
  		modifiedCols = bms_union(insertedCols, updatedCols);
! 		val_desc = ExecBuildSlotValueDescription(relid,
  												 slot,
  												 tupdesc,
  												 modifiedCols,
***************
*** 1914,1919 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1943,1949 ----
  				TupleTableSlot *slot, EState *estate)
  {
  	Relation	rel = resultRelInfo->ri_RelationDesc;
+ 	Oid			relid = RelationGetRelid(rel);
  	TupleDesc	tupdesc = RelationGetDescr(rel);
  	TupleConstr *constr = tupdesc->constr;
  	Bitmapset  *modifiedCols;
***************
*** 1947,1954 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1977,1986 ----
  				{
  					HeapTuple	tuple = ExecFetchSlotTuple(slot);
  					TupleConversionMap *map;
+ 					ResultRelInfo *rInfo;
  
  					rel = resultRelInfo->ri_PartitionRoot;
+ 					relid = RelationGetRelid(rel);
  					tupdesc = RelationGetDescr(rel);
  					/* a reverse map */
  					map = convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1958,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
  						tuple = do_convert_tuple(tuple, map);
  						ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  					}
  				}
- 
- 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
- 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  				modifiedCols = bms_union(insertedCols, updatedCols);
! 				val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
  														 slot,
  														 tupdesc,
  														 modifiedCols,
--- 1990,2007 ----
  						tuple = do_convert_tuple(tuple, map);
  						ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  					}
+ 					/* Find the root table's ResultRelInfo */
+ 					rInfo = ExecFindResultRelInfo(estate, relid, false);
+ 					insertedCols = GetInsertedColumns(rInfo, estate);
+ 					updatedCols = GetUpdatedColumns(rInfo, estate);
+ 				}
+ 				else
+ 				{
+ 					insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ 					updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  				}
  				modifiedCols = bms_union(insertedCols, updatedCols);
! 				val_desc = ExecBuildSlotValueDescription(relid,
  														 slot,
  														 tupdesc,
  														 modifiedCols,
***************
*** 1987,1992 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 2025,2031 ----
  		{
  			char	   *val_desc;
  			Relation	orig_rel = rel;
+ 			Oid			relid = RelationGetRelid(rel);
  
  			/* See the comment above. */
  			if (resultRelInfo->ri_PartitionRoot)
***************
*** 1994,2001 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 2033,2042 ----
  				HeapTuple	tuple = ExecFetchSlotTuple(slot);
  				TupleDesc	old_tupdesc = RelationGetDescr(rel);
  				TupleConversionMap *map;
+ 				ResultRelInfo *rInfo;
  
  				rel = resultRelInfo->ri_PartitionRoot;
+ 				relid = RelationGetRelid(rel);
  				tupdesc = RelationGetDescr(rel);
  				/* a reverse map */
  				map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2005,2016 **** ExecConstraints(ResultRelInfo *resultRelInfo,
  					tuple = do_convert_tuple(tuple, map);
  					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  				}
  			}
- 
- 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
- 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  			modifiedCols = bms_union(insertedCols, updatedCols);
! 			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
  													 slot,
  													 tupdesc,
  													 modifiedCols,
--- 2046,2063 ----
  					tuple = do_convert_tuple(tuple, map);
  					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  				}
+ 				/* Find the root table's ResultRelInfo */
+ 				rInfo = ExecFindResultRelInfo(estate, relid, false);
+ 				insertedCols = GetInsertedColumns(rInfo, estate);
+ 				updatedCols = GetUpdatedColumns(rInfo, estate);
+ 			}
+ 			else
+ 			{
+ 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  			}
  			modifiedCols = bms_union(insertedCols, updatedCols);
! 			val_desc = ExecBuildSlotValueDescription(relid,
  													 slot,
  													 tupdesc,
  													 modifiedCols,
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 304,315 **** ExecInsert(ModifyTableState *mtstate,
  		saved_resultRelInfo = resultRelInfo;
  		resultRelInfo = mtstate->mt_partitions + leaf_part_index;
  
- 		/* We do not yet have a way to insert into a foreign partition */
- 		if (resultRelInfo->ri_FdwRoutine)
- 			ereport(ERROR,
- 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- 					 errmsg("cannot route inserted tuples to a foreign table")));
- 
  		/* For ExecInsertIndexTuples() to work on the partition's indexes */
  		estate->es_result_relation_info = resultRelInfo;
  
--- 304,309 ----
***************
*** 1925,1930 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
--- 1919,1970 ----
  		mtstate->mt_num_partitions = num_partitions;
  		mtstate->mt_partition_tupconv_maps = partition_tupconv_maps;
  		mtstate->mt_partition_tuple_slot = partition_tuple_slot;
+ 
+ 		mtstate->mt_partition_indexes = (int *)
+ 			palloc0(num_partitions * sizeof(int));
+ 		i = 0;
+ 		foreach(l, node->partition_rels)
+ 		{
+ 			Index		rti = lfirst_int(l);
+ 			Oid			relid = getrelid(rti, estate->es_range_table);
+ 			bool		found;
+ 			int			j;
+ 
+ 			/* First, find the ResultRelInfo for the partition */
+ 			found = false;
+ 			for (j = 0; j < mtstate->mt_num_partitions; j++)
+ 			{
+ 				resultRelInfo = partitions + j;
+ 
+ 				if (RelationGetRelid(resultRelInfo->ri_RelationDesc) == relid)
+ 				{
+ 					Assert(mtstate->mt_partition_indexes[i] == 0);
+ 					mtstate->mt_partition_indexes[i] = j;
+ 					found = true;
+ 					break;
+ 				}
+ 			}
+ 			if (!found)
+ 				elog(ERROR, "failed to find ResultRelInfo for rangetable index %u", rti);
+ 
+ 			/* Fix the rangetable index of the ResultRelInfo */
+ 			resultRelInfo->ri_RangeTableIndex = rti;
+ 
+ 			/* Let FDWs init themselves for foreign-table result rels */
+ 			if (resultRelInfo->ri_FdwRoutine != NULL &&
+ 				resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ 			{
+ 				List	   *fdw_private = (List *) list_nth(node->fdwPartitionPrivLists, i);
+ 
+ 				resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ 																 resultRelInfo,
+ 																 fdw_private,
+ 																 0,
+ 																 eflags);
+ 			}
+ 
+ 			i++;
+ 		}
  	}
  
  	/* Build state for collecting transition tuples */
***************
*** 2345,2350 **** ExecEndModifyTable(ModifyTableState *node)
--- 2385,2395 ----
  	{
  		ResultRelInfo *resultRelInfo = node->mt_partitions + i;
  
+ 		if (resultRelInfo->ri_FdwRoutine != NULL &&
+ 			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ 			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+ 														   resultRelInfo);
+ 
  		ExecCloseIndices(resultRelInfo);
  		heap_close(resultRelInfo->ri_RelationDesc, NoLock);
  	}
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 204,209 **** _copyModifyTable(const ModifyTable *from)
--- 204,210 ----
  	COPY_SCALAR_FIELD(canSetTag);
  	COPY_SCALAR_FIELD(nominalRelation);
  	COPY_NODE_FIELD(partitioned_rels);
+ 	COPY_NODE_FIELD(partition_rels);
  	COPY_NODE_FIELD(resultRelations);
  	COPY_SCALAR_FIELD(resultRelIndex);
  	COPY_SCALAR_FIELD(rootResultRelIndex);
***************
*** 220,225 **** _copyModifyTable(const ModifyTable *from)
--- 221,227 ----
  	COPY_NODE_FIELD(onConflictWhere);
  	COPY_SCALAR_FIELD(exclRelRTI);
  	COPY_NODE_FIELD(exclRelTlist);
+ 	COPY_NODE_FIELD(fdwPartitionPrivLists);
  
  	return newnode;
  }
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 349,354 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 349,355 ----
  	WRITE_BOOL_FIELD(canSetTag);
  	WRITE_UINT_FIELD(nominalRelation);
  	WRITE_NODE_FIELD(partitioned_rels);
+ 	WRITE_NODE_FIELD(partition_rels);
  	WRITE_NODE_FIELD(resultRelations);
  	WRITE_INT_FIELD(resultRelIndex);
  	WRITE_INT_FIELD(rootResultRelIndex);
***************
*** 365,370 **** _outModifyTable(StringInfo str, const ModifyTable *node)
--- 366,372 ----
  	WRITE_NODE_FIELD(onConflictWhere);
  	WRITE_UINT_FIELD(exclRelRTI);
  	WRITE_NODE_FIELD(exclRelTlist);
+ 	WRITE_NODE_FIELD(fdwPartitionPrivLists);
  }
  
  static void
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 1547,1552 **** _readModifyTable(void)
--- 1547,1553 ----
  	READ_BOOL_FIELD(canSetTag);
  	READ_UINT_FIELD(nominalRelation);
  	READ_NODE_FIELD(partitioned_rels);
+ 	READ_NODE_FIELD(partition_rels);
  	READ_NODE_FIELD(resultRelations);
  	READ_INT_FIELD(resultRelIndex);
  	READ_INT_FIELD(rootResultRelIndex);
***************
*** 1563,1568 **** _readModifyTable(void)
--- 1564,1570 ----
  	READ_NODE_FIELD(onConflictWhere);
  	READ_UINT_FIELD(exclRelRTI);
  	READ_NODE_FIELD(exclRelTlist);
+ 	READ_NODE_FIELD(fdwPartitionPrivLists);
  
  	READ_DONE();
  }
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 35,40 ****
--- 35,41 ----
  #include "optimizer/planmain.h"
  #include "optimizer/planner.h"
  #include "optimizer/predtest.h"
+ #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
  #include "optimizer/subselect.h"
  #include "optimizer/tlist.h"
***************
*** 6405,6410 **** make_modifytable(PlannerInfo *root,
--- 6406,6412 ----
  	ModifyTable *node = makeNode(ModifyTable);
  	List	   *fdw_private_list;
  	Bitmapset  *direct_modify_plans;
+ 	List	   *partition_rels;
  	ListCell   *lc;
  	int			i;
  
***************
*** 6525,6530 **** make_modifytable(PlannerInfo *root,
--- 6527,6600 ----
  	node->fdwPrivLists = fdw_private_list;
  	node->fdwDirectModifyPlans = direct_modify_plans;
  
+ 	/*
+ 	 * Also, if this is an INSERT into a partitioned table, build a list of
+ 	 * RT indexes of partitions, and for each partition that is a foreign table,
+ 	 * allow the FDW to construct private plan data and accumulate it all into
+ 	 * another list.
+ 	 */
+ 	partition_rels = NIL;
+ 	fdw_private_list = NIL;
+ 	if (operation == CMD_INSERT)
+ 	{
+ 		Index		rti = linitial_int(resultRelations);
+ 
+ 		if (planner_rt_fetch(rti, root)->relkind == RELKIND_PARTITIONED_TABLE)
+ 		{
+ 			foreach(lc, root->append_rel_list)
+ 			{
+ 				AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
+ 				Index		part_rti = appinfo->child_relid;
+ 				RangeTblEntry *part_rte;
+ 				FdwRoutine *fdwroutine = NULL;
+ 				List	   *fdw_private = NIL;
+ 
+ 				/* append_rel_list contains all append rels; ignore others */
+ 				if (appinfo->parent_relid != rti)
+ 					continue;
+ 
+ 				part_rte = planner_rt_fetch(part_rti, root);
+ 				Assert(part_rte->rtekind == RTE_RELATION);
+ 
+ 				if (part_rte->relkind == RELKIND_FOREIGN_TABLE)
+ 					fdwroutine = GetFdwRoutineByRelId(part_rte->relid);
+ 
+ 				if (fdwroutine != NULL &&
+ 					fdwroutine->PlanForeignModify != NULL)
+ 				{
+ 					PlannerInfo *part_root;
+ 					ModifyTable *part_node;
+ 
+ 					/* Create a working copy of the PlannerInfo */
+ 					part_root = makeNode(PlannerInfo);
+ 					memcpy(part_root, root, sizeof(PlannerInfo));
+ 					/* Generate modified query with this partition as target */
+ 					part_root->parse = (Query *)
+ 						adjust_appendrel_attrs(root,
+ 											   (Node *) root->parse,
+ 											   appinfo);
+ 
+ 					/* Create a working copy of the ModifyTable */
+ 					part_node = copyObject(node);
+ 					part_node->nominalRelation = part_rti;
+ 					part_node->resultRelations = list_make1_int(part_rti);
+ 					part_node->returningLists =
+ 						list_make1(part_root->parse->returningList);
+ 
+ 					fdw_private = fdwroutine->PlanForeignModify(part_root,
+ 																part_node,
+ 																part_rti,
+ 																0);
+ 				}
+ 
+ 				partition_rels = lappend_int(partition_rels, part_rti);
+ 				fdw_private_list = lappend(fdw_private_list, fdw_private);
+ 			}
+ 		}
+ 	}
+ 	node->partition_rels = partition_rels;
+ 	node->fdwPartitionPrivLists = fdw_private_list;
+ 
  	return node;
  }
  
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 828,834 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  	 * Do the main planning.  If we have an inherited target relation, that
  	 * needs special processing, else go straight to grouping_planner.
  	 */
! 	if (parse->resultRelation &&
  		rt_fetch(parse->resultRelation, parse->rtable)->inh)
  		inheritance_planner(root);
  	else
--- 828,835 ----
  	 * Do the main planning.  If we have an inherited target relation, that
  	 * needs special processing, else go straight to grouping_planner.
  	 */
! 	if ((parse->commandType == CMD_UPDATE ||
! 		 parse->commandType == CMD_DELETE) &&
  		rt_fetch(parse->resultRelation, parse->rtable)->inh)
  		inheritance_planner(root);
  	else
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 850,855 **** set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
--- 850,859 ----
  				{
  					lfirst_int(l) += rtoffset;
  				}
+ 				foreach(l, splan->partition_rels)
+ 				{
+ 					lfirst_int(l) += rtoffset;
+ 				}
  				foreach(l, splan->resultRelations)
  				{
  					lfirst_int(l) += rtoffset;
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 542,547 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 542,554 ----
  	qry->resultRelation = setTargetTable(pstate, stmt->relation,
  										 false, false, targetPerms);
  
+ 	/*
+ 	 * If the target table is a partitioned table, reset the inh flag to true.
+ 	 */
+ 	rte = pstate->p_target_rangetblentry;
+ 	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+ 		rte->inh = true;
+ 
  	/* Validate stmt->cols list, or build default list if no list given */
  	icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
  	Assert(list_length(icolumns) == list_length(attrnos));
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 2859,2871 **** rewriteTargetView(Query *parsetree, Relation view)
  	new_rt_index = list_length(parsetree->rtable);
  
  	/*
- 	 * INSERTs never inherit.  For UPDATE/DELETE, we use the view query's
- 	 * inheritance flag for the base relation.
- 	 */
- 	if (parsetree->commandType == CMD_INSERT)
- 		new_rte->inh = false;
- 
- 	/*
  	 * Adjust the view's targetlist Vars to reference the new target RTE, ie
  	 * make their varnos be new_rt_index instead of base_rt_index.  There can
  	 * be no Vars for other rels in the tlist, so this is sufficient to pull
--- 2859,2864 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 963,968 **** typedef struct ModifyTableState
--- 963,970 ----
  	TupleConversionMap **mt_partition_tupconv_maps;
  	/* Per partition tuple conversion map */
  	TupleTableSlot *mt_partition_tuple_slot;
+ 	int		   *mt_partition_indexes;	/* indexes into mt_partitions for use
+ 										 * in explain.c */
  	struct TransitionCaptureState *mt_transition_capture;
  									/* controls transition table population */
  	TupleConversionMap **mt_transition_tupconv_maps;
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 219,224 **** typedef struct ModifyTable
--- 219,226 ----
  	Index		nominalRelation;	/* Parent RT index for use of EXPLAIN */
  	/* RT indexes of non-leaf tables in a partition tree */
  	List	   *partitioned_rels;
+ 	List	   *partition_rels;	/* RT indexes of leaf tables in a partition
+ 								 * tree */
  	List	   *resultRelations;	/* integer list of RT indexes */
  	int			resultRelIndex; /* index of first resultRel in plan's list */
  	int			rootResultRelIndex; /* index of the partitioned table root */
***************
*** 235,240 **** typedef struct ModifyTable
--- 237,244 ----
  	Node	   *onConflictWhere;	/* WHERE for ON CONFLICT UPDATE */
  	Index		exclRelRTI;		/* RTI of the EXCLUDED pseudo relation */
  	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
+ 	List	   *fdwPartitionPrivLists;	/* per-partition FDW private data
+ 										 * lists */
  } ModifyTable;
  
  /* ----------------