*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 130,135 **** static void deparseTargetList(StringInfo buf,
--- 130,136 ----
  				  Relation rel,
  				  bool is_returning,
  				  Bitmapset *attrs_used,
+ 				  bool tableoid_needed,
  				  bool qualify_col,
  				  List **retrieved_attrs);
  static void deparseExplicitTargetList(List *tlist,
***************
*** 901,906 **** build_tlist_to_deparse(RelOptInfo *foreignrel)
--- 902,926 ----
  												  PVC_RECURSE_PLACEHOLDERS));
  	}
  
+ 	/* Also, add the Param representing the remote table OID, if it exists. */
+ 	if (fpinfo->tableoid_param)
+ 	{
+ 		TargetEntry *tle;
+ 
+ 		/*
+ 		 * Core code should have contained the Param in the given relation's
+ 		 * reltarget.
+ 		 */
+ 		Assert(list_member(foreignrel->reltarget->exprs,
+ 						   fpinfo->tableoid_param));
+ 
+ 		tle = makeTargetEntry((Expr *) copyObject(fpinfo->tableoid_param),
+ 							  list_length(tlist) + 1,
+ 							  NULL,
+ 							  false);
+ 		tlist = lappend(tlist, tle);
+ 	}
+ 
  	return tlist;
  }
  
***************
*** 1052,1058 **** deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
  		Relation	rel = heap_open(rte->relid, NoLock);
  
  		deparseTargetList(buf, rte, foreignrel->relid, rel, false,
! 						  fpinfo->attrs_used, false, retrieved_attrs);
  		heap_close(rel, NoLock);
  	}
  }
--- 1072,1080 ----
  		Relation	rel = heap_open(rte->relid, NoLock);
  
  		deparseTargetList(buf, rte, foreignrel->relid, rel, false,
! 						  fpinfo->attrs_used,
! 						  fpinfo->tableoid_param ? true : false,
! 						  false, retrieved_attrs);
  		heap_close(rel, NoLock);
  	}
  }
***************
*** 1093,1098 **** deparseFromExpr(List *quals, deparse_expr_cxt *context)
--- 1115,1122 ----
   * This is used for both SELECT and RETURNING targetlists; the is_returning
   * parameter is true only for a RETURNING targetlist.
   *
+  * For SELECT, the target list contains remote tableoid if tableoid_needed.
+  *
   * The tlist text is appended to buf, and we also create an integer List
   * of the columns being retrieved, which is returned to *retrieved_attrs.
   *
***************
*** 1105,1110 **** deparseTargetList(StringInfo buf,
--- 1129,1135 ----
  				  Relation rel,
  				  bool is_returning,
  				  Bitmapset *attrs_used,
+ 				  bool tableoid_needed,
  				  bool qualify_col,
  				  List **retrieved_attrs)
  {
***************
*** 1146,1152 **** deparseTargetList(StringInfo buf,
  
  	/*
  	 * Add ctid and oid if needed.  We currently don't support retrieving any
! 	 * other system columns.
  	 */
  	if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
  					  attrs_used))
--- 1171,1177 ----
  
  	/*
  	 * Add ctid and oid if needed.  We currently don't support retrieving any
! 	 * other system columns, except tableoid, which is retrieved if required.
  	 */
  	if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
  					  attrs_used))
***************
*** 1180,1185 **** deparseTargetList(StringInfo buf,
--- 1205,1224 ----
  		*retrieved_attrs = lappend_int(*retrieved_attrs,
  									   ObjectIdAttributeNumber);
  	}
+ 	if (tableoid_needed)
+ 	{
+ 		Assert(bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
+ 							 attrs_used));
+ 		Assert(!first);
+ 		Assert(!is_returning);
+ 		Assert(!qualify_col);
+ 
+ 		appendStringInfoString(buf, ", ");
+ 		appendStringInfoString(buf, "tableoid");
+ 
+ 		*retrieved_attrs = lappend_int(*retrieved_attrs,
+ 									   TableOidAttributeNumber);
+ 	}
  
  	/* Don't generate bad syntax if no undropped columns */
  	if (first && !is_returning)
***************
*** 1728,1734 **** deparseUpdateSql(StringInfo buf, RangeTblEntry *rte,
  	deparseRelation(buf, rel);
  	appendStringInfoString(buf, " SET ");
  
! 	pindex = 2;					/* ctid is always the first param */
  	first = true;
  	foreach(lc, targetAttrs)
  	{
--- 1767,1774 ----
  	deparseRelation(buf, rel);
  	appendStringInfoString(buf, " SET ");
  
! 	pindex = 3;					/* ctid and tableoid are always the two
! 								 * leading params */
  	first = true;
  	foreach(lc, targetAttrs)
  	{
***************
*** 1742,1748 **** deparseUpdateSql(StringInfo buf, RangeTblEntry *rte,
  		appendStringInfo(buf, " = $%d", pindex);
  		pindex++;
  	}
! 	appendStringInfoString(buf, " WHERE ctid = $1");
  
  	deparseReturningList(buf, rte, rtindex, rel,
  						 rel->trigdesc && rel->trigdesc->trig_update_after_row,
--- 1782,1788 ----
  		appendStringInfo(buf, " = $%d", pindex);
  		pindex++;
  	}
! 	appendStringInfoString(buf, " WHERE ctid = $1 AND tableoid = $2");
  
  	deparseReturningList(buf, rte, rtindex, rel,
  						 rel->trigdesc && rel->trigdesc->trig_update_after_row,
***************
*** 1858,1864 **** deparseDeleteSql(StringInfo buf, RangeTblEntry *rte,
  {
  	appendStringInfoString(buf, "DELETE FROM ");
  	deparseRelation(buf, rel);
! 	appendStringInfoString(buf, " WHERE ctid = $1");
  
  	deparseReturningList(buf, rte, rtindex, rel,
  						 rel->trigdesc && rel->trigdesc->trig_delete_after_row,
--- 1898,1904 ----
  {
  	appendStringInfoString(buf, "DELETE FROM ");
  	deparseRelation(buf, rel);
! 	appendStringInfoString(buf, " WHERE ctid = $1 AND tableoid = $2");
  
  	deparseReturningList(buf, rte, rtindex, rel,
  						 rel->trigdesc && rel->trigdesc->trig_delete_after_row,
***************
*** 1974,1980 **** deparseReturningList(StringInfo buf, RangeTblEntry *rte,
  
  	if (attrs_used != NULL)
  		deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false,
! 						  retrieved_attrs);
  	else
  		*retrieved_attrs = NIL;
  }
--- 2014,2020 ----
  
  	if (attrs_used != NULL)
  		deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false,
! 						  false, retrieved_attrs);
  	else
  		*retrieved_attrs = NIL;
  }
***************
*** 2147,2154 **** deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte,
  		}
  
  		appendStringInfoString(buf, "ROW(");
! 		deparseTargetList(buf, rte, varno, rel, false, attrs_used, qualify_col,
! 						  &retrieved_attrs);
  		appendStringInfoChar(buf, ')');
  
  		/* Complete the CASE WHEN statement started above. */
--- 2187,2194 ----
  		}
  
  		appendStringInfoString(buf, "ROW(");
! 		deparseTargetList(buf, rte, varno, rel, false, attrs_used, false,
! 						  qualify_col, &retrieved_attrs);
  		appendStringInfoChar(buf, ')');
  
  		/* Complete the CASE WHEN statement started above. */
***************
*** 2514,2519 **** deparseConst(Const *node, deparse_expr_cxt *context, int showtype)
--- 2554,2575 ----
  static void
  deparseParam(Param *node, deparse_expr_cxt *context)
  {
+ 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) context->foreignrel->fdw_private;
+ 
+ 	/*
+ 	 * If the Param is the one representing the remote table OID, the value
+ 	 * needs to be produced; fetch the remote table OID, instead.
+ 	 */
+ 	if (equal(node, (Node *) fpinfo->tableoid_param))
+ 	{
+ 		Assert(bms_is_member(context->root->parse->resultRelation,
+ 							 context->foreignrel->relids));
+ 		Assert(bms_membership(context->foreignrel->relids) == BMS_MULTIPLE);
+ 		ADD_REL_QUALIFIER(context->buf, context->root->parse->resultRelation);
+ 		appendStringInfoString(context->buf, "tableoid");
+ 		return;
+ 	}
+ 
  	if (context->params_list)
  	{
  		int			pindex = 0;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 5497,5511 **** INSERT INTO ft2 (c1,c2,c3)
    SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id;
  EXPLAIN (verbose, costs off)
  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;            -- can't be pushed down
!                                                 QUERY PLAN                                                
! ----------------------------------------------------------------------------------------------------------
   Update on public.ft2
     Output: c1, c2, c3, c4, c5, c6, c7, c8
!    Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
     ->  Foreign Scan on public.ft2
!          Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
           Filter: (postgres_fdw_abs(ft2.c1) > 2000)
!          Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
  (7 rows)
  
  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
--- 5497,5511 ----
    SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id;
  EXPLAIN (verbose, costs off)
  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;            -- can't be pushed down
!                                                          QUERY PLAN                                                         
! ----------------------------------------------------------------------------------------------------------------------------
   Update on public.ft2
     Output: c1, c2, c3, c4, c5, c6, c7, c8
!    Remote SQL: UPDATE "S 1"."T 1" SET c3 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
     ->  Foreign Scan on public.ft2
!          Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid, $0
           Filter: (postgres_fdw_abs(ft2.c1) > 2000)
!          Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" FOR UPDATE
  (7 rows)
  
  UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
***************
*** 5532,5544 **** UPDATE ft2 SET c3 = 'baz'
  ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Update on public.ft2
     Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
!    Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
     ->  Nested Loop
!          Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
           Join Filter: (ft2.c2 === ft4.c1)
           ->  Foreign Scan on public.ft2
!                Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid
!                Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
           ->  Foreign Scan
                 Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
                 Relations: (public.ft4) INNER JOIN (public.ft5)
--- 5532,5544 ----
  ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Update on public.ft2
     Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
!    Remote SQL: UPDATE "S 1"."T 1" SET c3 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
     ->  Nested Loop
!          Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ($0), ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
           Join Filter: (ft2.c2 === ft4.c1)
           ->  Foreign Scan on public.ft2
!                Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, $0
!                Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
           ->  Foreign Scan
                 Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
                 Relations: (public.ft4) INNER JOIN (public.ft5)
***************
*** 5570,5593 **** DELETE FROM ft2
    USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1)
    WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1
    RETURNING ft2.c1, ft2.c2, ft2.c3;       -- can't be pushed down
!                                                                                                                                                                      QUERY PLAN                                                                                                                                                                     
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Delete on public.ft2
     Output: ft2.c1, ft2.c2, ft2.c3
!    Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c2, c3
     ->  Foreign Scan
!          Output: ft2.ctid, ft4.*, ft5.*
           Filter: (ft4.c1 === ft5.c1)
           Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
!          Remote SQL: SELECT r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r3.c1 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 2000)))) INNER JOIN "S 1"."T 4" r3 ON (TRUE)) FOR UPDATE OF r1
           ->  Nested Loop
!                Output: ft2.ctid, ft4.*, ft5.*, ft4.c1, ft5.c1
                 ->  Nested Loop
!                      Output: ft2.ctid, ft4.*, ft4.c1
                       Join Filter: (ft2.c2 = ft4.c1)
                       ->  Foreign Scan on public.ft2
!                            Output: ft2.ctid, ft2.c2
!                            Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
                       ->  Foreign Scan on public.ft4
                             Output: ft4.*, ft4.c1
                             Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
--- 5570,5593 ----
    USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1)
    WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1
    RETURNING ft2.c1, ft2.c2, ft2.c3;       -- can't be pushed down
!                                                                                                                                                                            QUERY PLAN                                                                                                                                                                            
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Delete on public.ft2
     Output: ft2.c1, ft2.c2, ft2.c3
!    Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 AND tableoid = $2 RETURNING "C 1", c2, c3
     ->  Foreign Scan
!          Output: ft2.ctid, ($0), ft4.*, ft5.*
           Filter: (ft4.c1 === ft5.c1)
           Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
!          Remote SQL: SELECT r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r3.c1, r1.tableoid FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 2000)))) INNER JOIN "S 1"."T 4" r3 ON (TRUE)) FOR UPDATE OF r1
           ->  Nested Loop
!                Output: ft2.ctid, ft4.*, ft5.*, ft4.c1, ft5.c1, ($0)
                 ->  Nested Loop
!                      Output: ft2.ctid, ($0), ft4.*, ft4.c1
                       Join Filter: (ft2.c2 = ft4.c1)
                       ->  Foreign Scan on public.ft2
!                            Output: ft2.ctid, $0, ft2.c2
!                            Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
                       ->  Foreign Scan on public.ft4
                             Output: ft4.*, ft4.c1
                             Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
***************
*** 6088,6093 **** SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
--- 6088,6174 ----
     40 |  42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0    | 0          | foo
  (10 rows)
  
+ -- Test UPDATE/DELETE in the case where the remote target is
+ -- an inheritance tree
+ CREATE TABLE p1 (a int, b int);
+ CREATE TABLE c1 (LIKE p1) INHERITS (p1);
+ NOTICE:  merging column "a" with inherited definition
+ NOTICE:  merging column "b" with inherited definition
+ CREATE TABLE c2 (LIKE p1) INHERITS (p1);
+ NOTICE:  merging column "a" with inherited definition
+ NOTICE:  merging column "b" with inherited definition
+ CREATE FOREIGN TABLE fp1 (a int, b int)
+   SERVER loopback OPTIONS (table_name 'p1');
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+  tableoid | ctid  | a | b 
+ ----------+-------+---+---
+  fp1      | (0,1) | 0 | 1
+  fp1      | (0,1) | 1 | 1
+ (2 rows)
+ 
+ -- Update statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+                                         QUERY PLAN                                         
+ -------------------------------------------------------------------------------------------
+  Update on public.fp1
+    Remote SQL: UPDATE public.p1 SET b = $3 WHERE ctid = $1 AND tableoid = $2
+    ->  Foreign Scan on public.fp1
+          Output: a, (b + 1), ctid, $0
+          Filter: (random() <= '1'::double precision)
+          Remote SQL: SELECT a, b, ctid, tableoid FROM public.p1 WHERE ((a = 0)) FOR UPDATE
+ (6 rows)
+ 
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+ -- Only one tuple should be updated
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+  tableoid | ctid  | a | b 
+ ----------+-------+---+---
+  fp1      | (0,2) | 0 | 2
+  fp1      | (0,1) | 1 | 1
+ (2 rows)
+ 
+ -- Recreate the table data
+ TRUNCATE c1;
+ TRUNCATE c2;
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+  tableoid | ctid  | a | b 
+ ----------+-------+---+---
+  fp1      | (0,1) | 0 | 1
+  fp1      | (0,1) | 1 | 1
+ (2 rows)
+ 
+ -- Delete statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+                                      QUERY PLAN                                      
+ -------------------------------------------------------------------------------------
+  Delete on public.fp1
+    Remote SQL: DELETE FROM public.p1 WHERE ctid = $1 AND tableoid = $2
+    ->  Foreign Scan on public.fp1
+          Output: ctid, $0
+          Filter: (random() <= '1'::double precision)
+          Remote SQL: SELECT ctid, tableoid FROM public.p1 WHERE ((a = 1)) FOR UPDATE
+ (6 rows)
+ 
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+ -- Only one tuple should be deleted
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+  tableoid | ctid  | a | b 
+ ----------+-------+---+---
+  fp1      | (0,1) | 0 | 1
+ (1 row)
+ 
+ -- cleanup
+ DROP FOREIGN TABLE fp1;
+ DROP TABLE p1 CASCADE;
+ NOTICE:  drop cascades to 2 other objects
+ DETAIL:  drop cascades to table c1
+ drop cascades to table c2
  -- ===================================================================
  -- test check constraints
  -- ===================================================================
***************
*** 6229,6241 **** SELECT * FROM foreign_tbl;
  
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 5;
!                                       QUERY PLAN                                       
! ---------------------------------------------------------------------------------------
   Update on public.foreign_tbl
!    Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
!          Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
  (5 rows)
  
  UPDATE rw_view SET b = b + 5; -- should fail
--- 6310,6322 ----
  
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 5;
!                                             QUERY PLAN                                            
! --------------------------------------------------------------------------------------------------
   Update on public.foreign_tbl
!    Remote SQL: UPDATE public.base_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid, $0
!          Remote SQL: SELECT a, b, ctid, tableoid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
  (5 rows)
  
  UPDATE rw_view SET b = b + 5; -- should fail
***************
*** 6243,6255 **** ERROR:  new row violates check option for view "rw_view"
  DETAIL:  Failing row contains (20, 20).
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 15;
!                                       QUERY PLAN                                       
! ---------------------------------------------------------------------------------------
   Update on public.foreign_tbl
!    Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
!          Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
  (5 rows)
  
  UPDATE rw_view SET b = b + 15; -- ok
--- 6324,6336 ----
  DETAIL:  Failing row contains (20, 20).
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 15;
!                                             QUERY PLAN                                            
! --------------------------------------------------------------------------------------------------
   Update on public.foreign_tbl
!    Remote SQL: UPDATE public.base_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid, $0
!          Remote SQL: SELECT a, b, ctid, tableoid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
  (5 rows)
  
  UPDATE rw_view SET b = b + 15; -- ok
***************
*** 6316,6329 **** SELECT * FROM foreign_tbl;
  
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 5;
!                                        QUERY PLAN                                       
! ----------------------------------------------------------------------------------------
   Update on public.parent_tbl
     Foreign Update on public.foreign_tbl
!      Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
!          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
  (6 rows)
  
  UPDATE rw_view SET b = b + 5; -- should fail
--- 6397,6410 ----
  
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 5;
!                                              QUERY PLAN                                              
! -----------------------------------------------------------------------------------------------------
   Update on public.parent_tbl
     Foreign Update on public.foreign_tbl
!      Remote SQL: UPDATE public.child_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid, $0
!          Remote SQL: SELECT a, b, ctid, tableoid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
  (6 rows)
  
  UPDATE rw_view SET b = b + 5; -- should fail
***************
*** 6331,6344 **** ERROR:  new row violates check option for view "rw_view"
  DETAIL:  Failing row contains (20, 20).
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 15;
!                                        QUERY PLAN                                       
! ----------------------------------------------------------------------------------------
   Update on public.parent_tbl
     Foreign Update on public.foreign_tbl
!      Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
!          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
  (6 rows)
  
  UPDATE rw_view SET b = b + 15; -- ok
--- 6412,6425 ----
  DETAIL:  Failing row contains (20, 20).
  EXPLAIN (VERBOSE, COSTS OFF)
  UPDATE rw_view SET b = b + 15;
!                                              QUERY PLAN                                              
! -----------------------------------------------------------------------------------------------------
   Update on public.parent_tbl
     Foreign Update on public.foreign_tbl
!      Remote SQL: UPDATE public.child_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
     ->  Foreign Scan on public.foreign_tbl
!          Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid, $0
!          Remote SQL: SELECT a, b, ctid, tableoid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
  (6 rows)
  
  UPDATE rw_view SET b = b + 15; -- ok
***************
*** 6808,6820 **** BEFORE UPDATE ON rem1
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
  EXPLAIN (verbose, costs off)
  UPDATE rem1 set f2 = '';          -- can't be pushed down
!                              QUERY PLAN                              
! ---------------------------------------------------------------------
   Update on public.rem1
!    Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1
     ->  Foreign Scan on public.rem1
!          Output: f1, ''::text, ctid, rem1.*
!          Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  EXPLAIN (verbose, costs off)
--- 6889,6901 ----
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
  EXPLAIN (verbose, costs off)
  UPDATE rem1 set f2 = '';          -- can't be pushed down
!                                    QUERY PLAN                                   
! --------------------------------------------------------------------------------
   Update on public.rem1
!    Remote SQL: UPDATE public.loc1 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2
     ->  Foreign Scan on public.rem1
!          Output: f1, ''::text, ctid, $0, rem1.*
!          Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  EXPLAIN (verbose, costs off)
***************
*** 6832,6844 **** AFTER UPDATE ON rem1
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
  EXPLAIN (verbose, costs off)
  UPDATE rem1 set f2 = '';          -- can't be pushed down
!                                   QUERY PLAN                                   
! -------------------------------------------------------------------------------
   Update on public.rem1
!    Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2
     ->  Foreign Scan on public.rem1
!          Output: f1, ''::text, ctid, rem1.*
!          Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  EXPLAIN (verbose, costs off)
--- 6913,6925 ----
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
  EXPLAIN (verbose, costs off)
  UPDATE rem1 set f2 = '';          -- can't be pushed down
!                                            QUERY PLAN                                            
! -------------------------------------------------------------------------------------------------
   Update on public.rem1
!    Remote SQL: UPDATE public.loc1 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2
     ->  Foreign Scan on public.rem1
!          Output: f1, ''::text, ctid, $0, rem1.*
!          Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  EXPLAIN (verbose, costs off)
***************
*** 6866,6878 **** UPDATE rem1 set f2 = '';          -- can be pushed down
  
  EXPLAIN (verbose, costs off)
  DELETE FROM rem1;                 -- can't be pushed down
!                              QUERY PLAN                              
! ---------------------------------------------------------------------
   Delete on public.rem1
!    Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1
     ->  Foreign Scan on public.rem1
!          Output: ctid, rem1.*
!          Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  DROP TRIGGER trig_row_before_delete ON rem1;
--- 6947,6959 ----
  
  EXPLAIN (verbose, costs off)
  DELETE FROM rem1;                 -- can't be pushed down
!                                   QUERY PLAN                                   
! -------------------------------------------------------------------------------
   Delete on public.rem1
!    Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 AND tableoid = $2
     ->  Foreign Scan on public.rem1
!          Output: ctid, $0, rem1.*
!          Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  DROP TRIGGER trig_row_before_delete ON rem1;
***************
*** 6890,6902 **** UPDATE rem1 set f2 = '';          -- can be pushed down
  
  EXPLAIN (verbose, costs off)
  DELETE FROM rem1;                 -- can't be pushed down
!                                QUERY PLAN                               
! ------------------------------------------------------------------------
   Delete on public.rem1
!    Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 RETURNING f1, f2
     ->  Foreign Scan on public.rem1
!          Output: ctid, rem1.*
!          Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  DROP TRIGGER trig_row_after_delete ON rem1;
--- 6971,6983 ----
  
  EXPLAIN (verbose, costs off)
  DELETE FROM rem1;                 -- can't be pushed down
!                                         QUERY PLAN                                        
! ------------------------------------------------------------------------------------------
   Delete on public.rem1
!    Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2
     ->  Foreign Scan on public.rem1
!          Output: ctid, $0, rem1.*
!          Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
  (5 rows)
  
  DROP TRIGGER trig_row_after_delete ON rem1;
***************
*** 7147,7158 **** select * from bar where f1 in (select f1 from foo) for share;
  -- Check UPDATE with inherited target and an inherited source table
  explain (verbose, costs off)
  update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
!                                          QUERY PLAN                                          
! ---------------------------------------------------------------------------------------------
   Update on public.bar
     Update on public.bar
     Foreign Update on public.bar2
!      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
     ->  Hash Join
           Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
           Inner Unique: true
--- 7228,7239 ----
  -- Check UPDATE with inherited target and an inherited source table
  explain (verbose, costs off)
  update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
!                                             QUERY PLAN                                             
! ---------------------------------------------------------------------------------------------------
   Update on public.bar
     Update on public.bar
     Foreign Update on public.bar2
!      Remote SQL: UPDATE public.loct2 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2
     ->  Hash Join
           Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
           Inner Unique: true
***************
*** 7171,7182 **** update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
                                   Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
                                   Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
     ->  Hash Join
!          Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid
           Inner Unique: true
           Hash Cond: (bar2.f1 = foo.f1)
           ->  Foreign Scan on public.bar2
!                Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
!                Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
           ->  Hash
                 Output: foo.ctid, foo.*, foo.tableoid, foo.f1
                 ->  HashAggregate
--- 7252,7263 ----
                                   Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
                                   Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
     ->  Hash Join
!          Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, ($0), foo.ctid, foo.*, foo.tableoid
           Inner Unique: true
           Hash Cond: (bar2.f1 = foo.f1)
           ->  Foreign Scan on public.bar2
!                Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid, $0
!                Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 FOR UPDATE
           ->  Hash
                 Output: foo.ctid, foo.*, foo.tableoid, foo.f1
                 ->  HashAggregate
***************
*** 7208,7219 **** update bar set f2 = f2 + 100
  from
    ( select f1 from foo union all select f1+3 from foo ) ss
  where bar.f1 = ss.f1;
!                                       QUERY PLAN                                      
! --------------------------------------------------------------------------------------
   Update on public.bar
     Update on public.bar
     Foreign Update on public.bar2
!      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
     ->  Hash Join
           Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
           Hash Cond: (foo.f1 = bar.f1)
--- 7289,7300 ----
  from
    ( select f1 from foo union all select f1+3 from foo ) ss
  where bar.f1 = ss.f1;
!                                            QUERY PLAN                                           
! ------------------------------------------------------------------------------------------------
   Update on public.bar
     Update on public.bar
     Foreign Update on public.bar2
!      Remote SQL: UPDATE public.loct2 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2
     ->  Hash Join
           Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
           Hash Cond: (foo.f1 = bar.f1)
***************
*** 7233,7246 **** where bar.f1 = ss.f1;
                 ->  Seq Scan on public.bar
                       Output: bar.f1, bar.f2, bar.ctid
     ->  Merge Join
!          Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
           Merge Cond: (bar2.f1 = foo.f1)
           ->  Sort
!                Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
                 Sort Key: bar2.f1
                 ->  Foreign Scan on public.bar2
!                      Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
!                      Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
           ->  Sort
                 Output: (ROW(foo.f1)), foo.f1
                 Sort Key: foo.f1
--- 7314,7327 ----
                 ->  Seq Scan on public.bar
                       Output: bar.f1, bar.f2, bar.ctid
     ->  Merge Join
!          Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, ($0), (ROW(foo.f1))
           Merge Cond: (bar2.f1 = foo.f1)
           ->  Sort
!                Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid, ($0)
                 Sort Key: bar2.f1
                 ->  Foreign Scan on public.bar2
!                      Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid, $0
!                      Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 FOR UPDATE
           ->  Sort
                 Output: (ROW(foo.f1)), foo.f1
                 Sort Key: foo.f1
***************
*** 7438,7454 **** AFTER UPDATE OR DELETE ON bar2
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
  explain (verbose, costs off)
  update bar set f2 = f2 + 100;
!                                       QUERY PLAN                                      
! --------------------------------------------------------------------------------------
   Update on public.bar
     Update on public.bar
     Foreign Update on public.bar2
!      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2, f3
     ->  Seq Scan on public.bar
           Output: bar.f1, (bar.f2 + 100), bar.ctid
     ->  Foreign Scan on public.bar2
!          Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, bar2.*
!          Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
  (9 rows)
  
  update bar set f2 = f2 + 100;
--- 7519,7535 ----
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
  explain (verbose, costs off)
  update bar set f2 = f2 + 100;
!                                                QUERY PLAN                                               
! --------------------------------------------------------------------------------------------------------
   Update on public.bar
     Update on public.bar
     Foreign Update on public.bar2
!      Remote SQL: UPDATE public.loct2 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2, f3
     ->  Seq Scan on public.bar
           Output: bar.f1, (bar.f2 + 100), bar.ctid
     ->  Foreign Scan on public.bar2
!          Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, $0, bar2.*
!          Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 FOR UPDATE
  (9 rows)
  
  update bar set f2 = f2 + 100;
***************
*** 7466,7483 **** NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
  NOTICE:  OLD: (7,277,77),NEW: (7,377,77)
  explain (verbose, costs off)
  delete from bar where f2 < 400;
!                                          QUERY PLAN                                          
! ---------------------------------------------------------------------------------------------
   Delete on public.bar
     Delete on public.bar
     Foreign Delete on public.bar2
!      Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3
     ->  Seq Scan on public.bar
           Output: bar.ctid
           Filter: (bar.f2 < 400)
     ->  Foreign Scan on public.bar2
!          Output: bar2.ctid, bar2.*
!          Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
  (10 rows)
  
  delete from bar where f2 < 400;
--- 7547,7564 ----
  NOTICE:  OLD: (7,277,77),NEW: (7,377,77)
  explain (verbose, costs off)
  delete from bar where f2 < 400;
!                                               QUERY PLAN                                               
! -------------------------------------------------------------------------------------------------------
   Delete on public.bar
     Delete on public.bar
     Foreign Delete on public.bar2
!      Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2, f3
     ->  Seq Scan on public.bar
           Output: bar.ctid
           Filter: (bar.f2 < 400)
     ->  Foreign Scan on public.bar2
!          Output: bar2.ctid, $0, bar2.*
!          Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
  (10 rows)
  
  delete from bar where f2 < 400;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 30,35 ****
--- 30,36 ----
  #include "optimizer/pathnode.h"
  #include "optimizer/paths.h"
  #include "optimizer/planmain.h"
+ #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
  #include "optimizer/var.h"
  #include "optimizer/tlist.h"
***************
*** 67,72 **** enum FdwScanPrivateIndex
--- 68,75 ----
  	FdwScanPrivateSelectSql,
  	/* Integer list of attribute numbers retrieved by the SELECT */
  	FdwScanPrivateRetrievedAttrs,
+ 	/* Param ID for remote table OID for target rel (-1 if none) */
+ 	FdwScanPrivateTableOidParamId,
  	/* Integer representing the desired fetch_size */
  	FdwScanPrivateFetchSize,
  
***************
*** 133,138 **** typedef struct PgFdwScanState
--- 136,142 ----
  	/* extracted fdw_private data */
  	char	   *query;			/* text of SELECT command */
  	List	   *retrieved_attrs;	/* list of retrieved attribute numbers */
+ 	int			tableoid_param_id;	/* Param ID for remote table OID */
  
  	/* for remote query execution */
  	PGconn	   *conn;			/* connection for the scan */
***************
*** 147,152 **** typedef struct PgFdwScanState
--- 151,157 ----
  	HeapTuple  *tuples;			/* array of currently-retrieved tuples */
  	int			num_tuples;		/* # of tuples in array */
  	int			next_tuple;		/* index of next one to return */
+ 	bool		set_tableoid_param;	/* Do we need to set the Param? */
  
  	/* batch-level state, for optimizing rewinds and avoiding useless fetch */
  	int			fetch_ct_2;		/* Min(# of fetches done, 2) */
***************
*** 179,184 **** typedef struct PgFdwModifyState
--- 184,190 ----
  
  	/* info about parameters for prepared statement */
  	AttrNumber	ctidAttno;		/* attnum of input resjunk ctid column */
+ 	AttrNumber	tableoidAttno;	/* attnum of input resjunk tableoid column */
  	int			p_nums;			/* number of parameters to transmit */
  	FmgrInfo   *p_flinfo;		/* output conversion functions for them */
  
***************
*** 393,398 **** static PgFdwModifyState *create_foreign_modify(EState *estate,
--- 399,405 ----
  static void prepare_foreign_modify(PgFdwModifyState *fmstate);
  static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
  						 ItemPointer tupleid,
+ 						 Oid tableoid,
  						 TupleTableSlot *slot);
  static void store_returning_result(PgFdwModifyState *fmstate,
  					   TupleTableSlot *slot, PGresult *res);
***************
*** 597,602 **** postgresGetForeignRelSize(PlannerInfo *root,
--- 604,632 ----
  	}
  
  	/*
+ 	 * If the table is an UPDATE/DELETE target, the table's reltarget would
+ 	 * have contained a Param representing the remote table OID of the target;
+ 	 * get the Param and save a copy of it in fpinfo for use later.
+ 	 */
+ 	if (baserel->relid == root->parse->resultRelation)
+ 	{
+ 		foreach(lc, baserel->reltarget->exprs)
+ 		{
+ 			Param	   *param = (Param *) lfirst(lc);
+ 
+ 			if (IsA(param, Param))
+ 			{
+ 				Assert(IS_FOREIGN_PARAM(root, param));
+ 				fpinfo->tableoid_param = (Param *) copyObject(param);
+ 				break;
+ 			}
+ 		}
+ 		Assert(fpinfo->tableoid_param);
+ 	}
+ 	else
+ 		fpinfo->tableoid_param = NULL;
+ 
+ 	/*
  	 * Compute the selectivity and cost of the local_conds, so we don't have
  	 * to do it over again for each path.  The best we can do for these
  	 * conditions is to estimate selectivity on the basis of local statistics.
***************
*** 1139,1144 **** postgresGetForeignPlan(PlannerInfo *root,
--- 1169,1175 ----
  	List	   *fdw_scan_tlist = NIL;
  	List	   *fdw_recheck_quals = NIL;
  	List	   *retrieved_attrs;
+ 	int			tableoid_param_id;
  	StringInfoData sql;
  	ListCell   *lc;
  
***************
*** 1278,1289 **** postgresGetForeignPlan(PlannerInfo *root,
  	/* Remember remote_exprs for possible use by postgresPlanDirectModify */
  	fpinfo->final_remote_exprs = remote_exprs;
  
  	/*
  	 * Build the fdw_private list that will be available to the executor.
  	 * Items in the list must match order in enum FdwScanPrivateIndex.
  	 */
! 	fdw_private = list_make3(makeString(sql.data),
  							 retrieved_attrs,
  							 makeInteger(fpinfo->fetch_size));
  	if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
  		fdw_private = lappend(fdw_private,
--- 1309,1327 ----
  	/* Remember remote_exprs for possible use by postgresPlanDirectModify */
  	fpinfo->final_remote_exprs = remote_exprs;
  
+ 	/* Get the Param ID for the remote table OID, if it exists */
+ 	if (fpinfo->tableoid_param)
+ 		tableoid_param_id = fpinfo->tableoid_param->paramid;
+ 	else
+ 		tableoid_param_id = -1;
+ 
  	/*
  	 * Build the fdw_private list that will be available to the executor.
  	 * Items in the list must match order in enum FdwScanPrivateIndex.
  	 */
! 	fdw_private = list_make4(makeString(sql.data),
  							 retrieved_attrs,
+ 							 makeInteger(tableoid_param_id),
  							 makeInteger(fpinfo->fetch_size));
  	if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
  		fdw_private = lappend(fdw_private,
***************
*** 1367,1372 **** postgresBeginForeignScan(ForeignScanState *node, int eflags)
--- 1405,1412 ----
  									 FdwScanPrivateSelectSql));
  	fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
  												 FdwScanPrivateRetrievedAttrs);
+ 	fsstate->tableoid_param_id = intVal(list_nth(fsplan->fdw_private,
+ 												 FdwScanPrivateTableOidParamId));
  	fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private,
  										  FdwScanPrivateFetchSize));
  
***************
*** 1381,1396 **** postgresBeginForeignScan(ForeignScanState *node, int eflags)
--- 1421,1447 ----
  	/*
  	 * Get info we'll need for converting data fetched from the foreign server
  	 * into local representation and error reporting during that process.
+ 	 * Also, determine whether we need to set the Param for the remote table
+ 	 * OID.
  	 */
  	if (fsplan->scan.scanrelid > 0)
  	{
  		fsstate->rel = node->ss.ss_currentRelation;
  		fsstate->tupdesc = RelationGetDescr(fsstate->rel);
+ 
+ 		fsstate->set_tableoid_param =
+ 			fsstate->tableoid_param_id >= 0 ? true : false;
  	}
  	else
  	{
  		fsstate->rel = NULL;
  		fsstate->tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+ 
+ 		/*
+ 		 * No need to set the Param since the value will be produced as a
+ 		 * tlist entry of fdw_scan_tlist, even if it exists.
+ 		 */
+ 		fsstate->set_tableoid_param = false;
  	}
  
  	fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
***************
*** 1419,1424 **** postgresIterateForeignScan(ForeignScanState *node)
--- 1470,1476 ----
  {
  	PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
  	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ 	HeapTuple	tuple;
  
  	/*
  	 * If this is the first call after Begin or ReScan, we need to create the
***************
*** 1441,1451 **** postgresIterateForeignScan(ForeignScanState *node)
  	}
  
  	/*
  	 * Return the next tuple.
  	 */
! 	ExecStoreHeapTuple(fsstate->tuples[fsstate->next_tuple++],
! 					   slot,
! 					   false);
  
  	return slot;
  }
--- 1493,1520 ----
  	}
  
  	/*
+ 	 * Get the next tuple.
+ 	 */
+ 	tuple = fsstate->tuples[fsstate->next_tuple++];
+ 
+ 	/*
+ 	 * Set the Param for the remote table OID, if necessary.
+ 	 */
+ 	if (fsstate->set_tableoid_param)
+ 	{
+ 		ExprContext *econtext = node->ss.ps.ps_ExprContext;
+ 		ParamExecData *prm = &(econtext->ecxt_param_exec_vals[fsstate->tableoid_param_id]);
+ 
+ 		Assert(OidIsValid(tuple->t_tableOid));
+ 		prm->execPlan = NULL;
+ 		prm->value = ObjectIdGetDatum(tuple->t_tableOid);
+ 		prm->isnull = false;
+ 	}
+ 
+ 	/*
  	 * Return the next tuple.
  	 */
! 	ExecStoreHeapTuple(tuple, slot, false);
  
  	return slot;
  }
***************
*** 1540,1553 **** postgresAddForeignUpdateTargets(Query *parsetree,
  								Relation target_relation)
  {
  	Var		   *var;
  	const char *attrname;
  	TargetEntry *tle;
  
  	/*
! 	 * In postgres_fdw, what we need is the ctid, same as for a regular table.
  	 */
  
! 	/* Make a Var representing the desired value */
  	var = makeVar(parsetree->resultRelation,
  				  SelfItemPointerAttributeNumber,
  				  TIDOID,
--- 1609,1625 ----
  								Relation target_relation)
  {
  	Var		   *var;
+ 	Param	   *param;
  	const char *attrname;
  	TargetEntry *tle;
  
  	/*
! 	 * In postgres_fdw, what we need is the ctid, same as for a regular table,
! 	 * and the remote table OID, which is needed since the remote table might
! 	 * be an inheritance tree.
  	 */
  
! 	/* Make a Var representing the ctid value */
  	var = makeVar(parsetree->resultRelation,
  				  SelfItemPointerAttributeNumber,
  				  TIDOID,
***************
*** 1565,1570 **** postgresAddForeignUpdateTargets(Query *parsetree,
--- 1637,1656 ----
  
  	/* ... and add it to the query's targetlist */
  	parsetree->targetList = lappend(parsetree->targetList, tle);
+ 
+ 	/* Make a Param representing the tableoid value */
+ 	param = generate_foreign_param(OIDOID, -1, InvalidOid);
+ 
+ 	/* Wrap it in a resjunk TLE with the right name ... */
+ 	attrname = "remotetableoid";
+ 
+ 	tle = makeTargetEntry((Expr *) param,
+ 						  list_length(parsetree->targetList) + 1,
+ 						  pstrdup(attrname),
+ 						  true);
+ 
+ 	/* ... and add it to the query's targetlist */
+ 	parsetree->targetList = lappend(parsetree->targetList, tle);
  }
  
  /*
***************
*** 1768,1774 **** postgresExecForeignInsert(EState *estate,
  		prepare_foreign_modify(fmstate);
  
  	/* Convert parameters needed by prepared statement to text form */
! 	p_values = convert_prep_stmt_params(fmstate, NULL, slot);
  
  	/*
  	 * Execute the prepared statement.
--- 1854,1860 ----
  		prepare_foreign_modify(fmstate);
  
  	/* Convert parameters needed by prepared statement to text form */
! 	p_values = convert_prep_stmt_params(fmstate, NULL, InvalidOid, slot);
  
  	/*
  	 * Execute the prepared statement.
***************
*** 1824,1829 **** postgresExecForeignUpdate(EState *estate,
--- 1910,1916 ----
  {
  	PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
  	Datum		datum;
+ 	Datum		datum2;
  	bool		isNull;
  	const char **p_values;
  	PGresult   *res;
***************
*** 1841,1849 **** postgresExecForeignUpdate(EState *estate,
--- 1928,1945 ----
  	if (isNull)
  		elog(ERROR, "ctid is NULL");
  
+ 	/* Get the tableoid that was passed up as a resjunk column */
+ 	datum2 = ExecGetJunkAttribute(planSlot,
+ 								  fmstate->tableoidAttno,
+ 								  &isNull);
+ 	/* shouldn't ever get a null result... */
+ 	if (isNull)
+ 		elog(ERROR, "tableoid is NULL");
+ 
  	/* Convert parameters needed by prepared statement to text form */
  	p_values = convert_prep_stmt_params(fmstate,
  										(ItemPointer) DatumGetPointer(datum),
+ 										DatumGetObjectId(datum2),
  										slot);
  
  	/*
***************
*** 1900,1905 **** postgresExecForeignDelete(EState *estate,
--- 1996,2002 ----
  {
  	PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
  	Datum		datum;
+ 	Datum		datum2;
  	bool		isNull;
  	const char **p_values;
  	PGresult   *res;
***************
*** 1917,1925 **** postgresExecForeignDelete(EState *estate,
--- 2014,2031 ----
  	if (isNull)
  		elog(ERROR, "ctid is NULL");
  
+ 	/* Get the tableoid that was passed up as a resjunk column */
+ 	datum2 = ExecGetJunkAttribute(planSlot,
+ 								  fmstate->tableoidAttno,
+ 								  &isNull);
+ 	/* shouldn't ever get a null result... */
+ 	if (isNull)
+ 		elog(ERROR, "tableoid is NULL");
+ 
  	/* Convert parameters needed by prepared statement to text form */
  	p_values = convert_prep_stmt_params(fmstate,
  										(ItemPointer) DatumGetPointer(datum),
+ 										DatumGetObjectId(datum2),
  										NULL);
  
  	/*
***************
*** 3340,3346 **** create_foreign_modify(EState *estate,
  		fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
  
  	/* Prepare for output conversion of parameters used in prepared stmt. */
! 	n_params = list_length(fmstate->target_attrs) + 1;
  	fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
  	fmstate->p_nums = 0;
  
--- 3446,3452 ----
  		fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
  
  	/* Prepare for output conversion of parameters used in prepared stmt. */
! 	n_params = list_length(fmstate->target_attrs) + 2;
  	fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
  	fmstate->p_nums = 0;
  
***************
*** 3358,3363 **** create_foreign_modify(EState *estate,
--- 3464,3480 ----
  		getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
  		fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
  		fmstate->p_nums++;
+ 
+ 		/* Find the tableoid resjunk column in the subplan's result */
+ 		fmstate->tableoidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ 															  "remotetableoid");
+ 		if (!AttributeNumberIsValid(fmstate->tableoidAttno))
+ 			elog(ERROR, "could not find junk tableoid column");
+ 
+ 		/* Second transmittable parameter will be tableoid */
+ 		getTypeOutputInfo(OIDOID, &typefnoid, &isvarlena);
+ 		fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ 		fmstate->p_nums++;
  	}
  
  	if (operation == CMD_INSERT || operation == CMD_UPDATE)
***************
*** 3431,3436 **** prepare_foreign_modify(PgFdwModifyState *fmstate)
--- 3548,3554 ----
   *		Create array of text strings representing parameter values
   *
   * tupleid is ctid to send, or NULL if none
+  * tableoid is tableoid to send, or InvalidOid if none
   * slot is slot to get remaining parameters from, or NULL if none
   *
   * Data is constructed in temp_cxt; caller should reset that after use.
***************
*** 3438,3443 **** prepare_foreign_modify(PgFdwModifyState *fmstate)
--- 3556,3562 ----
  static const char **
  convert_prep_stmt_params(PgFdwModifyState *fmstate,
  						 ItemPointer tupleid,
+ 						 Oid tableoid,
  						 TupleTableSlot *slot)
  {
  	const char **p_values;
***************
*** 3457,3462 **** convert_prep_stmt_params(PgFdwModifyState *fmstate,
--- 3576,3592 ----
  		pindex++;
  	}
  
+ 	/* 2nd parameter should be tableoid, if it's in use */
+ 	if (OidIsValid(tableoid))
+ 	{
+ 		Assert(tupleid != NULL);
+ 
+ 		/* don't need set_transmission_modes for OID output */
+ 		p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
+ 											  ObjectIdGetDatum(tableoid));
+ 		pindex++;
+ 	}
+ 
  	/* get following parameters from slot */
  	if (slot != NULL && fmstate->target_attrs != NIL)
  	{
***************
*** 3846,3851 **** init_returning_filter(PgFdwDirectModifyState *dmstate,
--- 3976,3992 ----
  		TargetEntry *tle = (TargetEntry *) lfirst(lc);
  		Var		   *var = (Var *) tle->expr;
  
+ 		/*
+ 		 * No need to set the Param for the remote table OID; ignore it.
+ 		 */
+ 		if (IsA(var, Param))
+ 		{
+ 			/* We would not retrieve the remote table OID anymore. */
+ 			Assert(!list_member_int(dmstate->retrieved_attrs, i));
+ 			i++;
+ 			continue;
+ 		}
+ 
  		Assert(IsA(var, Var));
  
  		/*
***************
*** 4883,4888 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 5024,5062 ----
  	/* Mark that this join can be pushed down safely */
  	fpinfo->pushdown_safe = true;
  
+ 	/*
+ 	 * If the join relation contains an UPDATE/DELETE target, either of the
+ 	 * input relations would have saved the Param representing the remote
+ 	 * table OID of the target; get the Param and remember it in fpinfo for
+ 	 * use later.
+ 	 */
+ 	if ((root->parse->commandType == CMD_UPDATE ||
+ 		 root->parse->commandType == CMD_DELETE) &&
+ 		bms_is_member(root->parse->resultRelation, joinrel->relids))
+ 	{
+ 		if (bms_is_member(root->parse->resultRelation,
+ 						  outerrel->relids))
+ 		{
+ 			Assert(fpinfo_o->tableoid_param);
+ 			fpinfo->tableoid_param = fpinfo_o->tableoid_param;
+ 		}
+ 		else
+ 		{
+ 			Assert(bms_is_member(root->parse->resultRelation,
+ 								 innerrel->relids));
+ 			Assert(fpinfo_i->tableoid_param);
+ 			fpinfo->tableoid_param = fpinfo_i->tableoid_param;
+ 		}
+ 
+ 		/*
+ 		 * Core code should have contained the Param in the join relation's
+ 		 * reltarget.
+ 		 */
+ 		Assert(list_member(joinrel->reltarget->exprs, fpinfo->tableoid_param));
+ 	}
+ 	else
+ 		fpinfo->tableoid_param = NULL;
+ 
  	/* Get user mapping */
  	if (fpinfo->use_remote_estimate)
  	{
***************
*** 5556,5561 **** make_tuple_from_result_row(PGresult *res,
--- 5730,5736 ----
  	bool	   *nulls;
  	ItemPointer ctid = NULL;
  	Oid			oid = InvalidOid;
+ 	Oid			tableoid = InvalidOid;
  	ConversionLocation errpos;
  	ErrorContextCallback errcallback;
  	MemoryContext oldcontext;
***************
*** 5649,5654 **** make_tuple_from_result_row(PGresult *res,
--- 5824,5840 ----
  				oid = DatumGetObjectId(datum);
  			}
  		}
+ 		else if (i == TableOidAttributeNumber)
+ 		{
+ 			/* tableoid */
+ 			if (valstr != NULL)
+ 			{
+ 				Datum		datum;
+ 
+ 				datum = DirectFunctionCall1(oidin, CStringGetDatum(valstr));
+ 				tableoid = DatumGetObjectId(datum);
+ 			}
+ 		}
  		errpos.cur_attno = 0;
  
  		j++;
***************
*** 5698,5703 **** make_tuple_from_result_row(PGresult *res,
--- 5884,5899 ----
  	if (OidIsValid(oid))
  		HeapTupleSetOid(tuple, oid);
  
+ 	/*
+ 	 * If we have a table OID to return, install it.  (Note that this is not
+ 	 * really right because the installed value is the value on the remote
+ 	 * side, not the local side, but we do this for use by
+ 	 * postgresIterateForeignScan().  The correct value will be re-installed
+ 	 * in ForeignNext() if necessary.)
+ 	 */
+ 	if (OidIsValid(tableoid))
+ 		tuple->t_tableOid = tableoid;
+ 
  	/* Clean up */
  	MemoryContextReset(temp_context);
  
***************
*** 5714,5719 **** conversion_error_callback(void *arg)
--- 5910,5916 ----
  	const char *attname = NULL;
  	const char *relname = NULL;
  	bool		is_wholerow = false;
+ 	bool		is_tableoid = false;
  	ConversionLocation *errpos = (ConversionLocation *) arg;
  
  	if (errpos->rel)
***************
*** 5728,5733 **** conversion_error_callback(void *arg)
--- 5925,5932 ----
  			attname = "ctid";
  		else if (errpos->cur_attno == ObjectIdAttributeNumber)
  			attname = "oid";
+ 		else if (errpos->cur_attno == TableOidAttributeNumber)
+ 			is_tableoid = true;
  
  		relname = RelationGetRelationName(errpos->rel);
  	}
***************
*** 5744,5751 **** conversion_error_callback(void *arg)
  
  		/*
  		 * Target list can have Vars and expressions.  For Vars, we can get
! 		 * its relation, however for expressions we can't.  Thus for
! 		 * expressions, just show generic context message.
  		 */
  		if (IsA(tle->expr, Var))
  		{
--- 5943,5952 ----
  
  		/*
  		 * Target list can have Vars and expressions.  For Vars, we can get
! 		 * its relation, however for expressions we can't, except for the
! 		 * Param representing the remote table OID, in which case we can.
! 		 * Thus for expressions other than the Param, just show generic
! 		 * context message.
  		 */
  		if (IsA(tle->expr, Var))
  		{
***************
*** 5761,5766 **** conversion_error_callback(void *arg)
--- 5962,5981 ----
  
  			relname = get_rel_name(rte->relid);
  		}
+ 		else if (IsA(tle->expr, Param))
+ 		{
+ 			RangeTblEntry *rte;
+ 			ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+ 
+ 			Assert(((Param *) tle->expr)->paramid == ((PgFdwScanState *) fsstate->fdw_state)->tableoid_param_id);
+ 			Assert(resultRelInfo);
+ 
+ 			is_tableoid = true;
+ 
+ 			rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex, estate);
+ 
+ 			relname = get_rel_name(rte->relid);
+ 		}
  		else
  			errcontext("processing expression at position %d in select list",
  					   errpos->cur_attno);
***************
*** 5770,5775 **** conversion_error_callback(void *arg)
--- 5985,5992 ----
  	{
  		if (is_wholerow)
  			errcontext("whole-row reference to foreign table \"%s\"", relname);
+ 		else if (is_tableoid)
+ 			errcontext("remote tableoid of foreign table \"%s\"", relname);
  		else if (attname)
  			errcontext("column \"%s\" of foreign table \"%s\"", attname, relname);
  	}
*** a/contrib/postgres_fdw/postgres_fdw.h
--- b/contrib/postgres_fdw/postgres_fdw.h
***************
*** 49,54 **** typedef struct PgFdwRelationInfo
--- 49,57 ----
  	/* Bitmap of attr numbers we need to fetch from the remote server. */
  	Bitmapset  *attrs_used;
  
+ 	/* PARAM_EXEC Param representing the remote table OID of a target rel */
+ 	Param	   *tableoid_param;
+ 
  	/* Cost and selectivity of local_conds. */
  	QualCost	local_conds_cost;
  	Selectivity local_conds_sel;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1228,1233 **** SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
--- 1228,1266 ----
  EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
  SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
  
+ -- Test UPDATE/DELETE in the case where the remote target is
+ -- an inheritance tree
+ CREATE TABLE p1 (a int, b int);
+ CREATE TABLE c1 (LIKE p1) INHERITS (p1);
+ CREATE TABLE c2 (LIKE p1) INHERITS (p1);
+ CREATE FOREIGN TABLE fp1 (a int, b int)
+   SERVER loopback OPTIONS (table_name 'p1');
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ -- Update statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+ -- Only one tuple should be updated
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ -- Recreate the table data
+ TRUNCATE c1;
+ TRUNCATE c2;
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ -- Delete statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+ -- Only one tuple should be deleted
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ 
+ -- cleanup
+ DROP FOREIGN TABLE fp1;
+ DROP TABLE p1 CASCADE;
+ 
  -- ===================================================================
  -- test check constraints
  -- ===================================================================
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 440,445 **** AddForeignUpdateTargets(Query *parsetree,
--- 440,454 ----
      </para>
  
      <para>
+      This function may also add to the targetlist <structname>Param</structname>
+      nodes representing extra target information, in which case
+      <function>IterateForeignScan</function> must set the values of these parameters
+      for each row fetched from the foreign source; it's recommended to use
+      <function>generate_foreign_param</function> to build the
+      <structname>Param</structname> nodes.
+     </para>
+ 
+     <para>
       Although this function is called during planning, the
       information provided is a bit different from that available to other
       planning routines.
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2272,2277 **** _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
--- 2272,2278 ----
  	WRITE_BOOL_FIELD(parallelModeOK);
  	WRITE_BOOL_FIELD(parallelModeNeeded);
  	WRITE_CHAR_FIELD(maxParallelHazard);
+ 	WRITE_BITMAPSET_FIELD(foreignParamIDs);
  }
  
  static void
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 892,897 **** use_physical_tlist(PlannerInfo *root, Path *path, int flags)
--- 892,917 ----
  		}
  	}
  
+ 	/*
+ 	 * Also, can't do it to a ForeignPath if the path is requested to emit
+ 	 * Params generated by the FDW.
+ 	 */
+ 	if (IsA(path, ForeignPath) &&
+ 		path->parent->relid == root->parse->resultRelation &&
+ 		!bms_is_empty(root->glob->foreignParamIDs))
+ 	{
+ 		foreach(lc, path->pathtarget->exprs)
+ 		{
+ 			Param	   *param = (Param *) lfirst(lc);
+ 
+ 			if (param && IsA(param, Param))
+ 			{
+ 				Assert(IS_FOREIGN_PARAM(root, param));
+ 				return false;
+ 			}
+ 		}
+ 	}
+ 
  	return true;
  }
  
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 29,34 ****
--- 29,35 ----
  #include "optimizer/restrictinfo.h"
  #include "optimizer/var.h"
  #include "parser/analyze.h"
+ #include "parser/parsetree.h"
  #include "rewrite/rewriteManip.h"
  #include "utils/lsyscache.h"
  
***************
*** 46,51 **** typedef struct PostponedQual
--- 47,55 ----
  } PostponedQual;
  
  
+ static void add_params_to_result_rel(PlannerInfo *root,
+ 						 int result_relation,
+ 						 List *final_tlist);
  static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
  						   Index rtindex);
  static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
***************
*** 146,151 **** add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
--- 150,161 ----
   *
   * We mark such vars as needed by "relation 0" to ensure that they will
   * propagate up through all join plan steps.
+  *
+  * If this is an UPDATE/DELETE on a foreign table, the FDW might have added
+  * Params to the final tlist that are needed for identifying the rows to be
+  * updated or deleted.  Add targetlist entries for each such Param to the
+  * result relation.  Note that it's ensured by build_joinrel_tlist() that
+  * such Params will also propagate up through all join plan steps.
   */
  void
  build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
***************
*** 178,183 **** build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
--- 188,206 ----
  			list_free(having_vars);
  		}
  	}
+ 
+ 	/*
+ 	 * If this is an UPDATE/DELETE on a foreign table, add targetlist entries
+ 	 * for Params the FDW generated (if any) to the result relation.
+ 	 */
+ 	if (root->parse->commandType == CMD_UPDATE ||
+ 		root->parse->commandType == CMD_DELETE)
+ 	{
+ 		int			result_relation = root->parse->resultRelation;
+ 
+ 		if (planner_rt_fetch(result_relation, root)->relkind == RELKIND_FOREIGN_TABLE)
+ 			add_params_to_result_rel(root, result_relation, final_tlist);
+ 	}
  }
  
  /*
***************
*** 241,246 **** add_vars_to_targetlist(PlannerInfo *root, List *vars,
--- 264,303 ----
  	}
  }
  
+ /*
+  * add_params_to_result_rel
+  *	  If the query's final tlist contains Params the FDW generated, add
+  *	  targetlist entries for each such Param to the result relation.
+  */
+ static void
+ add_params_to_result_rel(PlannerInfo *root, int result_relation,
+ 						 List *final_tlist)
+ {
+ 	RelOptInfo *target_rel = find_base_rel(root, result_relation);
+ 	ListCell   *lc;
+ 
+ 	/*
+ 	 * If no parameters have been generated by any FDWs, we certainly don't
+ 	 * need to do anything here.
+ 	 */
+ 	if (bms_is_empty(root->glob->foreignParamIDs))
+ 		return;
+ 
+ 	foreach(lc, final_tlist)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 		Param	   *param = (Param *) tle->expr;
+ 
+ 		if (tle->resjunk && IsA(param, Param) &&
+ 			IS_FOREIGN_PARAM(root, param))
+ 		{
+ 			/* XXX is copyObject necessary here? */
+ 			target_rel->reltarget->exprs = lappend(target_rel->reltarget->exprs,
+ 												   copyObject(param));
+ 		}
+ 	}
+ }
+ 
  
  /*****************************************************************************
   *
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 313,318 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 313,319 ----
  	glob->lastPlanNodeId = 0;
  	glob->transientPlan = false;
  	glob->dependsOnRole = false;
+ 	glob->foreignParamIDs = NULL;
  
  	/*
  	 * Assess whether it's feasible to use parallel mode for this query. We
***************
*** 481,492 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
  	}
  
  	/*
! 	 * If any Params were generated, run through the plan tree and compute
! 	 * each plan node's extParam/allParam sets.  Ideally we'd merge this into
! 	 * set_plan_references' tree traversal, but for now it has to be separate
! 	 * because we need to visit subplans before not after main plan.
  	 */
! 	if (glob->paramExecTypes != NIL)
  	{
  		Assert(list_length(glob->subplans) == list_length(glob->subroots));
  		forboth(lp, glob->subplans, lr, glob->subroots)
--- 482,497 ----
  	}
  
  	/*
! 	 * If any Params were generated by the planner not by FDWs, run through
! 	 * the plan tree and compute each plan node's extParam/allParam sets.
! 	 * (Params added by FDWs are irrelevant for parameter change signaling.)
! 	 * Ideally we'd merge this into set_plan_references' tree traversal, but
! 	 * for now it has to be separate because we need to visit subplans before
! 	 * not after main plan.
  	 */
! 	if (glob->paramExecTypes != NIL &&
! 		(bms_is_empty(glob->foreignParamIDs) ||
! 		 bms_num_members(glob->foreignParamIDs) < list_length(glob->paramExecTypes)))
  	{
  		Assert(list_length(glob->subplans) == list_length(glob->subroots));
  		forboth(lp, glob->subplans, lr, glob->subroots)
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 168,184 **** static bool extract_query_dependencies_walker(Node *node,
   * 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
   * now that we have finished planning all MULTIEXPR subplans.
   *
!  * 6. We compute regproc OIDs for operators (ie, we look up the function
   * that implements each op).
   *
!  * 7. We create lists of specific objects that the plan depends on.
   * This will be used by plancache.c to drive invalidation of cached plans.
   * Relation dependencies are represented by OIDs, and everything else by
   * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
   * Currently, relations and user-defined functions are the only types of
   * objects that are explicitly tracked this way.
   *
!  * 8. We assign every plan node in the tree a unique ID.
   *
   * We also perform one final optimization step, which is to delete
   * SubqueryScan plan nodes that aren't doing anything useful (ie, have
--- 168,187 ----
   * 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
   * now that we have finished planning all MULTIEXPR subplans.
   *
!  * 6. PARAM_EXEC Params generated by FDWs in upper plan nodes are converted
!  * into simple Vars referencing the outputs of their subplans.
!  *
!  * 7. We compute regproc OIDs for operators (ie, we look up the function
   * that implements each op).
   *
!  * 8. We create lists of specific objects that the plan depends on.
   * This will be used by plancache.c to drive invalidation of cached plans.
   * Relation dependencies are represented by OIDs, and everything else by
   * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
   * Currently, relations and user-defined functions are the only types of
   * objects that are explicitly tracked this way.
   *
!  * 9. We assign every plan node in the tree a unique ID.
   *
   * We also perform one final optimization step, which is to delete
   * SubqueryScan plan nodes that aren't doing anything useful (ie, have
***************
*** 2343,2349 **** fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
--- 2346,2389 ----
  		return fix_join_expr_mutator((Node *) phv->phexpr, context);
  	}
  	if (IsA(node, Param))
+ 	{
+ 		Param	   *param = (Param *) node;
+ 
+ 		/*
+ 		 * If the Param is a PARAM_EXEC Param generated by an FDW, it should
+ 		 * have bubbled up from a lower plan node; convert it into a simple
+ 		 * Var referencing the output of the subplan.
+ 		 *
+ 		 * Note: set_join_references() would have kept has_non_vars=true for
+ 		 * the subplan emitting the Param since it effectively belong to the
+ 		 * result relation and that relation can never be the nullable side of
+ 		 * an outer join.
+ 		 */
+ 		if (IS_FOREIGN_PARAM(context->root, param))
+ 		{
+ 			if (context->outer_itlist && context->outer_itlist->has_non_vars)
+ 			{
+ 				newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ 														  context->outer_itlist,
+ 														  OUTER_VAR);
+ 				if (newvar)
+ 					return (Node *) newvar;
+ 			}
+ 			if (context->inner_itlist && context->inner_itlist->has_non_vars)
+ 			{
+ 				newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ 														  context->inner_itlist,
+ 														  INNER_VAR);
+ 				if (newvar)
+ 					return (Node *) newvar;
+ 			}
+ 			/* No referent found for foreign Param */
+ 			elog(ERROR, "foreign parameter not found in subplan target lists");
+ 		}
+ 
+ 		/* If not, do fix_param_node() */
  		return fix_param_node(context->root, (Param *) node);
+ 	}
  	/* Try matching more complex expressions too, if tlists have any */
  	if (context->outer_itlist && context->outer_itlist->has_non_vars)
  	{
***************
*** 2449,2455 **** fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
--- 2489,2518 ----
  		return fix_upper_expr_mutator((Node *) phv->phexpr, context);
  	}
  	if (IsA(node, Param))
+ 	{
+ 		Param	   *param = (Param *) node;
+ 
+ 		/*
+ 		 * If the Param is a PARAM_EXEC Param generated by an FDW, it should
+ 		 * have bubbled up from a lower plan node; convert it into a simple
+ 		 * Var referencing the output of the subplan.
+ 		 */
+ 		if (IS_FOREIGN_PARAM(context->root, param))
+ 		{
+ 			if (context->subplan_itlist->has_non_vars)
+ 			{
+ 				newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ 														  context->subplan_itlist,
+ 														  context->newvarno);
+ 				if (newvar)
+ 					return (Node *) newvar;
+ 			}
+ 			/* No referent found for foreign Param */
+ 			elog(ERROR, "foreign parameter not found in subplan target list");
+ 		}
+ 		/* If not, do fix_param_node() */
  		return fix_param_node(context->root, (Param *) node);
+ 	}
  	if (IsA(node, Aggref))
  	{
  		Aggref	   *aggref = (Aggref *) node;
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 2536,2542 **** finalize_plan(PlannerInfo *root, Plan *plan,
  				finalize_primnode((Node *) fscan->fdw_recheck_quals,
  								  &context);
  
! 				/* We assume fdw_scan_tlist cannot contain Params */
  				context.paramids = bms_add_members(context.paramids,
  												   scan_params);
  			}
--- 2536,2546 ----
  				finalize_primnode((Node *) fscan->fdw_recheck_quals,
  								  &context);
  
! 				/*
! 				 * We assume fdw_scan_tlist cannot contain Params other than
! 				 * ones generated by the FDW, which are never used for
! 				 * changed-param signaling.
! 				 */
  				context.paramids = bms_add_members(context.paramids,
  												   scan_params);
  			}
***************
*** 2897,2903 **** finalize_primnode(Node *node, finalize_primnode_context *context)
  		{
  			int			paramid = ((Param *) node)->paramid;
  
! 			context->paramids = bms_add_member(context->paramids, paramid);
  		}
  		return false;			/* no more to do here */
  	}
--- 2901,2912 ----
  		{
  			int			paramid = ((Param *) node)->paramid;
  
! 			/*
! 			 * Params added by FDWs are irrelevant for parameter change
! 			 * signaling.
! 			 */
! 			if (!bms_is_member(paramid, context->root->glob->foreignParamIDs))
! 				context->paramids = bms_add_member(context->paramids, paramid);
  		}
  		return false;			/* no more to do here */
  	}
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 55,60 ****
--- 55,61 ----
  
  static List *expand_targetlist(List *tlist, int command_type,
  				  Index result_relation, Relation rel);
+ static void fix_foreign_params(PlannerInfo *root, List *tlist);
  
  
  /*
***************
*** 106,113 **** preprocess_targetlist(PlannerInfo *root)
--- 107,120 ----
  	 * keep it that way to avoid changing APIs used by FDWs.
  	 */
  	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+ 	{
  		rewriteTargetListUD(parse, target_rte, target_relation);
  
+ 		/* The FDW might have added Params; fix such Params, if any */
+ 		if (target_rte->relkind == RELKIND_FOREIGN_TABLE)
+ 			fix_foreign_params(root, parse->targetList);
+ 	}
+ 
  	/*
  	 * for heap_form_tuple to work, the targetlist must match the exact order
  	 * of the attributes. We also need to fill in any missing attributes. -ay
***************
*** 416,421 **** expand_targetlist(List *tlist, int command_type,
--- 423,479 ----
  
  
  /*
+  * Generate a new Param node needed for an UPDATE/DELETE on a foreign table
+  *
+  * This is used by the FDW to build PARAM_EXEC Params representing extra
+  * information to ensure that it can identify the exact row to update or
+  * delete.
+  */
+ Param *
+ generate_foreign_param(Oid paramtype, int32 paramtypmod, Oid paramcollation)
+ {
+ 	Param	   *retval;
+ 
+ 	retval = makeNode(Param);
+ 	retval->paramkind = PARAM_EXEC;
+ 	/* paramid will be filled in by fix_foreign_params */
+ 	retval->paramid = -1;
+ 	retval->paramtype = paramtype;
+ 	retval->paramtypmod = paramtypmod;
+ 	retval->paramcollid = paramcollation;
+ 	retval->location = -1;
+ 
+ 	return retval;
+ }
+ 
+ /*
+  * Fix the paramids of PARAM_EXEC Params the FDW added to the tlist, if any.
+  */
+ static void
+ fix_foreign_params(PlannerInfo *root, List *tlist)
+ {
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, tlist)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 		Param	   *param = (Param *) tle->expr;
+ 
+ 		if (tle->resjunk && IsA(param, Param) &&
+ 			param->paramkind == PARAM_EXEC &&
+ 			param->paramid == -1)
+ 		{
+ 			param->paramid = list_length(root->glob->paramExecTypes);
+ 			root->glob->paramExecTypes =
+ 				lappend_oid(root->glob->paramExecTypes, param->paramtype);
+ 			root->glob->foreignParamIDs =
+ 				bms_add_member(root->glob->foreignParamIDs, param->paramid);
+ 		}
+ 	}
+ }
+ 
+ 
+ /*
   * Locate PlanRowMark for given RT index, or return NULL if none
   *
   * This probably ought to be elsewhere, but there's no very good place
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 28,33 ****
--- 28,34 ----
  #include "optimizer/tlist.h"
  #include "partitioning/partbounds.h"
  #include "utils/hsearch.h"
+ #include "utils/lsyscache.h"
  
  
  typedef struct JoinHashEntry
***************
*** 913,918 **** build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
--- 914,936 ----
  		RelOptInfo *baserel;
  		int			ndx;
  
+ 		/* Params are needed for final output, so add them to the output. */
+ 		if (IsA(var, Param))
+ 		{
+ 			Param	   *param = (Param *) var;
+ 
+ 			Assert(IS_FOREIGN_PARAM(root, param));
+ 			joinrel->reltarget->exprs =
+ 				lappend(joinrel->reltarget->exprs, param);
+ 			/*
+ 			 * Estimate using the type info  (Note: keep this in sync with
+ 			 * set_rel_width())
+ 			 */
+ 			joinrel->reltarget->width +=
+ 				get_typavgwidth(param->paramtype, param->paramtypmod);
+ 			continue;
+ 		}
+ 
  		/*
  		 * Ignore PlaceHolderVars in the input tlists; we'll make our own
  		 * decisions about whether to copy them.
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 145,156 **** typedef struct PlannerGlobal
--- 145,162 ----
  	bool		parallelModeNeeded; /* parallel mode actually required? */
  
  	char		maxParallelHazard;	/* worst PROPARALLEL hazard level */
+ 
+ 	Bitmapset  *foreignParamIDs;	/* PARAM_EXEC Params generated by FDWs */
  } PlannerGlobal;
  
  /* macro for fetching the Plan associated with a SubPlan node */
  #define planner_subplan_get_plan(root, subplan) \
  	((Plan *) list_nth((root)->glob->subplans, (subplan)->plan_id - 1))
  
+ /* macro for checking if a Param is a PARAM_EXEC Param generated by an FDW */
+ #define IS_FOREIGN_PARAM(root, param) \
+ 	((param)->paramkind == PARAM_EXEC && \
+ 	 bms_is_member((param)->paramid, (root)->glob->foreignParamIDs))
  
  /*----------
   * PlannerInfo
*** a/src/include/optimizer/prep.h
--- b/src/include/optimizer/prep.h
***************
*** 40,45 **** extern Expr *canonicalize_qual(Expr *qual, bool is_check);
--- 40,48 ----
   */
  extern List *preprocess_targetlist(PlannerInfo *root);
  
+ extern Param *generate_foreign_param(Oid paramtype, int32 paramtypmod,
+ 					   Oid paramcollation);
+ 
  extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
  
  /*
