On 2016/02/22 20:13, Rushabh Lathia wrote:
PFA update patch, which includes changes into postgresPlanDMLPushdown()
to check for join
condition before target columns and also fixed couple of whitespace issues.


For pushing down an UPDATE/DELETE on a foreign join to the remote, I created a WIP patch on top of the latest version of the DML pushdown patch. Attached is the WIP patch. I'd like to propose this as part of (I'd like to discuss this as a separate patch, though):

https://commitfest.postgresql.org/9/453/

The patch doesn't correctly evaluate the values of system columns of joined relations in RETURNING, other than ctid. I'll fix that ASAP.

Best regards,
Etsuro Fujita
*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 130,135 **** static void deparseReturningList(StringInfo buf, PlannerInfo *root,
--- 130,143 ----
  					 bool trig_after_row,
  					 List *returningList,
  					 List **retrieved_attrs);
+ static void deparseJoinedReturningList(List *fdw_scan_tlist,
+ 						   Index rtindex,
+ 						   Bitmapset *attrs_used,
+ 						   List *returningList,
+ 						   List **retrieved_attrs,
+ 						   List **result_attrs,
+ 						   List **result_attrnos,
+ 						   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);
***************
*** 158,164 **** 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 *joinrel, bool use_alias, List **params_list);
  
  
  /*
--- 166,175 ----
  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 *joinrel,
! 					Index ignore_rel,
! 					bool use_alias,
! 					List **params_list);
  
  
  /*
***************
*** 850,856 **** deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
  	 * Construct FROM clause
  	 */
  	appendStringInfoString(buf, " FROM ");
! 	deparseFromExprForRel(buf, root, foreignrel,
  						  (foreignrel->reloptkind == RELOPT_JOINREL),
  						  context->params_list);
  }
--- 861,867 ----
  	 * Construct FROM clause
  	 */
  	appendStringInfoString(buf, " FROM ");
! 	deparseFromExprForRel(buf, root, foreignrel, (Index) 0,
  						  (foreignrel->reloptkind == RELOPT_JOINREL),
  						  context->params_list);
  }
***************
*** 1135,1144 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
   * The function constructs ... JOIN ... ON ... for join relation. For a base
   * relation it just returns schema-qualified tablename, with the appropriate
   * alias if so requested.
   */
  static void
! deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
! 					  bool use_alias, List **params_list)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
--- 1146,1166 ----
   * The function constructs ... JOIN ... ON ... for join relation. For a base
   * relation it just returns schema-qualified tablename, with the appropriate
   * alias if so requested.
+  *
+  * If constructing FROM clause of UPDATE statement or USING clause of DELETE
+  * statement, we simply ignore the ignore_rel target relation when deparsing
+  * the join to the target relation.  Note that the join is safely interchanged
+  * with higher-level outer joins (if any) by outer-join identity 1 since that
+  * the join won't appear on the nullable side of such outer joins (we currently
+  * don't allow the result relation to appear on the nullable side of an outer
+  * join) and that the target relation won't be outer-joined to other relations.
   */
  static void
! deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
! 					  RelOptInfo *foreignrel,
! 					  Index ignore_rel,
! 					  bool use_alias,
! 					  List **params_list)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
***************
*** 1148,1169 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
  		RelOptInfo *rel_i = fpinfo->innerrel;
  		StringInfoData join_sql_o;
  		StringInfoData join_sql_i;
  
  		/* Deparse outer relation */
! 		initStringInfo(&join_sql_o);
! 		deparseFromExprForRel(&join_sql_o, root, rel_o, true, params_list);
  
  		/* Deparse inner relation */
! 		initStringInfo(&join_sql_i);
! 		deparseFromExprForRel(&join_sql_i, root, rel_i, true, params_list);
  
  		/*
  		 * For a join relation FROM clause entry is deparsed as
  		 *
  		 * ((outer relation) <join type> (inner relation) ON (joinclauses)
  		 */
! 		appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data,
! 					   get_jointype_name(fpinfo->jointype), join_sql_i.data);
  
  		/* Append join clause; (TRUE) if no join clause */
  		if (fpinfo->joinclauses)
--- 1170,1260 ----
  		RelOptInfo *rel_i = fpinfo->innerrel;
  		StringInfoData join_sql_o;
  		StringInfoData join_sql_i;
+ 		bool		do_deparse_o = true;
+ 		bool		do_deparse_i = true;
+ 		Relids		result = NULL;
+ 		Relids		target = NULL;
+ 
+ 		if (ignore_rel > 0)
+ 		{
+ 			int			varno_o = -1;
+ 			int			varno_i = -1;
+ 
+ 			do_deparse_o =
+ 				!(bms_get_singleton_member(rel_o->relids, &varno_o) &&
+ 				  (varno_o == ignore_rel));
+ 			do_deparse_i =
+ 				!(bms_get_singleton_member(rel_i->relids, &varno_i) &&
+ 				  (varno_i == ignore_rel));
+ 		}
  
  		/* Deparse outer relation */
! 		if (do_deparse_o)
! 		{
! 			initStringInfo(&join_sql_o);
! 			deparseFromExprForRel(&join_sql_o, root, rel_o, ignore_rel, true,
! 								  params_list);
! 		}
  
  		/* Deparse inner relation */
! 		if (do_deparse_i)
! 		{
! 			initStringInfo(&join_sql_i);
! 			deparseFromExprForRel(&join_sql_i, root, rel_i, ignore_rel, true,
! 								  params_list);
! 		}
! 
! 		if (!do_deparse_o || !do_deparse_i)
! 		{
! 			/* This should be for UPDATE/DELETE */
! 			Assert(ignore_rel > 0);
! 			/* The join should be an inner join */
! 			Assert(fpinfo->joinclauses == NIL);
! 
! 			/* Don't parenthesize the expression */
! 			if (!do_deparse_o)
! 				appendStringInfo(buf, "%s", join_sql_i.data);
! 			else
! 				appendStringInfo(buf, "%s", join_sql_o.data);
! 			return;
! 		}
  
  		/*
  		 * For a join relation FROM clause entry is deparsed as
  		 *
  		 * ((outer relation) <join type> (inner relation) ON (joinclauses)
+ 		 *
+ 		 * Note: if constructing FROM clause of UPDATE or USING clause of DELETE,
+ 		 * don't parenthesize the topmost expression.
  		 */
! 
! 		/* Begin the FROM clause entry. */
! 		if (ignore_rel == 0 && bms_equal(foreignrel->relids, root->all_baserels))
! 			appendStringInfoChar(buf, '(');
! 
! 		target = bms_make_singleton(ignore_rel);
! 
! 		if (ignore_rel == 0)
! 			result = rel_o->relids;
! 		else
! 			result = bms_difference(rel_o->relids, target);
! 		if (bms_num_members(result) > 1)
! 			appendStringInfo(buf, "(%s)", join_sql_o.data);
! 		else
! 			appendStringInfo(buf, "%s", join_sql_o.data);
! 
! 		appendStringInfo(buf, " %s JOIN ", get_jointype_name(fpinfo->jointype));
! 
! 		if (ignore_rel == 0)
! 			result = rel_i->relids;
! 		else
! 			result = bms_difference(rel_i->relids, target);
! 		if (bms_num_members(result) > 1)
! 			appendStringInfo(buf, "(%s)", join_sql_i.data);
! 		else
! 			appendStringInfo(buf, "%s", join_sql_i.data);
! 
! 		appendStringInfoString(buf, " ON ");
  
  		/* Append join clause; (TRUE) if no join clause */
  		if (fpinfo->joinclauses)
***************
*** 1183,1189 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
  			appendStringInfoString(buf, "(TRUE)");
  
  		/* End the FROM clause entry. */
! 		appendStringInfo(buf, ")");
  	}
  	else
  	{
--- 1274,1281 ----
  			appendStringInfoString(buf, "(TRUE)");
  
  		/* End the FROM clause entry. */
! 		if (ignore_rel == 0 && bms_equal(foreignrel->relids, root->all_baserels))
! 			appendStringInfoChar(buf, ')');
  	}
  	else
  	{
***************
*** 1207,1213 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
  
  		heap_close(rel, NoLock);
  	}
- 	return;
  }
  
  /*
--- 1299,1304 ----
***************
*** 1325,1338 **** deparseUpdateSql(StringInfo buf, PlannerInfo *root,
  void
  deparsePushedDownUpdateSql(StringInfo buf, PlannerInfo *root,
  						   Index rtindex, Relation rel,
  						   List	*targetlist,
  						   List *targetAttrs,
  						   List	*remote_conds,
  						   List **params_list,
  						   List *returningList,
! 						   List **retrieved_attrs)
  {
- 	RelOptInfo *baserel = root->simple_rel_array[rtindex];
  	deparse_expr_cxt context;
  	int			nestlevel;
  	bool		first;
--- 1416,1433 ----
  void
  deparsePushedDownUpdateSql(StringInfo buf, PlannerInfo *root,
  						   Index rtindex, Relation rel,
+ 						   RelOptInfo *foreignrel,
+ 						   List *fdw_scan_tlist,
+ 						   Bitmapset *attrs_used,
  						   List	*targetlist,
  						   List *targetAttrs,
  						   List	*remote_conds,
  						   List **params_list,
  						   List *returningList,
! 						   List **retrieved_attrs,
! 						   List **result_attrs,
! 						   List **result_attrnos)
  {
  	deparse_expr_cxt context;
  	int			nestlevel;
  	bool		first;
***************
*** 1340,1351 **** deparsePushedDownUpdateSql(StringInfo buf, PlannerInfo *root,
  
  	/* Set up context struct for recursion */
  	context.root = root;
! 	context.foreignrel = baserel;
  	context.buf = buf;
  	context.params_list = params_list;
  
  	appendStringInfoString(buf, "UPDATE ");
  	deparseRelation(buf, rel);
  	appendStringInfoString(buf, " SET ");
  
  	/* Make sure any constants in the exprs are printed portably */
--- 1435,1448 ----
  
  	/* Set up context struct for recursion */
  	context.root = root;
! 	context.foreignrel = foreignrel;
  	context.buf = buf;
  	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 */
***************
*** 1368,1381 **** deparsePushedDownUpdateSql(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);
  }
  
  /*
--- 1465,1490 ----
  
  	reset_transmission_modes(nestlevel);
  
+ 	if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 	{
+ 		appendStringInfoString(buf, " FROM ");
+ 		deparseFromExprForRel(buf, root, foreignrel, rtindex, true,
+ 							  params_list);
+ 	}
+ 
  	if (remote_conds)
  	{
  		appendStringInfo(buf, " WHERE ");
  		appendConditions(remote_conds, &context);
  	}
  
! 	if (foreignrel->reloptkind == RELOPT_JOINREL)
! 		deparseJoinedReturningList(fdw_scan_tlist, rtindex, attrs_used,
! 								   returningList, retrieved_attrs,
! 								   result_attrs, result_attrnos, &context);
! 	else
! 		deparseReturningList(buf, root, rtindex, rel, false,
! 							 returningList, retrieved_attrs);
  }
  
  /*
***************
*** 1410,1431 **** deparseDeleteSql(StringInfo buf, PlannerInfo *root,
  void
  deparsePushedDownDeleteSql(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.params_list = params_list;
  
  	appendStringInfoString(buf, "DELETE FROM ");
  	deparseRelation(buf, rel);
  
  	if (remote_conds)
  	{
--- 1519,1553 ----
  void
  deparsePushedDownDeleteSql(StringInfo buf, PlannerInfo *root,
  						   Index rtindex, Relation rel,
+ 						   RelOptInfo *foreignrel,
+ 						   List *fdw_scan_tlist,
+ 						   Bitmapset *attrs_used,
  						   List	*remote_conds,
  						   List	**params_list,
  						   List *returningList,
! 						   List **retrieved_attrs,
! 						   List **result_attrs,
! 						   List **result_attrnos)
  {
  	deparse_expr_cxt context;
  
  	/* Set up context struct for recursion */
  	context.root = root;
! 	context.foreignrel = foreignrel;
  	context.buf = buf;
  	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)
+ 	{
+ 		appendStringInfoString(buf, " USING ");
+ 		deparseFromExprForRel(buf, root, foreignrel, rtindex, true,
+ 							  params_list);
+ 	}
  
  	if (remote_conds)
  	{
***************
*** 1433,1440 **** deparsePushedDownDeleteSql(StringInfo buf, PlannerInfo *root,
  		appendConditions(remote_conds, &context);
  	}
  
! 	deparseReturningList(buf, root, rtindex, rel, false,
! 						 returningList, retrieved_attrs);
  }
  
  /*
--- 1555,1567 ----
  		appendConditions(remote_conds, &context);
  	}
  
! 	if (foreignrel->reloptkind == RELOPT_JOINREL)
! 		deparseJoinedReturningList(fdw_scan_tlist, rtindex, attrs_used,
! 								   returningList, retrieved_attrs,
! 								   result_attrs, result_attrnos, &context);
! 	else
! 		deparseReturningList(buf, root, rtindex, rel, false,
! 							 returningList, retrieved_attrs);
  }
  
  /*
***************
*** 1474,1479 **** deparseReturningList(StringInfo buf, PlannerInfo *root,
--- 1601,1682 ----
  }
  
  /*
+  * Add a RETURNING clause, if needed, to an UPDATE/DELETE on a foreign join.
+  */
+ static void
+ deparseJoinedReturningList(List *fdw_scan_tlist,
+ 						   Index rtindex,
+ 						   Bitmapset *attrs_used,
+ 						   List *returningList,
+ 						   List **retrieved_attrs,
+ 						   List **result_attrs,
+ 						   List **result_attrnos,
+ 						   deparse_expr_cxt *context)
+ {
+ 	StringInfo	buf = context->buf;
+ 	List	   *returning_vars;
+ 	bool		have_wholerow;
+ 	bool		first;
+ 	ListCell   *lc;
+ 	int			i;
+ 
+ 	*retrieved_attrs = NIL;
+ 	*result_attrs = NIL;
+ 	*result_attrnos = NIL;
+ 
+ 	if (returningList == NIL)
+ 		return;
+ 
+ 	/* Pull out all the Vars mentioned in returningList */
+ 	returning_vars = pull_var_clause((Node *) returningList,
+ 									 PVC_REJECT_AGGREGATES,
+ 									 PVC_RECURSE_PLACEHOLDERS);
+ 
+ 	/*
+ 	 * If there's a whole-row reference of the result relation, we'll need all
+ 	 * the columns of the relation.
+ 	*/
+ 	have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ 								  attrs_used);
+ 
+ 	first = true;
+ 	i = 1;
+ 	foreach(lc, fdw_scan_tlist)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ 		Var		   *var = (Var *) tle->expr;
+ 
+ 		if (((var->varno != rtindex) &&
+ 			 (var->varattno >= 0 ||
+ 			  var->varattno == SelfItemPointerAttributeNumber) &&
+ 			 list_member(returning_vars, var)) ||
+ 			((var->varno == rtindex) &&
+ 			 (have_wholerow ||
+ 			  bms_is_member(var->varattno - FirstLowInvalidHeapAttributeNumber,
+ 							attrs_used))))
+ 		{
+ 			if (!first)
+ 				appendStringInfoString(buf, ", ");
+ 			else
+ 				appendStringInfoString(buf, " RETURNING ");
+ 			first = false;
+ 
+ 			deparseVar(var, context);
+ 
+ 			*retrieved_attrs = lappend_int(*retrieved_attrs, i);
+ 
+ 			if (var->varno == rtindex)
+ 			{
+ 				*result_attrs = lappend_int(*result_attrs, i);
+ 				*result_attrnos = lappend_int(*result_attrnos, var->varattno);
+ 			}
+ 		}
+ 
+ 		i++;
+ 	}
+ }
+ 
+ /*
   * 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
***************
*** 2387,2413 **** 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, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9)) 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;
--- 2387,2399 ----
  
  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 "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9))
! (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;
***************
*** 2530,2556 **** 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, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 2)) 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;
--- 2516,2528 ----
  (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 "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 2))
! (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
***************
*** 108,113 **** enum FdwModifyPrivateIndex
--- 108,115 ----
   * 2) Boolean flag showing if the remote query has a RETURNING clause
   * 3) Integer list of attribute numbers retrieved by RETURNING, if any
   * 4) Boolean flag showing if we set the command es_processed
+  * 5) Integer list of the attribute numbers of result rel's columns
+  * 6) Integer list of result rel's attribute numbers of the columns
   */
  enum FdwDmlPushdownPrivateIndex
  {
***************
*** 118,124 **** enum FdwDmlPushdownPrivateIndex
  	/* Integer list of attribute numbers retrieved by RETURNING */
  	FdwDmlPushdownPrivateRetrievedAttrs,
  	/* set-processed flag (as an integer Value node) */
! 	FdwDmlPushdownPrivateSetProcessed
  };
  
  /*
--- 120,130 ----
  	/* Integer list of attribute numbers retrieved by RETURNING */
  	FdwDmlPushdownPrivateRetrievedAttrs,
  	/* set-processed flag (as an integer Value node) */
! 	FdwDmlPushdownPrivateSetProcessed,
! 	/* Integer list of the attribute numbers of result rel's columns */
! 	FdwDmlPushdownPrivateResultAttrs,
! 	/* Integer list of result rel's attribute numbers of the columns */
! 	FdwDmlPushdownPrivateResultAttrnos
  };
  
  /*
***************
*** 201,206 **** typedef struct PgFdwDmlPushdownState
--- 207,214 ----
  	bool		has_returning;	/* is there a RETURNING clause? */
  	List	   *retrieved_attrs;	/* attr numbers retrieved by RETURNING */
  	bool		set_processed;	/* do we set the command es_processed? */
+ 	List	   *result_attrs;
+ 	List	   *result_attrnos;
  
  	/* for remote query execution */
  	PGconn	   *conn;			/* connection for the update */
***************
*** 213,218 **** typedef struct PgFdwDmlPushdownState
--- 221,230 ----
  	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;
+ 	AttrNumber	ctidAttno;
  
  	/* working memory context */
  	MemoryContext temp_cxt;		/* context for per-tuple temporary data */
***************
*** 378,385 **** static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
--- 390,404 ----
  						 TupleTableSlot *slot);
  static void store_returning_result(PgFdwModifyState *fmstate,
  					   TupleTableSlot *slot, PGresult *res);
+ static void expand_scan_tlist(Index resultRelation,
+ 				  Relation rel,
+ 				  Bitmapset *attrs_used,
+ 				  List **fdw_scan_tlist);
  static void execute_dml_stmt(ForeignScanState *node);
  static TupleTableSlot *get_returning_data(ForeignScanState *node);
+ static void init_returning_filter(PgFdwDmlPushdownState *dpstate);
+ static TupleTableSlot *execute_returning_filter(PgFdwDmlPushdownState *dpstate,
+ 												TupleTableSlot *slot);
  static void prepare_query_params(PlanState *node,
  					 List *fdw_exprs,
  					 int numParams,
***************
*** 2069,2079 **** postgresPlanDMLPushdown(PlannerInfo *root,
--- 2088,2102 ----
  	Relation	rel;
  	StringInfoData sql;
  	ForeignScan *fscan;
+ 	RelOptInfo *foreignrel;
+ 	Bitmapset  *attrs_used = NULL;
  	List	   *targetAttrs = NIL;
  	List	   *remote_conds;
  	List	   *params_list = NIL;
  	List	   *returningList = NIL;
  	List	   *retrieved_attrs = NIL;
+ 	List	   *result_attrs = NIL;
+ 	List	   *result_attrnos = NIL;
  
  	/*
  	 * Decide whether the table modification is pushdown-safe.
***************
*** 2100,2113 **** postgresPlanDMLPushdown(PlannerInfo *root,
  		return false;
  
  	/*
! 	 * 4. We can't push down an UPDATE or DELETE on a foreign join for now.
! 	 */
! 	fscan = (ForeignScan *) subplan;
! 	if (fscan->scan.scanrelid == 0)
! 		return false;
! 
! 	/*
! 	 * 5. We can't push down an UPDATE, if any expressions to assign to the
  	 * target columns are unsafe to evaluate on the remote end.
  	 */
  	if (operation == CMD_UPDATE)
--- 2123,2129 ----
  		return false;
  
  	/*
! 	 * 4. We can't push down an UPDATE, if any expressions to assign to the
  	 * target columns are unsafe to evaluate on the remote end.
  	 */
  	if (operation == CMD_UPDATE)
***************
*** 2141,2146 **** postgresPlanDMLPushdown(PlannerInfo *root,
--- 2157,2164 ----
  	/*
  	 * Ok, modify subplan so as to push down the command to the remote server.
  	 */
+ 	fscan = (ForeignScan *) subplan;
+ 
  	initStringInfo(&sql);
  
  	/*
***************
*** 2150,2155 **** postgresPlanDMLPushdown(PlannerInfo *root,
--- 2168,2185 ----
  	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,
***************
*** 2159,2166 **** postgresPlanDMLPushdown(PlannerInfo *root,
--- 2189,2219 ----
  	 * Extract the relevant RETURNING list if any.
  	 */
  	if (plan->returningLists)
+ 	{
  		returningList = (List *) list_nth(plan->returningLists, subplan_index);
  
+ 		if (foreignrel->reloptkind == RELOPT_JOINREL)
+ 		{
+ 			/*
+ 			 * We need the attrs, non-system and system, mentioned in the local
+ 			 * query's RETURNING list.
+ 			 */
+ 			pull_varattnos((Node *) returningList, resultRelation,
+ 						   &attrs_used);
+ 
+ 			/*
+ 			 * For UPDATE queries, fdw_scan_tlist contains tlist entries for
+ 			 * all attributes of the result relation through the work of the
+ 			 * rewriter and planner, but for DELETE queries, it doesn't contain
+ 			 * any such entriers except for ctid.  So, add to it tlist entries
+ 			 * needed for the local query's RETURNING calculation.
+ 			 */
+ 			if (operation == CMD_DELETE)
+ 				expand_scan_tlist(resultRelation, rel, attrs_used,
+ 								  &fscan->fdw_scan_tlist);
+ 		}
+ 	}
+ 
  	/*
  	 * Construct the SQL command string.
  	 */
***************
*** 2168,2182 **** postgresPlanDMLPushdown(PlannerInfo *root,
  	{
  		case CMD_UPDATE:
  			deparsePushedDownUpdateSql(&sql, root, resultRelation, rel,
  									   ((Plan *) fscan)->targetlist,
  									   targetAttrs,
  									   remote_conds, &params_list,
! 									   returningList, &retrieved_attrs);
  			break;
  		case CMD_DELETE:
  			deparsePushedDownDeleteSql(&sql, root, resultRelation, rel,
  									   remote_conds, &params_list,
! 									   returningList, &retrieved_attrs);
  			break;
  		default:
  			elog(ERROR, "unexpected operation: %d", (int) operation);
--- 2221,2241 ----
  	{
  		case CMD_UPDATE:
  			deparsePushedDownUpdateSql(&sql, root, resultRelation, rel,
+ 									   foreignrel, fscan->fdw_scan_tlist,
+ 									   attrs_used,
  									   ((Plan *) fscan)->targetlist,
  									   targetAttrs,
  									   remote_conds, &params_list,
! 									   returningList, &retrieved_attrs,
! 									   &result_attrs, &result_attrnos);
  			break;
  		case CMD_DELETE:
  			deparsePushedDownDeleteSql(&sql, root, resultRelation, rel,
+ 									   foreignrel, fscan->fdw_scan_tlist,
+ 									   attrs_used,
  									   remote_conds, &params_list,
! 									   returningList, &retrieved_attrs,
! 									   &result_attrs, &result_attrnos);
  			break;
  		default:
  			elog(ERROR, "unexpected operation: %d", (int) operation);
***************
*** 2202,2207 **** postgresPlanDMLPushdown(PlannerInfo *root,
--- 2261,2272 ----
  									retrieved_attrs,
  									makeInteger(plan->canSetTag));
  
+ 	fscan->fdw_private = lappend(fscan->fdw_private, result_attrs);
+ 	fscan->fdw_private = lappend(fscan->fdw_private, result_attrnos);
+ 
+ 	if (fscan->scan.scanrelid == 0)
+ 		fscan->scan.plan.lefttree = NULL;
+ 
  	heap_close(rel, NoLock);
  	return true;
  }
***************
*** 2216,2221 **** postgresBeginDMLPushdown(ForeignScanState *node, int eflags)
--- 2281,2287 ----
  	ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
  	EState	   *estate = node->ss.ps.state;
  	PgFdwDmlPushdownState *dpstate;
+ 	Index		rtindex;
  	RangeTblEntry *rte;
  	Oid			userid;
  	ForeignTable *table;
***************
*** 2238,2248 **** postgresBeginDMLPushdown(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. */
! 	dpstate->rel = node->ss.ss_currentRelation;
  	table = GetForeignTable(RelationGetRelid(dpstate->rel));
  	user = GetUserMapping(userid, table->serverid);
  
--- 2304,2323 ----
  	 * Identify which user to do the remote access as.  This should match what
  	 * ExecCheckRTEPerms() does.
  	 */
! 	if (fsplan->scan.scanrelid == 0)
! 		rtindex = estate->es_result_relation_info->ri_RangeTableIndex;
! 	else
! 		rtindex = fsplan->scan.scanrelid;
! 
! 	rte = rt_fetch(rtindex, estate->es_range_table);
  	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
  
  	/* Get info about foreign table. */
! 	if (fsplan->scan.scanrelid == 0)
! 		dpstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
! 	else
! 		dpstate->rel = node->ss.ss_currentRelation;
! 
  	table = GetForeignTable(RelationGetRelid(dpstate->rel));
  	user = GetUserMapping(userid, table->serverid);
  
***************
*** 2252,2257 **** postgresBeginDMLPushdown(ForeignScanState *node, int eflags)
--- 2327,2341 ----
  	 */
  	dpstate->conn = GetConnection(user, false);
  
+ 	if (fsplan->scan.scanrelid == 0)
+ 	{
+ 		/* Save info about target table. */
+ 		dpstate->resultRel = dpstate->rel;
+ 
+ 		/* rel should be NULL if foreign join. */
+ 		dpstate->rel = NULL;
+ 	}
+ 
  	/* Initialize state variable */
  	dpstate->num_tuples = -1;		/* -1 means not set yet */
  
***************
*** 2264,2269 **** postgresBeginDMLPushdown(ForeignScanState *node, int eflags)
--- 2348,2357 ----
  										FdwDmlPushdownPrivateRetrievedAttrs);
  	dpstate->set_processed = intVal(list_nth(fsplan->fdw_private,
  										 FdwDmlPushdownPrivateSetProcessed));
+ 	dpstate->result_attrs = (List *) list_nth(fsplan->fdw_private,
+ 										FdwDmlPushdownPrivateResultAttrs);
+ 	dpstate->result_attrnos = (List *) list_nth(fsplan->fdw_private,
+ 										FdwDmlPushdownPrivateResultAttrnos);
  
  	/* Create context for per-tuple temp workspace. */
  	dpstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
***************
*** 2274,2280 **** postgresBeginDMLPushdown(ForeignScanState *node, int eflags)
  
  	/* Prepare for input conversion of RETURNING results. */
  	if (dpstate->has_returning)
! 		dpstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(dpstate->rel));
  
  	/*
  	 * Prepare for processing of parameters used in remote query, if any.
--- 2362,2386 ----
  
  	/* Prepare for input conversion of RETURNING results. */
  	if (dpstate->has_returning)
! 	{
! 		TupleDesc	tupdesc;
! 
! 		if (fsplan->scan.scanrelid == 0)
! 			tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
! 		else
! 			tupdesc = RelationGetDescr(dpstate->rel);
! 
! 		dpstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
! 
! 		if (fsplan->scan.scanrelid == 0)
! 		{
! 			/* Make a new slot for storing an output tuple. */
! 			dpstate->resultSlot = ExecInitExtraTupleSlot(estate);
! 
! 			/* Initialize a filter to extract an updated/deleted tuple. */
! 			init_returning_filter(dpstate);
! 		}
! 	}
  
  	/*
  	 * Prepare for processing of parameters used in remote query, if any.
***************
*** 2355,2360 **** postgresEndDMLPushdown(ForeignScanState *node)
--- 2461,2470 ----
  	ReleaseConnection(dpstate->conn);
  	dpstate->conn = NULL;
  
+ 	/* close the result relation. */
+ 	if (dpstate->resultRel)
+ 		ExecCloseScanRelation(dpstate->resultRel);
+ 
  	/* MemoryContext will be deleted automatically. */
  }
  
***************
*** 3116,3121 **** store_returning_result(PgFdwModifyState *fmstate,
--- 3226,3282 ----
  }
  
  /*
+  * Add to *fdw_scan_tlist tlist entries for the columns specified in attrs_used.
+  */
+ static void
+ expand_scan_tlist(Index resultRelation,
+ 				  Relation rel,
+ 				  Bitmapset *attrs_used,
+ 				  List **fdw_scan_tlist)
+ {
+ 	TupleDesc	tupdesc = RelationGetDescr(rel);
+ 	bool		have_wholerow;
+ 	int			next_resno;
+ 	int			i;
+ 
+ 	/* If there's a whole-row reference, we'll need all the columns. */
+ 	have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ 								  attrs_used);
+ 
+ 	next_resno = list_length(*fdw_scan_tlist) + 1;
+ 	for (i = 1; i <= tupdesc->natts; i++)
+ 	{
+ 		Form_pg_attribute attr = tupdesc->attrs[i - 1];
+ 
+ 		/* Ignore dropped attributes. */
+ 		if (attr->attisdropped)
+ 			continue;
+ 
+ 		if (have_wholerow ||
+ 			bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
+ 						  attrs_used))
+ 		{
+ 			Node	   *expr;
+ 			TargetEntry *tle;
+ 
+ 			expr = (Node *) makeVar(resultRelation,
+ 									i,
+ 									attr->atttypid,
+ 									attr->atttypmod,
+ 									attr->attcollation,
+ 									0);
+ 
+ 			tle = makeTargetEntry((Expr *) expr,
+ 								  next_resno++,
+ 								  NULL,
+ 								  false);
+ 
+ 			*fdw_scan_tlist = lappend(*fdw_scan_tlist, tle);
+ 		}
+ 	}
+ }
+ 
+ /*
   * Execute a pushed-down UPDATE/DELETE statement.
   */
  static void
***************
*** 3169,3174 **** get_returning_data(ForeignScanState *node)
--- 3330,3336 ----
  	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);
  
***************
*** 3186,3192 **** get_returning_data(ForeignScanState *node)
--- 3348,3357 ----
  	 * UPDATE/DELETE .. RETURNING 1 for example.)
  	 */
  	if (!dpstate->has_returning)
+ 	{
  		ExecStoreAllNullTuple(slot);
+ 		resultSlot = slot;
+ 	}
  	else
  	{
  		/*
***************
*** 3202,3208 **** get_returning_data(ForeignScanState *node)
  												dpstate->rel,
  												dpstate->attinmeta,
  												dpstate->retrieved_attrs,
! 												NULL,
  												dpstate->temp_cxt);
  			ExecStoreTuple(newtup, slot, InvalidBuffer, false);
  		}
--- 3367,3373 ----
  												dpstate->rel,
  												dpstate->attinmeta,
  												dpstate->retrieved_attrs,
! 												node,
  												dpstate->temp_cxt);
  			ExecStoreTuple(newtup, slot, InvalidBuffer, false);
  		}
***************
*** 3213,3228 **** get_returning_data(ForeignScanState *node)
  			PG_RE_THROW();
  		}
  		PG_END_TRY();
  	}
  	dpstate->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
--- 3378,3521 ----
  			PG_RE_THROW();
  		}
  		PG_END_TRY();
+ 
+ 		/* Get the updated/deleted tuple. */
+ 		if (dpstate->rel)
+ 			resultSlot = slot;
+ 		else
+ 			resultSlot = execute_returning_filter(dpstate, slot);
  	}
  	dpstate->next_tuple++;
  
! 	/*
! 	 * Make resultSlot available to the ModifyTable node for evaluation of
! 	 * the local query's RETURNING list.
! 	 */
! 	resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple = resultSlot;
  
  	return slot;
  }
  
  /*
+  * Initialize a filter to extract an updated/deleted tuple from a given
+  * fdw_scan_tlist tuple.
+  */
+ static void
+ init_returning_filter(PgFdwDmlPushdownState *dpstate)
+ {
+ 	TupleDesc	resultTupType = RelationGetDescr(dpstate->resultRel);
+ 	ListCell   *lc;
+ 	ListCell   *lc2;
+ 
+ 	ExecSetSlotDescriptor(dpstate->resultSlot, resultTupType);
+ 
+ 	/*
+ 	 * Calculate the mapping between the fdw_scan_tlist tuple's attributes 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 attribute number of the corresponding attribute of
+ 	 * the fdw_scan_tlist tuple.  We store zero for any attributes that won't
+ 	 * be retrieved from the remote server, marking that a NULL is needed in
+ 	 * the output tuple.  We also get the attribute number of the ctid of the
+ 	 * updated/deleted tuple, if any.
+ 	 */
+ 	dpstate->attnoMap = (AttrNumber *) palloc0(resultTupType->natts * sizeof(AttrNumber));
+ 
+ 	dpstate->ctidAttno = 0;
+ 
+ 	forboth(lc, dpstate->result_attrs, lc2, dpstate->result_attrnos)
+ 	{
+ 		int			attr = lfirst_int(lc);
+ 		int			attrno = lfirst_int(lc2);
+ 
+ 		if (attrno == SelfItemPointerAttributeNumber)
+ 			dpstate->ctidAttno = attr;
+ 		else
+ 		{
+ 			Assert(attrno > 0);
+ 			dpstate->attnoMap[attrno - 1] = attr;
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Extract and return an updated/deleted tuple from a given fdw_scan_tlist
+  * tuple.
+  */
+ static TupleTableSlot *
+ execute_returning_filter(PgFdwDmlPushdownState *dpstate,
+ 						 TupleTableSlot *slot)
+ {
+ 	TupleTableSlot *resultSlot = dpstate->resultSlot;
+ 	TupleDesc	resultTupType = RelationGetDescr(dpstate->resultRel);
+ 	int			i;
+ 	Datum	   *values;
+ 	bool	   *isnull;
+ 	Datum	   *old_values;
+ 	bool	   *old_isnull;
+ 
+ 	/*
+ 	 * Extract all the values of the old 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 new tuple.
+ 	 */
+ 	for (i = 0; i < resultTupType->natts; i++)
+ 	{
+ 		int			j = dpstate->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 a CTID to return, install it in t_self.
+ 	 */
+ 	if (dpstate->ctidAttno)
+ 	{
+ 		ItemPointer ctid = NULL;
+ 		HeapTuple	resultTup;
+ 
+ 		ctid = (ItemPointer) DatumGetPointer(old_values[dpstate->ctidAttno - 1]);
+ 
+ 		resultTup = ExecMaterializeSlot(resultSlot);
+ 
+ 		resultTup->t_self = *ctid;
+ 	}
+ 
+ 	/*
+ 	 * And return the result tuple.
+ 	 */
+ 	return resultSlot;
+ }
+ 
+ /*
   * Prepare for processing of parameters used in remote query.
   */
  static void
***************
*** 4258,4268 **** 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));
--- 4551,4558 ----
  		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
***************
*** 132,153 **** extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root,
  				 List **retrieved_attrs);
  extern void deparsePushedDownUpdateSql(StringInfo buf, PlannerInfo *root,
  									   Index rtindex, Relation rel,
  									   List	*targetlist,
  									   List *targetAttrs,
  									   List	*remote_conds,
  									   List **params_list,
  									   List *returningList,
! 									   List **retrieved_attrs);
  extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root,
  				 Index rtindex, Relation rel,
  				 List *returningList,
  				 List **retrieved_attrs);
  extern void deparsePushedDownDeleteSql(StringInfo buf, PlannerInfo *root,
  									   Index rtindex, Relation rel,
  									   List	*remote_conds,
  									   List **params_list,
  									   List *returningList,
! 									   List **retrieved_attrs);
  extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
  extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
  				  List **retrieved_attrs);
--- 132,163 ----
  				 List **retrieved_attrs);
  extern void deparsePushedDownUpdateSql(StringInfo buf, PlannerInfo *root,
  									   Index rtindex, Relation rel,
+ 									   RelOptInfo *foreignrel,
+ 									   List *fdw_scan_tlist,
+ 									   Bitmapset *attrs_used,
  									   List	*targetlist,
  									   List *targetAttrs,
  									   List	*remote_conds,
  									   List **params_list,
  									   List *returningList,
! 									   List **retrieved_attrs,
! 									   List **result_attrs,
! 									   List **result_attrnos);
  extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root,
  				 Index rtindex, Relation rel,
  				 List *returningList,
  				 List **retrieved_attrs);
  extern void deparsePushedDownDeleteSql(StringInfo buf, PlannerInfo *root,
  									   Index rtindex, Relation rel,
+ 									   RelOptInfo *foreignrel,
+ 									   List *fdw_scan_tlist,
+ 									   Bitmapset *attrs_used,
  									   List	*remote_conds,
  									   List **params_list,
  									   List *returningList,
! 									   List **retrieved_attrs,
! 									   List **result_attrs,
! 									   List **result_attrnos);
  extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
  extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
  				  List **retrieved_attrs);
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 607,620 **** 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)
--- 607,620 ----
  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)
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to