*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 48,53 ****
--- 48,54 ----
  #include "nodes/nodeFuncs.h"
  #include "nodes/plannodes.h"
  #include "optimizer/clauses.h"
+ #include "optimizer/placeholder.h"
  #include "optimizer/prep.h"
  #include "optimizer/tlist.h"
  #include "optimizer/var.h"
***************
*** 164,169 **** static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context);
--- 165,171 ----
  static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context);
  static void deparseNullTest(NullTest *node, deparse_expr_cxt *context);
  static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context);
+ static void deparseExprInPlaceHolderVar(PlaceHolderVar *node, deparse_expr_cxt *context);
  static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod,
  				 deparse_expr_cxt *context);
  static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
***************
*** 657,662 **** foreign_expr_walker(Node *node,
--- 659,683 ----
  				check_type = false;
  			}
  			break;
+ 		case T_PlaceHolderVar:
+ 			{
+ 				PlaceHolderVar *phv = (PlaceHolderVar *) node;
+ 				PlaceHolderInfo *phinfo = find_placeholder_info(glob_cxt->root,
+ 																phv, false);
+ 
+ 				/*
+ 				 * If the PHV's contained expression is computable on the
+ 				 * remote server, we consider the PHV safe to send.
+ 				 */
+ 				if (phv->phlevelsup == 0 &&
+ 					bms_is_subset(phinfo->ph_eval_at,
+ 								  glob_cxt->foreignrel->relids))
+ 					return foreign_expr_walker((Node *) phv->phexpr,
+ 											   glob_cxt, outer_cxt);
+ 				else
+ 					return false;
+ 			}
+ 			break;
  		default:
  
  			/*
***************
*** 749,769 **** deparse_type_name(Oid type_oid, int32 typemod)
   * foreign server for the given relation.
   */
  List *
! build_tlist_to_deparse(RelOptInfo *foreignrel)
  {
! 	List	   *tlist = NIL;
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
  	/*
! 	 * We require columns specified in foreignrel->reltarget->exprs and those
! 	 * required for evaluating the local conditions.
  	 */
! 	tlist = add_to_flat_tlist(tlist,
! 					   pull_var_clause((Node *) foreignrel->reltarget->exprs,
! 									   PVC_RECURSE_PLACEHOLDERS));
! 	tlist = add_to_flat_tlist(tlist,
! 							  pull_var_clause((Node *) fpinfo->local_conds,
! 											  PVC_RECURSE_PLACEHOLDERS));
  
  	return tlist;
  }
--- 770,849 ----
   * foreign server for the given relation.
   */
  List *
! build_tlist_to_deparse(PlannerInfo *root, RelOptInfo *foreignrel)
  {
! 	List	   *tlist;
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
  	/*
! 	 * Add expressions in foreignrel's reltarget as-is if the expressions are
! 	 * all shippable.  Otherwise add shipplable expressions in the reltarget
! 	 * as-is plus expressions required to evaluate non-shippable expressions
! 	 * in the reltarget.
  	 */
! 	if (fpinfo->reltarget_is_shippable)
! 		tlist = make_tlist_from_pathtarget(foreignrel->reltarget);
! 	else
! 	{
! 		List	   *exprs = NIL;
! 		ListCell   *lc;
! 
! 		/* Note: we have at least one non-shippable PHV in the reltarget. */
! 		foreach(lc, foreignrel->reltarget->exprs)
! 		{
! 			Node	   *node = (Node *) lfirst(lc);
! 
! 			Assert(IsA(node, Var) || IsA(node, PlaceHolderVar));
! 
! 			if (IsA(node, Var))
! 				exprs = lappend(exprs, node);
! 			else
! 			{
! 				PlaceHolderVar *phv = (PlaceHolderVar *) node;
! 				PlaceHolderInfo *phinfo = find_placeholder_info(root, phv,
! 																false);
! 
! 				if (bms_is_subset(phinfo->ph_eval_at,
! 								  fpinfo->outerrel->relids) ||
! 					bms_is_subset(phinfo->ph_eval_at,
! 								  fpinfo->innerrel->relids))
! 				{
! 					/*
! 					 * The PHV coming from an either input should be shippable.
! 					 */
! 					exprs = lappend(exprs, node);
! 				}
! 				else
! 				{
! 					/*
! 					 * The PHV might be shippable, but in any case we retrieve
! 					 * expressions required to evaluate the PHV.
! 					 */
! 					List	   *exprs2 = pull_var_clause(node,
! 													PVC_INCLUDE_PLACEHOLDERS);
! 					ListCell   *lc2;
! 
! 					foreach(lc2, exprs2)
! 					{
! 						Node	   *node2 = (Node *) lfirst(lc2);
! 
! 						exprs = lappend(exprs, node2);
! 					}
! 				}
! 			}
! 		}
! 
! 		tlist = NIL;
! 		tlist = add_to_flat_tlist(tlist, exprs);
! 	}
! 
! 	/*
! 	 * Add expressions required to evaluate local conditions, if any.
! 	 */
! 	if (fpinfo->local_conds)
! 		tlist = add_to_flat_tlist(tlist,
! 								pull_var_clause((Node *) fpinfo->local_conds,
! 												PVC_INCLUDE_PLACEHOLDERS));
  
  	return tlist;
  }
***************
*** 1190,1198 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
  		Assert(IsA(tle, TargetEntry));
  		var = (Var *) tle->expr;
  
! 		/* We expect only Var nodes here */
! 		if (!IsA(var, Var))
! 			elog(ERROR, "non-Var not expected in target list");
  
  		if (i > 0)
  			appendStringInfoString(buf, ", ");
--- 1270,1279 ----
  		Assert(IsA(tle, TargetEntry));
  		var = (Var *) tle->expr;
  
! 		/* Should be a Var or PlaceHolderVar node */
! 		if (!IsA(var, Var) && !IsA(var, PlaceHolderVar))
! 			elog(ERROR, "unexpected node type in target list: %d",
! 			     (int) nodeTag(var));
  
  		if (i > 0)
  			appendStringInfoString(buf, ", ");
***************
*** 1293,1299 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
   *
   * If the given relation is an operand of a foreign join performing a full
   * outer join and has conditions that need to be evaluated below the full
!  * outer join, deparse the relation as a subquery.
   */
  static void
  deparseRangeTblRef(StringInfo buf, PlannerInfo *root,
--- 1374,1382 ----
   *
   * If the given relation is an operand of a foreign join performing a full
   * outer join and has conditions that need to be evaluated below the full
!  * outer join, deparse the relation as a subquery.  Also, if the relation's
!  * reltarget has non-Var expressions, do the same, regardless of the join type
!  * of the foreign join.
   */
  static void
  deparseRangeTblRef(StringInfo buf, PlannerInfo *root,
***************
*** 1304,1311 **** deparseRangeTblRef(StringInfo buf, PlannerInfo *root,
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
  	Assert(fpinfo->local_conds == NIL);
  
! 	if (jointype == JOIN_FULL && fpinfo->remote_conds)
  	{
  		List	   *tlist;
  		List	   *retrieved_attrs;
--- 1387,1396 ----
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
  	Assert(fpinfo->local_conds == NIL);
+ 	Assert(fpinfo->reltarget_is_shippable);
  
! 	if ((jointype == JOIN_FULL && fpinfo->remote_conds) ||
! 		fpinfo->reltarget_has_non_vars)
  	{
  		List	   *tlist;
  		List	   *retrieved_attrs;
***************
*** 1317,1323 **** deparseRangeTblRef(StringInfo buf, PlannerInfo *root,
  		context.aliased = aliased;
  		context.params_list = params_list;
  
! 		tlist = build_tlist_to_deparse(foreignrel);
  		appendStringInfoChar(buf, '(');
  		deparseSelectSql(tlist,
  						 fpinfo->remote_conds,
--- 1402,1408 ----
  		context.aliased = aliased;
  		context.params_list = params_list;
  
! 		tlist = build_tlist_to_deparse(root, foreignrel);
  		appendStringInfoChar(buf, '(');
  		deparseSelectSql(tlist,
  						 fpinfo->remote_conds,
***************
*** 1847,1860 **** deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root,
  			fetchval = rte->relid;
  		}
  
! 		if (qualify_col)
! 		{
! 			appendStringInfoString(buf, "CASE WHEN (");
! 			ADD_REL_QUALIFIER(buf, varno);
! 			appendStringInfo(buf, "*)::text IS NOT NULL THEN %u END", fetchval);
! 		}
! 		else
! 			appendStringInfo(buf, "%u", fetchval);
  	}
  	else if (varattno == 0)
  	{
--- 1932,1938 ----
  			fetchval = rte->relid;
  		}
  
! 		appendStringInfo(buf, "%u", fetchval);
  	}
  	else if (varattno == 0)
  	{
***************
*** 1885,1912 **** deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root,
  		attrs_used = bms_add_member(NULL,
  									0 - FirstLowInvalidHeapAttributeNumber);
  
- 		/*
- 		 * In case the whole-row reference is under an outer join then it has
- 		 * to go NULL whenever the rest of the row goes NULL. Deparsing a join
- 		 * query would always involve multiple relations, thus qualify_col
- 		 * would be true.
- 		 */
- 		if (qualify_col)
- 		{
- 			appendStringInfoString(buf, "CASE WHEN (");
- 			ADD_REL_QUALIFIER(buf, varno);
- 			appendStringInfo(buf, "*)::text IS NOT NULL THEN ");
- 		}
- 
  		appendStringInfoString(buf, "ROW(");
  		deparseTargetList(buf, root, varno, rel, false, attrs_used, qualify_col,
  						  &retrieved_attrs);
  		appendStringInfoString(buf, ")");
  
- 		/* Complete the CASE WHEN statement started above. */
- 		if (qualify_col)
- 			appendStringInfo(buf, " END");
- 
  		heap_close(rel, NoLock);
  		bms_free(attrs_used);
  	}
--- 1963,1973 ----
***************
*** 2088,2093 **** deparseExpr(Expr *node, deparse_expr_cxt *context)
--- 2149,2157 ----
  		case T_ArrayExpr:
  			deparseArrayExpr((ArrayExpr *) node, context);
  			break;
+ 		case T_PlaceHolderVar:
+ 			deparseExprInPlaceHolderVar((PlaceHolderVar *) node, context);
+ 			break;
  		default:
  			elog(ERROR, "unsupported expression type for deparse: %d",
  				 (int) nodeTag(node));
***************
*** 2659,2664 **** deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context)
--- 2723,2737 ----
  }
  
  /*
+  * Deparse a PlaceHolderVar's contained expression.
+  */
+ static void
+ deparseExprInPlaceHolderVar(PlaceHolderVar *node, deparse_expr_cxt *context)
+ {
+ 	deparseExpr(node->phexpr, context);
+ }
+ 
+ /*
   * Print the representation of a parameter to be sent to the remote side.
   *
   * Note: we always label the Param's type explicitly rather than relying on
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 1503,1510 **** SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1
  -- tests whole-row reference for row marks
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1;
!                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                     
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
--- 1503,1510 ----
  -- tests whole-row reference for row marks
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1;
!                                                                                                                                                                                    QUERY PLAN                                                                                                                                                                                   
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
***************
*** 1512,1518 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
--- 1512,1518 ----
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR UPDATE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1") ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
***************
*** 1547,1554 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
  
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
!                                                                                                                                                                                                                              QUERY PLAN                                                                                                                                                                                                                              
! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
--- 1547,1554 ----
  
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
!                                                                                                                                                                                         QUERY PLAN                                                                                                                                                                                         
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
***************
*** 1556,1562 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
--- 1556,1562 ----
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR UPDATE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR UPDATE) ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
***************
*** 1592,1599 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
  -- join two tables with FOR SHARE clause
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1;
!                                                                                                                                                                                                                     QUERY PLAN                                                                                                                                                                                                                     
! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
--- 1592,1599 ----
  -- join two tables with FOR SHARE clause
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1;
!                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                   
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
***************
*** 1601,1607 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
--- 1601,1607 ----
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR SHARE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1") ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
***************
*** 1636,1643 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
  
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
!                                                                                                                                                                                                                             QUERY PLAN                                                                                                                                                                                                                             
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
--- 1636,1643 ----
  
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
!                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                        
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
     ->  LockRows
***************
*** 1645,1651 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 FOR SHARE OF r2
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
--- 1645,1651 ----
           ->  Foreign Scan
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR SHARE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR SHARE) ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST
                 ->  Merge Join
                       Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                       Merge Cond: (t1.c1 = t2.c1)
***************
*** 1715,1728 **** WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2
  -- ctid with whole-row reference
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
!                                                                                                                                                                                                          QUERY PLAN                                                                                                                                                                                                         
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
     ->  Foreign Scan
           Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
           Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!          Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r1."C 1", r1.c3, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST
  (6 rows)
  
  -- SEMI JOIN, not pushed down
--- 1715,1728 ----
  -- ctid with whole-row reference
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
!                                                                                                                                                                                QUERY PLAN                                                                                                                                                                                
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
     ->  Foreign Scan
           Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
           Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!          Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss1.c4, ss2.c1 FROM ((SELECT ctid, ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1", c3 FROM "S 1"."T 1") ss1(c1, c2, c3, c4) INNER JOIN (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1") ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c3 = ss2.c2)) ORDER BY ss1.c4 ASC NULLS LAST, ss1.c3 ASC NULLS LAST
  (6 rows)
  
  -- SEMI JOIN, not pushed down
***************
*** 2051,2075 **** SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM
     1
  (10 rows)
  
! -- non-Var items in targelist of the nullable rel of a join preventing
! -- push-down in some cases
! -- unable to push {ft1, ft2}
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
!                                                         QUERY PLAN                                                         
! ---------------------------------------------------------------------------------------------------------------------------
!  Nested Loop Left Join
     Output: (13), ft2.c1
!    Join Filter: (13 = ft2.c1)
!    ->  Foreign Scan on public.ft2
!          Output: ft2.c1
!          Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST
!    ->  Materialize
!          Output: (13)
!          ->  Foreign Scan on public.ft1
!                Output: 13
!                Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13))
! (11 rows)
  
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
   a  | c1 
--- 2051,2066 ----
     1
  (10 rows)
  
! -- check join pushdown in situations where PHVs are involved
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
!                                                                                                QUERY PLAN                                                                                               
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  Foreign Scan
     Output: (13), ft2.c1
!    Relations: (public.ft2) LEFT JOIN (public.ft1)
!    Remote SQL: SELECT r2."C 1", ss1.c1 FROM ("S 1"."T 1" r2 LEFT JOIN (SELECT 13 FROM "S 1"."T 1" WHERE (("C 1" = 13))) ss1(c1) ON (((13 = r2."C 1")))) WHERE ((r2."C 1" >= 10)) AND ((r2."C 1" <= 15))
! (4 rows)
  
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
   a  | c1 
***************
*** 2082,2105 **** SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 O
      | 15
  (6 rows)
  
! -- ok to push {ft1, ft2} but not {ft1, ft2, ft4}
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
!                                                                                           QUERY PLAN                                                                                          
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  Nested Loop Left Join
     Output: ft4.c1, (13), ft1.c1, ft2.c1
!    Join Filter: (ft4.c1 = ft1.c1)
!    ->  Foreign Scan on public.ft4
!          Output: ft4.c1, ft4.c2, ft4.c3
!          Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15))
!    ->  Materialize
!          Output: ft1.c1, ft2.c1, (13)
!          ->  Foreign Scan
!                Output: ft1.c1, ft2.c1, 13
!                Relations: (public.ft1) INNER JOIN (public.ft2)
!                Remote SQL: SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (TRUE)) WHERE ((r5."C 1" = 12)) AND ((r4."C 1" = 12)) ORDER BY r4."C 1" ASC NULLS LAST
! (12 rows)
  
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
   c1 | a  | b  | c  
--- 2073,2113 ----
      | 15
  (6 rows)
  
! EXPLAIN (VERBOSE, COSTS OFF)
! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15;
!                                                                                                                                                                                  QUERY PLAN                                                                                                                                                                                  
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  Foreign Scan
!    Output: ft2.c1, (13), (13), ft2_1.c1
!    Relations: (public.ft2) LEFT JOIN ((public.ft2) LEFT JOIN (public.ft1))
!    Remote SQL: SELECT r1."C 1", ss2.c1, ss2.c2, ss2.c3 FROM ("S 1"."T 1" r1 LEFT JOIN (SELECT r5."C 1", 13, ss1.c1 FROM ("S 1"."T 1" r5 LEFT JOIN (SELECT 13 FROM "S 1"."T 1" WHERE (("C 1" = 13))) ss1(c1) ON (((13 = r5."C 1")))) WHERE ((r5."C 1" >= 10)) AND ((r5."C 1" <= 15))) ss2(c1, c2, c3) ON (((r1."C 1" = 13)))) WHERE ((r1."C 1" >= 10)) AND ((r1."C 1" <= 15))
! (4 rows)
! 
! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15;
!  c1 | a2 | b2 | c2 
! ----+----+----+----
!  10 |    |    |   
!  11 |    |    |   
!  12 |    |    |   
!  13 | 13 |    | 10
!  13 | 13 |    | 11
!  13 | 13 |    | 12
!  13 | 13 | 13 | 13
!  13 | 13 |    | 14
!  13 | 13 |    | 15
!  14 |    |    |   
!  15 |    |    |   
! (11 rows)
! 
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
!                                                                                                                                                   QUERY PLAN                                                                                                                                                  
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  Foreign Scan
     Output: ft4.c1, (13), ft1.c1, ft2.c1
!    Relations: (public.ft4) LEFT JOIN ((public.ft1) INNER JOIN (public.ft2))
!    Remote SQL: SELECT r1.c1, ss1.c1, ss1.c2, ss1.c3 FROM ("S 1"."T 3" r1 LEFT JOIN (SELECT r4."C 1", r5."C 1", 13 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (TRUE)) WHERE ((r5."C 1" = 12)) AND ((r4."C 1" = 12))) ss1(c1, c2, c3) ON (((r1.c1 = ss1.c1)))) WHERE ((r1.c1 >= 10)) AND ((r1.c1 <= 15))
! (4 rows)
  
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
   c1 | a  | b  | c  
***************
*** 2109,2124 **** SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
   14 |    |    |   
  (3 rows)
  
  -- join with nullable side with some columns with null values
  UPDATE ft5 SET c3 = null where c1 % 9 = 0;
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
!                                                                                                                                       QUERY PLAN                                                                                                                                      
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Foreign Scan
     Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
     Relations: (public.ft5) INNER JOIN (public.ft4)
!    Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (TRUE)) WHERE ((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) ORDER BY r1.c1 ASC NULLS LAST
  (4 rows)
  
  SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
--- 2117,2150 ----
   14 |    |    |   
  (3 rows)
  
+ EXPLAIN (VERBOSE, COSTS OFF)
+ SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15;
+                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                      
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Foreign Scan
+    Output: ft4.c1, ((ft1.c1 IS NOT NULL)), ft4_1.c1, (13), ft1.c1, ft2.c1
+    Relations: (public.ft4) LEFT JOIN ((public.ft4) LEFT JOIN ((public.ft1) INNER JOIN (public.ft2)))
+    Remote SQL: SELECT r1.c1, ss2.c1, ss2.c2, ss2.c3, ss2.c4, ss2.c5 FROM ("S 1"."T 3" r1 LEFT JOIN (SELECT r4.c1, ss1.c1, ss1.c2, (ss1.c1 IS NOT NULL), ss1.c3 FROM ("S 1"."T 3" r4 LEFT JOIN (SELECT r7."C 1", r8."C 1", 13 FROM ("S 1"."T 1" r7 INNER JOIN "S 1"."T 1" r8 ON (TRUE)) WHERE ((r8."C 1" = 12)) AND ((r7."C 1" = 12))) ss1(c1, c2, c3) ON (((r4.c1 = ss1.c1)))) WHERE ((r4.c1 >= 10)) AND ((r4.c1 <= 15))) ss2(c1, c2, c3, c4, c5) ON (((r1.c1 = ss2.c1)))) WHERE ((r1.c1 >= 10)) AND ((r1.c1 <= 15))
+ (4 rows)
+ 
+ SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15;
+  c1 | a2 | b2 | c2 | d2 | e2 
+ ----+----+----+----+----+----
+  10 | f  | 10 |    |    |   
+  12 | t  | 12 | 13 | 12 | 12
+  14 | f  | 14 |    |    |   
+ (3 rows)
+ 
  -- join with nullable side with some columns with null values
  UPDATE ft5 SET c3 = null where c1 % 9 = 0;
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
!                                                                                                                                        QUERY PLAN                                                                                                                                       
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Foreign Scan
     Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
     Relations: (public.ft5) INNER JOIN (public.ft4)
!    Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss1.c4, r2.c1, r2.c2 FROM ((SELECT ROW(c1, c2, c3), c1, c2, c3 FROM "S 1"."T 4") ss1(c1, c2, c3, c4) INNER JOIN "S 1"."T 3" r2 ON (TRUE)) WHERE ((ss1.c2 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) ORDER BY ss1.c2 ASC NULLS LAST
  (4 rows)
  
  SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
***************
*** 2907,2920 **** 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, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END 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)
--- 2933,2946 ----
  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)
***************
*** 3050,3063 **** 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
!                                                                                                                                     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, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END 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)
--- 3076,3089 ----
  
  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)
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 27,32 ****
--- 27,33 ----
  #include "optimizer/cost.h"
  #include "optimizer/pathnode.h"
  #include "optimizer/paths.h"
+ #include "optimizer/placeholder.h"
  #include "optimizer/planmain.h"
  #include "optimizer/restrictinfo.h"
  #include "optimizer/var.h"
***************
*** 403,409 **** static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
  static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
  								 RelOptInfo *rel);
  static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
- static bool reltarget_has_non_vars(RelOptInfo *foreignrel);
  static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
  								Path *epq_path);
  
--- 404,409 ----
***************
*** 641,646 **** postgresGetForeignRelSize(PlannerInfo *root,
--- 641,707 ----
  	}
  
  	/*
+ 	 * Detect whether the reltarget expressions are all shippable and whether
+ 	 * there are any non-Var expressions in the reltarget.
+ 	 *
+ 	 * Note: if the relation is an appendrel child, it isn't in the main join
+ 	 * tree, so the info is not used.
+ 	 */
+ 	if (baserel->reloptkind == RELOPT_BASEREL)
+ 	{
+ 		bool		reltarget_is_shippable = true;
+ 		bool		reltarget_has_non_vars = false;
+ 		int			i;
+ 
+ 		foreach(lc, baserel->reltarget->exprs)
+ 		{
+ 			Node	   *node = (Node *) lfirst(lc);
+ 
+ 			Assert(IsA(node, Var) || IsA(node, PlaceHolderVar));
+ 
+ 			if (IsA(node, PlaceHolderVar))
+ 			{
+ 				reltarget_has_non_vars = true;
+ 				if (!is_foreign_expr(root, baserel, (Expr *) node))
+ 				{
+ 					reltarget_is_shippable = false;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * Treat whole-row references and system columns other than citd and
+ 		 * oid like PHVs.
+ 		 *
+ 		 * Note: we deparse whole-row references as ROW() and system columns
+ 		 * as 0, except for tableoid, which is deparsed as a valid value for
+ 		 * the local table OID, in deparseExplicitTargetList.
+ 		 *
+ 		 * Note: any Vars including those are considered shippable.
+ 		 */
+ 		if (reltarget_is_shippable)
+ 		{
+ 			for (i = FirstLowInvalidHeapAttributeNumber + 1; i <= 0; i++)
+ 			{
+ 				if (i == SelfItemPointerAttributeNumber ||
+ 					i == ObjectIdAttributeNumber)
+ 					continue;
+ 
+ 				if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
+ 								  fpinfo->attrs_used))
+ 				{
+ 					reltarget_has_non_vars = true;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 
+ 		fpinfo->reltarget_is_shippable = reltarget_is_shippable;
+ 		fpinfo->reltarget_has_non_vars = reltarget_has_non_vars;
+ 	}
+ 
+ 	/*
  	 * Set the name of relation in fpinfo, while we are constructing it here.
  	 * It will be used to build the string describing the join relation in
  	 * EXPLAIN output. We can't know whether VERBOSE option is specified or
***************
*** 1178,1184 **** postgresGetForeignPlan(PlannerInfo *root,
  		local_exprs = fpinfo->local_conds;
  
  		/* Build the list of columns to be fetched from the foreign server. */
! 		fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
  
  		/*
  		 * Ensure that the outer plan produces a tuple whose descriptor
--- 1239,1245 ----
  		local_exprs = fpinfo->local_conds;
  
  		/* Build the list of columns to be fetched from the foreign server. */
! 		fdw_scan_tlist = build_tlist_to_deparse(root, foreignrel);
  
  		/*
  		 * Ensure that the outer plan produces a tuple whose descriptor
***************
*** 2507,2513 **** estimate_path_cost_size(PlannerInfo *root,
  
  		/* Build the list of columns to be fetched from the foreign server. */
  		if (foreignrel->reloptkind == RELOPT_JOINREL)
! 			fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
  		else
  			fdw_scan_tlist = NIL;
  
--- 2568,2574 ----
  
  		/* Build the list of columns to be fetched from the foreign server. */
  		if (foreignrel->reloptkind == RELOPT_JOINREL)
! 			fdw_scan_tlist = build_tlist_to_deparse(root, foreignrel);
  		else
  			fdw_scan_tlist = NIL;
  
***************
*** 3948,3953 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 4009,4016 ----
  	ListCell   *lc;
  	List	   *joinclauses;
  	List	   *otherclauses;
+ 	bool		reltarget_is_shippable = true;
+ 	bool		reltarget_has_non_vars = false;
  
  	/*
  	 * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins.
***************
*** 3977,3982 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 4040,4053 ----
  	if (fpinfo_o->local_conds || fpinfo_i->local_conds)
  		return false;
  
+ 	/*
+ 	 * If the reltargets of joining relations are not shippable, those are
+ 	 * required to be evaluated locally, so the join can not be pushed down.
+ 	 */
+ 	if (!fpinfo_o->reltarget_is_shippable ||
+ 		!fpinfo_i->reltarget_is_shippable)
+ 		return false;
+ 
  	/* Separate restrict list into join quals and quals on join relation */
  	if (IS_OUTER_JOIN(jointype))
  		extract_actual_join_clauses(extra->restrictlist, &joinclauses, &otherclauses);
***************
*** 4002,4027 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  			return false;
  	}
  
- 	/*
- 	 * deparseExplicitTargetList() isn't smart enough to handle anything other
- 	 * than a Var.  In particular, if there's some PlaceHolderVar that would
- 	 * need to be evaluated within this join tree (because there's an upper
- 	 * reference to a quantity that may go to NULL as a result of an outer
- 	 * join), then we can't try to push the join down because we'll fail when
- 	 * we get to deparseExplicitTargetList().  However, a PlaceHolderVar that
- 	 * needs to be evaluated *at the top* of this join tree is OK, because we
- 	 * can do that locally after fetching the results from the remote side.
- 	 */
- 	foreach(lc, root->placeholder_list)
- 	{
- 		PlaceHolderInfo *phinfo = lfirst(lc);
- 		Relids		relids = joinrel->relids;
- 
- 		if (bms_is_subset(phinfo->ph_eval_at, relids) &&
- 			bms_nonempty_difference(relids, phinfo->ph_eval_at))
- 			return false;
- 	}
- 
  	/* Save the join clauses, for later use. */
  	fpinfo->joinclauses = joinclauses;
  
--- 4073,4078 ----
***************
*** 4069,4099 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  	switch (jointype)
  	{
  		case JOIN_INNER:
! 			fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_i->remote_conds));
! 			fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_o->remote_conds));
  			break;
  
  		case JOIN_LEFT:
! 			fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
  										  list_copy(fpinfo_i->remote_conds));
! 			fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_o->remote_conds));
  			break;
  
  		case JOIN_RIGHT:
! 			fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
  										  list_copy(fpinfo_o->remote_conds));
! 			fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_i->remote_conds));
  			break;
  
  		case JOIN_FULL:
! 			if (fpinfo_o->remote_conds && reltarget_has_non_vars(outerrel))
! 				return false;
! 			if (fpinfo_i->remote_conds && reltarget_has_non_vars(innerrel))
! 				return false;
  			break;
  
  		default:
--- 4120,4153 ----
  	switch (jointype)
  	{
  		case JOIN_INNER:
! 			if (!fpinfo_i->reltarget_has_non_vars)
! 				fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_i->remote_conds));
! 			if (!fpinfo_o->reltarget_has_non_vars)
! 				fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_o->remote_conds));
  			break;
  
  		case JOIN_LEFT:
! 			if (!fpinfo_i->reltarget_has_non_vars)
! 				fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
  										  list_copy(fpinfo_i->remote_conds));
! 			if (!fpinfo_o->reltarget_has_non_vars)
! 				fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_o->remote_conds));
  			break;
  
  		case JOIN_RIGHT:
! 			if (!fpinfo_o->reltarget_has_non_vars)
! 				fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
  										  list_copy(fpinfo_o->remote_conds));
! 			if (!fpinfo_i->reltarget_has_non_vars)
! 				fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
  										  list_copy(fpinfo_i->remote_conds));
  			break;
  
  		case JOIN_FULL:
! 			/* can't do anything here */
  			break;
  
  		default:
***************
*** 4105,4110 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 4159,4199 ----
  	fpinfo->pushdown_safe = true;
  
  	/*
+ 	 * Detect whether the reltarget expressions are all shippable and whether
+ 	 * there are any non-Var expressions in the reltarget.
+ 	 *
+ 	 * Note: we don't need to consider whole-row references or system columns
+ 	 * other than ctid and oid here, since those are to be deparsed in leaf
+ 	 * subqueries.
+ 	 */
+ 	foreach(lc, joinrel->reltarget->exprs)
+ 	{
+ 		Node	   *node = (Node *) lfirst(lc);
+ 
+ 		Assert(IsA(node, Var) || IsA(node, PlaceHolderVar));
+ 
+ 		if (IsA(node, PlaceHolderVar))
+ 		{
+ 			PlaceHolderVar *phv = (PlaceHolderVar *) node;
+ 			PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, false);
+ 
+ 			/* Ignore the PHV if it has bubbled up from an either input. */
+ 			if (bms_is_subset(phinfo->ph_eval_at, outerrel->relids) ||
+ 				bms_is_subset(phinfo->ph_eval_at, innerrel->relids))
+ 				continue;
+ 
+ 			reltarget_has_non_vars = true;
+ 			if (!is_foreign_expr(root, joinrel, (Expr *) node))
+ 			{
+ 				reltarget_is_shippable = false;
+ 				break;
+ 			}
+ 		}
+ 	}
+ 	fpinfo->reltarget_is_shippable = reltarget_is_shippable;
+ 	fpinfo->reltarget_has_non_vars = reltarget_has_non_vars;
+ 
+ 	/*
  	 * If user is willing to estimate cost for a scan of either of the joining
  	 * relations using EXPLAIN, he intends to estimate scans on that relation
  	 * more accurately. Then, it makes sense to estimate the cost the join
***************
*** 4165,4196 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  	return true;
  }
  
- /*
-  * Detect whether there are whole-row Vars or system columns other than ctid
-  * and oid in the given relation's reltarget.
-  *
-  * Note: currently, deparseExplicitTargetList can't properly handle anything
-  * other than a Var that is a ctid, oid, or user column, when deparsing the
-  * SELECT list of a subquery for a full join on relations with restrictions.
-  */
- static bool
- reltarget_has_non_vars(RelOptInfo *foreignrel)
- {
- 	ListCell   *lc;
- 
- 	foreach(lc, foreignrel->reltarget->exprs)
- 	{
- 		Var		   *var = (Var *) lfirst(lc);
- 
- 		Assert(IsA(var, Var));
- 		if (var->varattno <= 0 &&
- 			var->varattno != SelfItemPointerAttributeNumber &&
- 			var->varattno != ObjectIdAttributeNumber)
- 			return true;
- 	}
- 	return false;
- }
- 
  static void
  add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
  								Path *epq_path)
--- 4254,4259 ----
***************
*** 4541,4546 **** conversion_error_callback(void *arg)
--- 4604,4610 ----
  	const char *attname = NULL;
  	const char *relname = NULL;
  	bool		is_wholerow = false;
+ 	bool		is_placeholder = false;
  	ConversionLocation *errpos = (ConversionLocation *) arg;
  
  	if (errpos->rel)
***************
*** 4565,4595 **** conversion_error_callback(void *arg)
  		EState	   *estate = fsstate->ss.ps.state;
  		TargetEntry *tle;
  		Var		   *var;
- 		RangeTblEntry *rte;
  
  		Assert(IsA(fsplan, ForeignScan));
  		tle = (TargetEntry *) list_nth(fsplan->fdw_scan_tlist,
  									   errpos->cur_attno - 1);
  		Assert(IsA(tle, TargetEntry));
  		var = (Var *) tle->expr;
- 		Assert(IsA(var, Var));
- 
- 		rte = rt_fetch(var->varno, estate->es_range_table);
  
! 		if (var->varattno == 0)
! 			is_wholerow = true;
  		else
! 			attname = get_relid_attribute_name(rte->relid, var->varattno);
  
! 		relname = get_rel_name(rte->relid);
  	}
  
! 	if (relname)
  	{
  		if (is_wholerow)
! 			errcontext("whole-row reference to foreign table \"%s\"", relname);
  		else if (attname)
! 			errcontext("column \"%s\" of foreign table \"%s\"", attname, relname);
  	}
  }
  
--- 4629,4670 ----
  		EState	   *estate = fsstate->ss.ps.state;
  		TargetEntry *tle;
  		Var		   *var;
  
  		Assert(IsA(fsplan, ForeignScan));
  		tle = (TargetEntry *) list_nth(fsplan->fdw_scan_tlist,
  									   errpos->cur_attno - 1);
  		Assert(IsA(tle, TargetEntry));
  		var = (Var *) tle->expr;
  
! 		if (!IsA(var, Var))
! 		{
! 			Assert(IsA(var, PlaceHolderVar));
! 			is_placeholder = true;
! 		}
  		else
! 		{
! 			RangeTblEntry *rte  = rt_fetch(var->varno, estate->es_range_table);
! 
! 			if (var->varattno == 0)
! 				is_wholerow = true;
! 			else
! 				attname = get_relid_attribute_name(rte->relid, var->varattno);
  
! 			relname = get_rel_name(rte->relid);
! 		}
  	}
  
! 	if (is_placeholder)
! 		errcontext("expression at position %d in select list",
! 				   errpos->cur_attno);
! 	else if (relname)
  	{
  		if (is_wholerow)
! 			errcontext("whole-row reference to 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
***************
*** 92,97 **** typedef struct PgFdwRelationInfo
--- 92,106 ----
  	RelOptInfo *innerrel;
  	JoinType	jointype;
  	List	   *joinclauses;
+ 
+ 	/*
+ 	 * Flags on Path output tlist for the relation.  These are used to detect
+ 	 * whether the relation can be joined with any other foreign table (or
+ 	 * join) on the remote server, and detect whether the relation has to be
+ 	 * deparsed as a subquery in that case.
+ 	 */
+ 	bool		reltarget_is_shippable; /* are reltarget exprs shippable? */
+ 	bool		reltarget_has_non_vars;	/* are there any non-Var exprs? */
  } PgFdwRelationInfo;
  
  /* in postgres_fdw.c */
***************
*** 155,161 **** extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
  				  List **retrieved_attrs);
  extern void deparseStringLiteral(StringInfo buf, const char *val);
  extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
! extern List *build_tlist_to_deparse(RelOptInfo *foreign_rel);
  extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
  						RelOptInfo *foreignrel, List *tlist,
  						List *remote_conds, List *pathkeys,
--- 164,170 ----
  				  List **retrieved_attrs);
  extern void deparseStringLiteral(StringInfo buf, const char *val);
  extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
! extern List *build_tlist_to_deparse(PlannerInfo *root, RelOptInfo *foreignrel);
  extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
  						RelOptInfo *foreignrel, List *tlist,
  						List *remote_conds, List *pathkeys,
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 493,509 **** EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
  SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
  
! -- non-Var items in targelist of the nullable rel of a join preventing
! -- push-down in some cases
! -- unable to push {ft1, ft2}
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
! 
! -- ok to push {ft1, ft2} but not {ft1, ft2, ft4}
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
  
  -- join with nullable side with some columns with null values
  UPDATE ft5 SET c3 = null where c1 % 9 = 0;
--- 493,511 ----
  SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
  SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
  
! -- check join pushdown in situations where PHVs are involved
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
  SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
! EXPLAIN (VERBOSE, COSTS OFF)
! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15;
! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15;
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
  SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
+ EXPLAIN (VERBOSE, COSTS OFF)
+ SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15;
+ SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15;
  
  -- join with nullable side with some columns with null values
  UPDATE ft5 SET c3 = null where c1 % 9 = 0;
