*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 140,152 **** static void deparseTargetList(StringInfo buf,
  				  Bitmapset *attrs_used,
  				  bool qualify_col,
  				  List **retrieved_attrs);
! static void deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
  						  deparse_expr_cxt *context);
  static void deparseReturningList(StringInfo buf, PlannerInfo *root,
  					 Index rtindex, Relation rel,
  					 bool trig_after_row,
  					 List *returningList,
  					 List **retrieved_attrs);
  static void deparseColumnRef(StringInfo buf, int varno, int varattno,
  				 PlannerInfo *root, bool qualify_col);
  static void deparseRelation(StringInfo buf, Relation rel);
--- 140,159 ----
  				  Bitmapset *attrs_used,
  				  bool qualify_col,
  				  List **retrieved_attrs);
! static void deparseExplicitTargetList(List *tlist,
! 						  bool is_returning,
! 						  List **retrieved_attrs,
  						  deparse_expr_cxt *context);
  static void deparseReturningList(StringInfo buf, PlannerInfo *root,
  					 Index rtindex, Relation rel,
  					 bool trig_after_row,
  					 List *returningList,
  					 List **retrieved_attrs);
+ static void deparseExplicitReturningList(Index rtindex, Relation rel,
+ 							 List **fdw_scan_tlist,
+ 							 List *returningList,
+ 							 List **retrieved_attrs,
+ 							 deparse_expr_cxt *context);
  static void deparseColumnRef(StringInfo buf, int varno, int varattno,
  				 PlannerInfo *root, bool qualify_col);
  static void deparseRelation(StringInfo buf, Relation rel);
***************
*** 178,189 **** static void deparseLockingClause(deparse_expr_cxt *context);
  static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context);
  static void appendConditions(List *exprs, deparse_expr_cxt *context);
  static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
! 					  RelOptInfo *foreignrel, bool add_rel_alias,
! 					  AliasedExprs *aliased, List **params_list);
  static void deparseOperandRelation(StringInfo buf, PlannerInfo *root,
  					   RelOptInfo *foreignrel, JoinType jointype,
! 					   bool add_rel_alias, AliasedExprs *aliased,
! 					   List **params_list);
  static void appendSubselectAlias(List *exprs, deparse_expr_cxt *context);
  static void registerAliasedExpr(Expr *expr, int tabno, int colno, AliasedExprs *aliased);
  static bool is_aliased_expr(Expr *node, int *tabno, int *colno, deparse_expr_cxt *context);
--- 185,197 ----
  static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context);
  static void appendConditions(List *exprs, deparse_expr_cxt *context);
  static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
! 					  RelOptInfo *foreignrel, Index ignore_rel,
! 					  bool add_rel_alias, AliasedExprs *aliased,
! 					  List **params_list);
  static void deparseOperandRelation(StringInfo buf, PlannerInfo *root,
  					   RelOptInfo *foreignrel, JoinType jointype,
! 					   Index ignore_rel, bool add_rel_alias,
! 					   AliasedExprs *aliased, List **params_list);
  static void appendSubselectAlias(List *exprs, deparse_expr_cxt *context);
  static void registerAliasedExpr(Expr *expr, int tabno, int colno, AliasedExprs *aliased);
  static bool is_aliased_expr(Expr *node, int *tabno, int *colno, deparse_expr_cxt *context);
***************
*** 934,940 **** deparseSelectSql(List *tlist,
  	 * Deparse a join tree expression in FROM clause first.
  	 */
  	initStringInfo(&jointree);
! 	deparseFromExprForRel(&jointree, root, foreignrel,
  						  (foreignrel->reloptkind == RELOPT_JOINREL),
  						  context->aliased, context->params_list);
  
--- 942,948 ----
  	 * Deparse a join tree expression in FROM clause first.
  	 */
  	initStringInfo(&jointree);
! 	deparseFromExprForRel(&jointree, root, foreignrel, (Index) 0,
  						  (foreignrel->reloptkind == RELOPT_JOINREL),
  						  context->aliased, context->params_list);
  
***************
*** 950,956 **** deparseSelectSql(List *tlist,
  	 */
  	if (tlist != NIL)
  	{
! 		deparseExplicitTargetList(tlist, retrieved_attrs, context);
  	}
  	else
  	{
--- 958,964 ----
  	 */
  	if (tlist != NIL)
  	{
! 		deparseExplicitTargetList(tlist, false, retrieved_attrs, context);
  	}
  	else
  	{
***************
*** 1250,1256 **** get_jointype_name(JoinType jointype)
   * from 1. It has same number of entries as tlist.
   */
  static void
! deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
  						  deparse_expr_cxt *context)
  {
  	ListCell   *lc;
--- 1258,1266 ----
   * from 1. It has same number of entries as tlist.
   */
  static void
! deparseExplicitTargetList(List *tlist,
! 						  bool is_returning,
! 						  List **retrieved_attrs,
  						  deparse_expr_cxt *context)
  {
  	ListCell   *lc;
***************
*** 1275,1280 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
--- 1285,1293 ----
  
  		if (i > 0)
  			appendStringInfoString(buf, ", ");
+ 		else if (is_returning)
+ 			appendStringInfoString(buf, " RETURNING ");
+ 
  		deparseExpr((Expr *) var, context);
  
  		*retrieved_attrs = lappend_int(*retrieved_attrs, i + 1);
***************
*** 1283,1289 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
  	}
  
  	/* Don't generate bad syntax if no columns */
! 	if (i == 0)
  		appendStringInfoString(buf, "NULL");
  }
  
--- 1296,1302 ----
  	}
  
  	/* Don't generate bad syntax if no columns */
! 	if (i == 0 && !is_returning)
  		appendStringInfoString(buf, "NULL");
  }
  
***************
*** 1294,1322 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
   * ((outer relation) <join type> (inner relation) ON (joinclauses))
   * For a base relation the function just adds the schema-qualified tablename,
   * with the appropriate alias if so requested.
   */
  static void
  deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
! 					  RelOptInfo *foreignrel, bool add_rel_alias,
! 					  AliasedExprs *aliased, List **params_list)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
  	if (foreignrel->reloptkind == RELOPT_JOINREL)
  	{
  		/* Begin the FROM clause entry. */
  		appendStringInfoChar(buf, '(');
  
  		/* Deparse outer relation */
  		deparseOperandRelation(buf, root, fpinfo->outerrel, fpinfo->jointype,
! 							   true, aliased, params_list);
  
  		/* Append join type */
  		appendStringInfo(buf, " %s JOIN ", get_jointype_name(fpinfo->jointype));
  
  		/* Deparse inner relation */
  		deparseOperandRelation(buf, root, fpinfo->innerrel, fpinfo->jointype,
! 							   true, aliased, params_list);
  
  		/* Append join conditions */
  		appendStringInfoString(buf, " ON ");
--- 1307,1375 ----
   * ((outer relation) <join type> (inner relation) ON (joinclauses))
   * For a base relation the function just adds the schema-qualified tablename,
   * with the appropriate alias if so requested.
+  *
+  * ignore_rel is either zero or the rangetable index of a target relation.
+  * In the latter case the function constructs a FROM clause of an UPDATE or
+  * a USING clause of a DELETE by deparsing a JOIN/ON clause with the
+  * ignore_rel target relation removed.  Note that we can do that safely
+  * because (1) any join to the target relation is an inner join and thus can
+  * be interchanged with higher-level joins (note: since we currently don't
+  * allow the target relation to appear on the nullable side of an outer join,
+  * this is true for the case where the higher-level join is an outer join,
+  * due to outer-join identity 1 shown in src/backend/optimizer/README), and
+  * (2) join conditions mentioning the target relation are already pulled up
+  * into the fpinfo->remote_conds of the given foreignrel, which are deparsed
+  * as WHERE conditions of the UPDATE/DELETE.
   */
  static void
  deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
! 					  RelOptInfo *foreignrel, Index ignore_rel,
! 					  bool add_rel_alias, AliasedExprs *aliased,
! 					  List **params_list)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
  	if (foreignrel->reloptkind == RELOPT_JOINREL)
  	{
+ 		bool		deparse_outer = true;
+ 		bool		deparse_inner = true;
+ 
+ 		if (ignore_rel > 0)
+ 		{
+ 			int			varno;
+ 
+ 			if (bms_get_singleton_member(fpinfo->outerrel->relids, &varno) &&
+ 				varno == ignore_rel)
+ 				deparse_outer = false;
+ 			if (bms_get_singleton_member(fpinfo->innerrel->relids, &varno) &&
+ 				varno == ignore_rel)
+ 				deparse_inner = false;
+ 		}
+ 
+ 		if (deparse_outer && !deparse_inner)
+ 			return deparseOperandRelation(buf, root, fpinfo->outerrel,
+ 										  fpinfo->jointype, ignore_rel,
+ 										  true, aliased, params_list);
+ 		if (!deparse_outer && deparse_inner)
+ 			return deparseOperandRelation(buf, root, fpinfo->innerrel,
+ 										  fpinfo->jointype, ignore_rel,
+ 										  true, aliased, params_list);
+ 
+ 		Assert(deparse_outer && deparse_inner);
+ 
  		/* Begin the FROM clause entry. */
  		appendStringInfoChar(buf, '(');
  
  		/* Deparse outer relation */
  		deparseOperandRelation(buf, root, fpinfo->outerrel, fpinfo->jointype,
! 							   ignore_rel, true, aliased, params_list);
  
  		/* Append join type */
  		appendStringInfo(buf, " %s JOIN ", get_jointype_name(fpinfo->jointype));
  
  		/* Deparse inner relation */
  		deparseOperandRelation(buf, root, fpinfo->innerrel, fpinfo->jointype,
! 							   ignore_rel, true, aliased, params_list);
  
  		/* Append join conditions */
  		appendStringInfoString(buf, " ON ");
***************
*** 1379,1386 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
  static void
  deparseOperandRelation(StringInfo buf, PlannerInfo *root,
  					   RelOptInfo *foreignrel, JoinType jointype,
! 					   bool add_rel_alias, AliasedExprs *aliased,
! 					   List **params_list)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
--- 1432,1439 ----
  static void
  deparseOperandRelation(StringInfo buf, PlannerInfo *root,
  					   RelOptInfo *foreignrel, JoinType jointype,
! 					   Index ignore_rel, bool add_rel_alias,
! 					   AliasedExprs *aliased, List **params_list)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
***************
*** 1411,1417 **** deparseOperandRelation(StringInfo buf, PlannerInfo *root,
  		appendSubselectAlias(foreignrel->reltarget->exprs, &context);
  	}
  	else
! 		deparseFromExprForRel(buf, root, foreignrel,
  							  true, aliased, params_list);
  }
  
--- 1464,1470 ----
  		appendSubselectAlias(foreignrel->reltarget->exprs, &context);
  	}
  	else
! 		deparseFromExprForRel(buf, root, foreignrel, ignore_rel,
  							  true, aliased, params_list);
  }
  
***************
*** 1653,1658 **** deparseUpdateSql(StringInfo buf, PlannerInfo *root,
--- 1706,1713 ----
  void
  deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
  					   Index rtindex, Relation rel,
+ 					   RelOptInfo *foreignrel,
+ 					   List **fdw_scan_tlist,
  					   List *targetlist,
  					   List *targetAttrs,
  					   List *remote_conds,
***************
*** 1660,1680 **** deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
  					   List *returningList,
  					   List **retrieved_attrs)
  {
! 	RelOptInfo *baserel = root->simple_rel_array[rtindex];
  	deparse_expr_cxt context;
  	int			nestlevel;
  	bool		first;
  	ListCell   *lc;
  
  	/* Set up context struct for recursion */
  	context.root = root;
! 	context.foreignrel = baserel;
  	context.buf = buf;
! 	context.aliased = NULL;
  	context.params_list = params_list;
  
  	appendStringInfoString(buf, "UPDATE ");
  	deparseRelation(buf, rel);
  	appendStringInfoString(buf, " SET ");
  
  	/* Make sure any constants in the exprs are printed portably */
--- 1715,1754 ----
  					   List *returningList,
  					   List **retrieved_attrs)
  {
! 	AliasedExprs aliased;
! 	StringInfoData jointree;
  	deparse_expr_cxt context;
  	int			nestlevel;
  	bool		first;
  	ListCell   *lc;
  
+ 	/* Initialize fields of AliasedExprs */
+ 	aliased.exprs = NIL;
+ 	aliased.max_exprs = 32;
+ 	aliased.tabnos = (int *) palloc(aliased.max_exprs * sizeof(int));
+ 	aliased.colnos = (int *) palloc(aliased.max_exprs * sizeof(int));
+ 	aliased.next_tabno = 1;
+ 	aliased.relids = NULL;
+ 
+ 	/* Deparse a join tree expression in FROM if an UPDATE on a join */
+ 	if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 	{
+ 		initStringInfo(&jointree);
+ 		deparseFromExprForRel(&jointree, root, foreignrel, rtindex,
+ 							  true, &aliased, params_list);
+ 	}
+ 
  	/* Set up context struct for recursion */
  	context.root = root;
! 	context.foreignrel = foreignrel;
  	context.buf = buf;
! 	context.aliased = &aliased;
  	context.params_list = params_list;
  
  	appendStringInfoString(buf, "UPDATE ");
  	deparseRelation(buf, rel);
+ 	if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 		appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex);
  	appendStringInfoString(buf, " SET ");
  
  	/* Make sure any constants in the exprs are printed portably */
***************
*** 1701,1714 **** deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
  
  	reset_transmission_modes(nestlevel);
  
  	if (remote_conds)
  	{
  		appendStringInfo(buf, " WHERE ");
  		appendConditions(remote_conds, &context);
  	}
  
! 	deparseReturningList(buf, root, rtindex, rel, false,
! 						 returningList, retrieved_attrs);
  }
  
  /*
--- 1775,1796 ----
  
  	reset_transmission_modes(nestlevel);
  
+ 	if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 		appendStringInfo(buf, " FROM %s", jointree.data);
+ 
  	if (remote_conds)
  	{
  		appendStringInfo(buf, " WHERE ");
  		appendConditions(remote_conds, &context);
  	}
  
! 	if (foreignrel->reloptkind == RELOPT_JOINREL)
! 		deparseExplicitReturningList(rtindex, rel, fdw_scan_tlist,
! 									 returningList, retrieved_attrs,
! 									 &context);
! 	else
! 		deparseReturningList(buf, root, rtindex, rel, false,
! 							 returningList, retrieved_attrs);
  }
  
  /*
***************
*** 1743,1765 **** deparseDeleteSql(StringInfo buf, PlannerInfo *root,
  void
  deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
  					   Index rtindex, Relation rel,
  					   List *remote_conds,
  					   List **params_list,
  					   List *returningList,
  					   List **retrieved_attrs)
  {
! 	RelOptInfo *baserel = root->simple_rel_array[rtindex];
  	deparse_expr_cxt context;
  
  	/* Set up context struct for recursion */
  	context.root = root;
! 	context.foreignrel = baserel;
  	context.buf = buf;
! 	context.aliased = NULL;
  	context.params_list = params_list;
  
  	appendStringInfoString(buf, "DELETE FROM ");
  	deparseRelation(buf, rel);
  
  	if (remote_conds)
  	{
--- 1825,1871 ----
  void
  deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
  					   Index rtindex, Relation rel,
+ 					   RelOptInfo *foreignrel,
+ 					   List **fdw_scan_tlist,
  					   List *remote_conds,
  					   List **params_list,
  					   List *returningList,
  					   List **retrieved_attrs)
  {
! 	AliasedExprs aliased;
! 	StringInfoData jointree;
  	deparse_expr_cxt context;
  
+ 	/* Initialize fields of AliasedExprs */
+ 	aliased.exprs = NIL;
+ 	aliased.max_exprs = 32;
+ 	aliased.tabnos = (int *) palloc(aliased.max_exprs * sizeof(int));
+ 	aliased.colnos = (int *) palloc(aliased.max_exprs * sizeof(int));
+ 	aliased.next_tabno = 1;
+ 	aliased.relids = NULL;
+ 
+ 	/* Deparse a join tree expression in USING if a DELETE on a join */
+ 	if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 	{
+ 		initStringInfo(&jointree);
+ 		deparseFromExprForRel(&jointree, root, foreignrel, rtindex,
+ 							  true, &aliased, params_list);
+ 	}
+ 
  	/* Set up context struct for recursion */
  	context.root = root;
! 	context.foreignrel = foreignrel;
  	context.buf = buf;
! 	context.aliased = &aliased;
  	context.params_list = params_list;
  
  	appendStringInfoString(buf, "DELETE FROM ");
  	deparseRelation(buf, rel);
+ 	if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 		appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex);
+ 
+ 	if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 		appendStringInfo(buf, " USING %s", jointree.data);
  
  	if (remote_conds)
  	{
***************
*** 1767,1774 **** deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
  		appendConditions(remote_conds, &context);
  	}
  
! 	deparseReturningList(buf, root, rtindex, rel, false,
! 						 returningList, retrieved_attrs);
  }
  
  /*
--- 1873,1885 ----
  		appendConditions(remote_conds, &context);
  	}
  
! 	if (foreignrel->reloptkind == RELOPT_JOINREL)
! 	  deparseExplicitReturningList(rtindex, rel, fdw_scan_tlist,
! 								   returningList, retrieved_attrs,
! 								   &context);
! 	else
! 		deparseReturningList(buf, root, rtindex, rel, false,
! 							 returningList, retrieved_attrs);
  }
  
  /*
***************
*** 1808,1813 **** deparseReturningList(StringInfo buf, PlannerInfo *root,
--- 1919,2035 ----
  }
  
  /*
+  * Add a RETURNING clause, if needed, to an UPDATE/DELETE on a join.
+  */
+ static void
+ deparseExplicitReturningList(Index rtindex, Relation rel,
+ 							 List **fdw_scan_tlist,
+ 							 List *returningList,
+ 							 List **retrieved_attrs,
+ 							 deparse_expr_cxt *context)
+ {
+ 	TupleDesc	tupdesc = RelationGetDescr(rel);
+ 	bool		have_wholerow = false;
+ 	List	   *tlist = NIL;
+ 	List	   *vars;
+ 	ListCell   *lc;
+ 
+ 	*retrieved_attrs = NIL;
+ 
+ 	if (returningList == NIL)
+ 		return;
+ 
+ 	vars = pull_var_clause((Node *) returningList, PVC_INCLUDE_PLACEHOLDERS);
+ 
+ 	/* Check that there's a whole-row reference to the result relation. */
+ 	foreach(lc, vars)
+ 	{
+ 		Var		   *var = (Var *) lfirst(lc);
+ 
+ 		if (IsA(var, Var) &&
+ 			var->varno == rtindex &&
+ 			var->varattno == InvalidAttrNumber)
+ 		{
+ 			have_wholerow = true;
+ 			break;
+ 		}
+ 	}
+ 
+ 	/* If so, we'll need all non-system columns of the result relation. */
+ 	if (have_wholerow)
+ 	{
+ 		int			i;
+ 
+ 		for (i = 1; i <= tupdesc->natts; i++)
+ 		{
+ 			Form_pg_attribute attr = tupdesc->attrs[i - 1];
+ 			Var		   *var;
+ 
+ 			/* Ignore dropped attributes. */
+ 			if (attr->attisdropped)
+ 				continue;
+ 
+ 			var = makeVar(rtindex,
+ 						  i,
+ 						  attr->atttypid,
+ 						  attr->atttypmod,
+ 						  attr->attcollation,
+ 						  0);
+ 
+ 			tlist = lappend(tlist,
+ 							makeTargetEntry((Expr *) var,
+ 											i,
+ 											NULL,
+ 											false));
+ 		}
+ 	}
+ 
+ 	/* Now add any remaining columns. */ 
+ 	foreach(lc, vars)
+ 	{
+ 		Var		   *var = (Var *) lfirst(lc);
+ 
+ 		if (IsA(var, Var) &&
+ 			var->varno == rtindex &&
+ 			var->varattno == InvalidAttrNumber)
+ 			continue;		/* don't need it */
+ 
+ 		if (tlist_member((Node *) var, tlist))
+ 			continue;		/* already got it */
+ 
+ 		tlist = lappend(tlist,
+ 						makeTargetEntry((Expr *) var,
+ 										list_length(tlist) + 1,
+ 										NULL,
+ 										false));
+ 	}
+ 
+ 	deparseExplicitTargetList(tlist, true, retrieved_attrs, context);
+ 
+ 	/* Finally, rewrite *fdw_scan_tlist. */
+ 	if (tlist != NIL)
+ 	{
+ 		foreach(lc, *fdw_scan_tlist)
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 			Var		   *var = (Var *) tle->expr;
+ 
+ 			if (tlist_member((Node *) var, tlist))
+ 				continue;		/* already got it */
+ 
+ 			tlist = lappend(tlist,
+ 							makeTargetEntry((Expr *) var,
+ 											list_length(tlist) + 1,
+ 											NULL,
+ 											false));
+ 		}
+ 		*fdw_scan_tlist = tlist;
+ 	}
+ 
+ 	list_free(vars);
+ }
+ 
+ /*
   * Construct SELECT statement to acquire size in blocks of given relation.
   *
   * Note: we use local definition of block size, not remote definition.
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2932,2958 **** UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING
  
  EXPLAIN (verbose, costs off)
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
!   FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;                               -- can't be pushed down
!                                                                                                                                              QUERY PLAN                                                                                                                                             
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Update on public.ft2
!    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3, c7 = $4 WHERE ctid = $1
!    ->  Foreign Scan
!          Output: ft2.c1, (ft2.c2 + 500), NULL::integer, (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, 'ft2       '::character(10), ft2.c8, ft2.ctid, ft1.*
!          Relations: (public.ft2) INNER JOIN (public.ft1)
!          Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c8, r1.ctid, ss1.c1 FROM ("S 1"."T 1" r1 INNER JOIN (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9))) ss1(c1, c2) ON (TRUE)) WHERE ((r1.c2 = ss1.c2)) FOR UPDATE OF r1
!          ->  Hash Join
!                Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid, ft1.*
!                Hash Cond: (ft2.c2 = ft1.c1)
!                ->  Foreign Scan on public.ft2
!                      Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid
!                      Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" FOR UPDATE
!                ->  Hash
!                      Output: ft1.*, ft1.c1
!                      ->  Foreign Scan on public.ft1
!                            Output: ft1.*, ft1.c1
!                            Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9))
! (17 rows)
  
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
    FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
--- 2932,2944 ----
  
  EXPLAIN (verbose, costs off)
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
!   FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;                               -- can be pushed down
!                                                                                                                                     QUERY PLAN                                                                                                                                     
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Update on public.ft2
!    ->  Foreign Update
!          Remote SQL: UPDATE "S 1"."T 1" r1 SET c2 = (r1.c2 + 500), c3 = (r1.c3 || '_update9'::text), c7 = 'ft2       '::character(10) FROM (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9))) ss1(c1, c2) WHERE ((r1.c2 = ss1.c2))
! (3 rows)
  
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
    FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
***************
*** 3075,3101 **** DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
  (103 rows)
  
  EXPLAIN (verbose, costs off)
! DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;                -- can't be pushed down
!                                                                                                                    QUERY PLAN                                                                                                                   
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Delete on public.ft2
!    Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1
!    ->  Foreign Scan
!          Output: ft2.ctid, ft1.*
!          Relations: (public.ft2) INNER JOIN (public.ft1)
!          Remote SQL: SELECT r1.ctid, ss1.c1 FROM ("S 1"."T 1" r1 INNER JOIN (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2))) ss1(c1, c2) ON (TRUE)) WHERE ((r1.c2 = ss1.c2)) FOR UPDATE OF r1
!          ->  Hash Join
!                Output: ft2.ctid, ft1.*
!                Hash Cond: (ft2.c2 = ft1.c1)
!                ->  Foreign Scan on public.ft2
!                      Output: ft2.ctid, ft2.c2
!                      Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" FOR UPDATE
!                ->  Hash
!                      Output: ft1.*, ft1.c1
!                      ->  Foreign Scan on public.ft1
!                            Output: ft1.*, ft1.c1
!                            Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2))
! (17 rows)
  
  DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
  SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
--- 3061,3073 ----
  (103 rows)
  
  EXPLAIN (verbose, costs off)
! DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;                -- can be pushed down
!                                                                                           QUERY PLAN                                                                                          
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Delete on public.ft2
!    ->  Foreign Delete
!          Remote SQL: DELETE FROM "S 1"."T 1" r1 USING (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2))) ss1(c1, c2) WHERE ((r1.c2 = ss1.c2))
! (3 rows)
  
  DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
  SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 210,215 **** typedef struct PgFdwDirectModifyState
--- 210,221 ----
  	PGresult   *result;			/* result for query */
  	int			num_tuples;		/* # of result tuples */
  	int			next_tuple;		/* index of next one to return */
+ 	Relation	resultRel;		/* relcache entry for the target table */
+ 	TupleTableSlot *resultSlot;	/* slot for updated/deleted tuples */
+ 	AttrNumber *attnoMap;		/* array of resultRel attr numbers */
+ 	AttrNumber	ctidAttno;		/* attnum of ctid of result tuple */
+ 	AttrNumber	oidAttno;		/* attnum of oid of result tuple */
+ 	bool		hasSystemCols;	/* are there system columns of resultRel? */
  
  	/* working memory context */
  	MemoryContext temp_cxt;		/* context for per-tuple temporary data */
***************
*** 374,379 **** static void store_returning_result(PgFdwModifyState *fmstate,
--- 380,391 ----
  					   TupleTableSlot *slot, PGresult *res);
  static void execute_dml_stmt(ForeignScanState *node);
  static TupleTableSlot *get_returning_data(ForeignScanState *node);
+ static void init_returning_filter(PgFdwDirectModifyState *dmstate,
+ 					  List *fdw_scan_tlist,
+ 					  Index rtindex,
+ 					  EState *estate);
+ static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ 					   TupleTableSlot *slot);
  static void prepare_query_params(PlanState *node,
  					 List *fdw_exprs,
  					 int numParams,
***************
*** 2144,2149 **** postgresPlanDirectModify(PlannerInfo *root,
--- 2156,2162 ----
  	Relation	rel;
  	StringInfoData sql;
  	ForeignScan *fscan;
+ 	RelOptInfo *foreignrel;
  	List	   *targetAttrs = NIL;
  	List	   *remote_conds;
  	List	   *params_list = NIL;
***************
*** 2175,2187 **** postgresPlanDirectModify(PlannerInfo *root,
  		return false;
  
  	/*
- 	 * We can't handle an UPDATE or DELETE on a foreign join for now.
- 	 */
- 	fscan = (ForeignScan *) subplan;
- 	if (fscan->scan.scanrelid == 0)
- 		return false;
- 
- 	/*
  	 * It's unsafe to update a foreign table directly, if any expressions to
  	 * assign to the target columns are unsafe to evaluate remotely.
  	 */
--- 2188,2193 ----
***************
*** 2220,2225 **** postgresPlanDirectModify(PlannerInfo *root,
--- 2226,2233 ----
  	/*
  	 * Ok, rewrite subplan so as to modify the foreign table directly.
  	 */
+ 	fscan = (ForeignScan *) subplan;
+ 
  	initStringInfo(&sql);
  
  	/*
***************
*** 2229,2234 **** postgresPlanDirectModify(PlannerInfo *root,
--- 2237,2254 ----
  	rel = heap_open(rte->relid, NoLock);
  
  	/*
+ 	 * Get a rel for this foreign table or join.
+ 	 */
+ 	if (fscan->scan.scanrelid == 0)
+ 	{
+ 		/* We should have a rel for this foreign join. */
+ 		foreignrel = find_join_rel(root, fscan->fs_relids);
+ 		Assert(foreignrel);
+ 	}
+ 	else
+ 		foreignrel = find_base_rel(root, resultRelation);
+ 
+ 	/*
  	 * Extract the baserestrictinfo clauses that can be evaluated remotely.
  	 */
  	remote_conds = (List *) list_nth(fscan->fdw_private,
***************
*** 2247,2252 **** postgresPlanDirectModify(PlannerInfo *root,
--- 2267,2274 ----
  	{
  		case CMD_UPDATE:
  			deparseDirectUpdateSql(&sql, root, resultRelation, rel,
+ 								   foreignrel,
+ 								   &fscan->fdw_scan_tlist,
  								   ((Plan *) fscan)->targetlist,
  								   targetAttrs,
  								   remote_conds, &params_list,
***************
*** 2254,2259 **** postgresPlanDirectModify(PlannerInfo *root,
--- 2276,2283 ----
  			break;
  		case CMD_DELETE:
  			deparseDirectDeleteSql(&sql, root, resultRelation, rel,
+ 								   foreignrel,
+ 								   &fscan->fdw_scan_tlist,
  								   remote_conds, &params_list,
  								   returningList, &retrieved_attrs);
  			break;
***************
*** 2281,2286 **** postgresPlanDirectModify(PlannerInfo *root,
--- 2305,2316 ----
  									retrieved_attrs,
  									makeInteger(plan->canSetTag));
  
+ 	/*
+ 	 * We don't need the outer subplan.
+ 	 */
+ 	if (fscan->scan.scanrelid == 0)
+ 		fscan->scan.plan.lefttree = NULL;
+ 
  	heap_close(rel, NoLock);
  	return true;
  }
***************
*** 2295,2300 **** postgresBeginDirectModify(ForeignScanState *node, int eflags)
--- 2325,2331 ----
  	ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
  	EState	   *estate = node->ss.ps.state;
  	PgFdwDirectModifyState *dmstate;
+ 	Index		rtindex;
  	RangeTblEntry *rte;
  	Oid			userid;
  	ForeignTable *table;
***************
*** 2317,2327 **** postgresBeginDirectModify(ForeignScanState *node, int eflags)
  	 * Identify which user to do the remote access as.  This should match what
  	 * ExecCheckRTEPerms() does.
  	 */
! 	rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table);
  	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
  
  	/* Get info about foreign table. */
! 	dmstate->rel = node->ss.ss_currentRelation;
  	table = GetForeignTable(RelationGetRelid(dmstate->rel));
  	user = GetUserMapping(userid, table->serverid);
  
--- 2348,2362 ----
  	 * Identify which user to do the remote access as.  This should match what
  	 * ExecCheckRTEPerms() does.
  	 */
! 	rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
! 	rte = rt_fetch(rtindex, estate->es_range_table);
  	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
  
  	/* Get info about foreign table. */
! 	if (fsplan->scan.scanrelid > 0)
! 		dmstate->rel = node->ss.ss_currentRelation;
! 	else
! 		dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
  	table = GetForeignTable(RelationGetRelid(dmstate->rel));
  	user = GetUserMapping(userid, table->serverid);
  
***************
*** 2331,2336 **** postgresBeginDirectModify(ForeignScanState *node, int eflags)
--- 2366,2380 ----
  	 */
  	dmstate->conn = GetConnection(user, false);
  
+ 	if (fsplan->scan.scanrelid == 0)
+ 	{
+ 		/* Save info about target table. */
+ 		dmstate->resultRel = dmstate->rel;
+ 
+ 		/* rel should be NULL if foreign join. */
+ 		dmstate->rel = NULL;
+ 	}
+ 
  	/* Initialize state variable */
  	dmstate->num_tuples = -1;	/* -1 means not set yet */
  
***************
*** 2351,2357 **** postgresBeginDirectModify(ForeignScanState *node, int eflags)
  
  	/* Prepare for input conversion of RETURNING results. */
  	if (dmstate->has_returning)
! 		dmstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(dmstate->rel));
  
  	/*
  	 * Prepare for processing of parameters used in remote query, if any.
--- 2395,2415 ----
  
  	/* Prepare for input conversion of RETURNING results. */
  	if (dmstate->has_returning)
! 	{
! 		TupleDesc	tupdesc;
! 
! 		if (fsplan->scan.scanrelid > 0)
! 			tupdesc = RelationGetDescr(dmstate->rel);
! 		else
! 			tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
! 
! 		dmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
! 
! 		/* Initialize a filter to extract an updated/deleted tuple. */
! 		if (fsplan->scan.scanrelid == 0)
! 			init_returning_filter(dmstate, fsplan->fdw_scan_tlist,
! 								  rtindex, estate);
! 	}
  
  	/*
  	 * Prepare for processing of parameters used in remote query, if any.
***************
*** 2432,2437 **** postgresEndDirectModify(ForeignScanState *node)
--- 2490,2499 ----
  	ReleaseConnection(dmstate->conn);
  	dmstate->conn = NULL;
  
+ 	/* close the result relation. */
+ 	if (dmstate->resultRel)
+ 		ExecCloseScanRelation(dmstate->resultRel);
+ 
  	/* MemoryContext will be deleted automatically. */
  }
  
***************
*** 3282,3287 **** get_returning_data(ForeignScanState *node)
--- 3344,3350 ----
  	EState	   *estate = node->ss.ps.state;
  	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
  	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ 	TupleTableSlot *resultSlot;
  
  	Assert(resultRelInfo->ri_projectReturning);
  
***************
*** 3299,3305 **** get_returning_data(ForeignScanState *node)
--- 3362,3371 ----
  	 * "UPDATE/DELETE .. RETURNING 1" for example.)
  	 */
  	if (!dmstate->has_returning)
+ 	{
  		ExecStoreAllNullTuple(slot);
+ 		resultSlot = slot;
+ 	}
  	else
  	{
  		/*
***************
*** 3315,3321 **** get_returning_data(ForeignScanState *node)
  												dmstate->rel,
  												dmstate->attinmeta,
  												dmstate->retrieved_attrs,
! 												NULL,
  												dmstate->temp_cxt);
  			ExecStoreTuple(newtup, slot, InvalidBuffer, false);
  		}
--- 3381,3387 ----
  												dmstate->rel,
  												dmstate->attinmeta,
  												dmstate->retrieved_attrs,
! 												node,
  												dmstate->temp_cxt);
  			ExecStoreTuple(newtup, slot, InvalidBuffer, false);
  		}
***************
*** 3326,3341 **** get_returning_data(ForeignScanState *node)
  			PG_RE_THROW();
  		}
  		PG_END_TRY();
  	}
  	dmstate->next_tuple++;
  
  	/* Make slot available for evaluation of the local query RETURNING list. */
! 	resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple = slot;
  
  	return slot;
  }
  
  /*
   * Prepare for processing of parameters used in remote query.
   */
  static void
--- 3392,3579 ----
  			PG_RE_THROW();
  		}
  		PG_END_TRY();
+ 
+ 		/* Get the updated/deleted tuple. */
+ 		if (dmstate->rel)
+ 			resultSlot = slot;
+ 		else
+ 			resultSlot = apply_returning_filter(dmstate, slot);
  	}
  	dmstate->next_tuple++;
  
  	/* Make slot available for evaluation of the local query RETURNING list. */
! 	resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple = resultSlot;
  
  	return slot;
  }
  
  /*
+  * Initialize a filter to extract an updated/deleted tuple from a scan tuple.
+  */
+ static void
+ init_returning_filter(PgFdwDirectModifyState *dmstate,
+ 					  List *fdw_scan_tlist,
+ 					  Index rtindex,
+ 					  EState *estate)
+ {
+ 	TupleDesc	resultTupType = RelationGetDescr(dmstate->resultRel);
+ 	ListCell   *lc;
+ 	int			i;
+ 
+ 	/* Make a new slot for storing an output tuple. */
+ 	dmstate->resultSlot = ExecInitExtraTupleSlot(estate);
+ 	ExecSetSlotDescriptor(dmstate->resultSlot, resultTupType);
+ 
+ 	/*
+ 	 * Calculate the mapping between the fdw_scan_tlist list's entries and
+ 	 * the updated/deleted tuple's attributes.
+ 	 *
+ 	 * The "map" is an array of the resultRel attribute numbers, i.e. one
+ 	 * entry for every attribute of the updated/deleted tuple.  The value of
+ 	 * this entry is the index of the corresponding entry in fdw_scan_tlist.
+ 	 * We store zero for any attributes that don't have the corresponding
+ 	 * entries in fdw_scan_tlist (i.e. attributes that won't be retrieved
+ 	 * from the remote server), marking that a NULL is needed in the output
+ 	 * tuple.
+ 	 *
+ 	 * Also get the index of the entry for ctid/oid of the updated/deleted
+ 	 * tuple if any.
+ 	 */
+ 	dmstate->attnoMap = (AttrNumber *) palloc0(resultTupType->natts * sizeof(AttrNumber));
+ 
+ 	dmstate->ctidAttno = dmstate->oidAttno = 0;
+ 
+ 	i = 1;
+ 	dmstate->hasSystemCols = false;
+ 	foreach(lc, fdw_scan_tlist)
+ 	{
+ 		if (list_member_int(dmstate->retrieved_attrs, i))
+ 		{
+ 			TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 			Var		   *var = (Var *) tle->expr;
+ 
+ 			if (IsA(var, Var) && var->varno == rtindex)
+ 			{
+ 				int			attrno = var->varattno;
+ 
+ 				if (attrno < 0)
+ 				{
+ 					if (attrno == SelfItemPointerAttributeNumber)
+ 						dmstate->ctidAttno = i;
+ 					else if (attrno == ObjectIdAttributeNumber)
+ 						dmstate->oidAttno = i;
+ 					dmstate->hasSystemCols = true;
+ 				}
+ 				else
+ 				{
+ 					Assert(attrno > 0);
+ 					dmstate->attnoMap[attrno - 1] = i;
+ 				}
+ 			}
+ 		}
+ 		i++;
+ 	}
+ }
+ 
+ /*
+  * Extract and return an updated/deleted tuple from a scan tuple.
+  */
+ static TupleTableSlot *
+ apply_returning_filter(PgFdwDirectModifyState *dmstate,
+ 					   TupleTableSlot *slot)
+ {
+ 	TupleTableSlot *resultSlot = dmstate->resultSlot;
+ 	TupleDesc	resultTupType = RelationGetDescr(dmstate->resultRel);
+ 	HeapTuple	resultTup;
+ 	Datum	   *values;
+ 	bool	   *isnull;
+ 	Datum	   *old_values;
+ 	bool	   *old_isnull;
+ 	int			i;
+ 
+ 	/*
+ 	 * Extract all the values of the scan tuple.
+ 	 */
+ 	slot_getallattrs(slot);
+ 	old_values = slot->tts_values;
+ 	old_isnull = slot->tts_isnull;
+ 
+ 	/*
+ 	 * Prepare to build a result tuple.
+ 	 */
+ 	ExecClearTuple(resultSlot);
+ 	values = resultSlot->tts_values;
+ 	isnull = resultSlot->tts_isnull;
+ 
+ 	/*
+ 	 * Transpose data into proper fields of the result tuple.
+ 	 */
+ 	for (i = 0; i < resultTupType->natts; i++)
+ 	{
+ 		int			j = dmstate->attnoMap[i];
+ 
+ 		if (j == 0)
+ 		{
+ 			values[i] = (Datum) 0;
+ 			isnull[i] = true;
+ 		}
+ 		else
+ 		{
+ 			values[i] = old_values[j - 1];
+ 			isnull[i] = old_isnull[j - 1];
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Build the virtual result tuple.
+ 	 */
+ 	ExecStoreVirtualTuple(resultSlot);
+ 
+ 	/*
+ 	 * If we have any system columns to return, install it in t_self.
+ 	 */
+ 	if (dmstate->hasSystemCols)
+ 	{
+ 		resultTup = ExecMaterializeSlot(resultSlot);
+ 
+ 		/*
+ 		 * xmin, xmax, cmin, and tableoid
+ 		 *
+ 		 * Note: we currently don't allow the result relation to appear on
+ 		 * the nullable side of an outer join.
+ 		 */
+ 		HeapTupleHeaderSetXmin(resultTup->t_data, InvalidTransactionId);
+ 		HeapTupleHeaderSetXmax(resultTup->t_data, InvalidTransactionId);
+ 		HeapTupleHeaderSetCmin(resultTup->t_data, InvalidTransactionId);
+ 
+ 		resultTup->t_tableOid = RelationGetRelid(dmstate->resultRel);
+ 
+ 		/* ctid */
+ 		if (dmstate->ctidAttno)
+ 		{
+ 			ItemPointer ctid = NULL;
+ 
+ 			ctid = (ItemPointer) DatumGetPointer(old_values[dmstate->ctidAttno - 1]);
+ 			resultTup->t_self = *ctid;
+ 		}
+ 
+ 		/* oid */
+ 		if (dmstate->oidAttno)
+ 		{
+ 			Oid			oid = InvalidOid;
+ 
+ 			oid = DatumGetObjectId(old_values[dmstate->oidAttno - 1]);
+ 			HeapTupleSetOid(resultTup, oid);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * And return the result tuple.
+ 	 */
+ 	return resultSlot;
+ }
+ 
+ /*
   * Prepare for processing of parameters used in remote query.
   */
  static void
***************
*** 4455,4465 **** make_tuple_from_result_row(PGresult *res,
  		tupdesc = RelationGetDescr(rel);
  	else
  	{
- 		PgFdwScanState *fdw_sstate;
- 
  		Assert(fsstate);
! 		fdw_sstate = (PgFdwScanState *) fsstate->fdw_state;
! 		tupdesc = fdw_sstate->tupdesc;
  	}
  
  	values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
--- 4693,4700 ----
  		tupdesc = RelationGetDescr(rel);
  	else
  	{
  		Assert(fsstate);
! 		tupdesc = fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
  	}
  
  	values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
*** a/contrib/postgres_fdw/postgres_fdw.h
--- b/contrib/postgres_fdw/postgres_fdw.h
***************
*** 143,148 **** extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root,
--- 143,150 ----
  				 List **retrieved_attrs);
  extern void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
  					   Index rtindex, Relation rel,
+ 					   RelOptInfo *foreignrel,
+ 					   List **fdw_scan_tlist,
  					   List *targetlist,
  					   List *targetAttrs,
  					   List *remote_conds,
***************
*** 155,160 **** extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root,
--- 157,164 ----
  				 List **retrieved_attrs);
  extern void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
  					   Index rtindex, Relation rel,
+ 					   RelOptInfo *foreignrel,
+ 					   List **fdw_scan_tlist,
  					   List *remote_conds,
  					   List **params_list,
  					   List *returningList,
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 685,698 **** UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING
  UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
  EXPLAIN (verbose, costs off)
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
!   FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;                               -- can't be pushed down
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
    FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
  EXPLAIN (verbose, costs off)
    DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;                               -- can be pushed down
  DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
  EXPLAIN (verbose, costs off)
! DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;                -- can't be pushed down
  DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
  SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
  EXPLAIN (verbose, costs off)
--- 685,698 ----
  UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
  EXPLAIN (verbose, costs off)
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
!   FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;                               -- can be pushed down
  UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
    FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
  EXPLAIN (verbose, costs off)
    DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;                               -- can be pushed down
  DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
  EXPLAIN (verbose, costs off)
! DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;                -- can be pushed down
  DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
  SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
  EXPLAIN (verbose, costs off)
