On 2016/09/26 16:30, Etsuro Fujita wrote:
On 2016/09/13 14:17, Ashutosh Bapat wrote:

It won't remain minimal as the number of paths created increases,
increasing the number of times a query is deparsed. We deparse query
every time time we cost a path for a relation with use_remote_estimates
true. As we try to push down more and more stuff, we will create more
paths and deparse the query more time.

Also, that makes the interface better. Right now, in your patch, you
have changed the order of deparsing in the existing code, so that the
aliases are registered while deparsing FROM clause and before any Var
nodes are deparsed. If we create aliases at the time of path creation,
only once in GetForeignJoinPaths or GetForeignPaths as appropriate, that
would require less code churn and would save some CPU cycles as well.

Agreed.  Will fix.

Done.  Attached is an updated version of the patch.

I didn't create aliases at anytime. Instead, I added a logic to get info about the alias to a given expression from reltarget->exprs for relations in a given join tree. See isSubqueryExpr and getSubselectAliasInfo.

As proposed by you, the patch differentiates between a base relation alias and a subquery alias by using different prefixes "r" and "s", respectively. Also, subquery aliases are indexed by RTI for baserels and the position in join_rel_list + the length of rtable for joinrels, as proposed upthread.

Best regards,
Etsuro Fujita
*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 102,107 **** typedef struct deparse_expr_cxt
--- 102,109 ----
  /* Handy macro to add relation name qualification */
  #define ADD_REL_QUALIFIER(buf, varno)	\
  		appendStringInfo((buf), "%s%d.", REL_ALIAS_PREFIX, (varno))
+ #define SS_TAB_ALIAS_PREFIX	"s"
+ #define SS_COL_ALIAS_PREFIX	"c"
  
  /*
   * Functions to determine whether an expression can be evaluated safely on
***************
*** 152,164 **** static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod,
  				 deparse_expr_cxt *context);
  static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
  					   deparse_expr_cxt *context);
! static void deparseSelectSql(List *tlist, List **retrieved_attrs,
  				 deparse_expr_cxt *context);
  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);
  
  
  /*
--- 154,175 ----
  				 deparse_expr_cxt *context);
  static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
  					   deparse_expr_cxt *context);
! static void deparseSelectSql(List *tlist,
! 				 List *remote_conds,
! 				 List **retrieved_attrs,
  				 deparse_expr_cxt *context);
  static void deparseLockingClause(deparse_expr_cxt *context);
  static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context);
  static void appendConditions(List *exprs, deparse_expr_cxt *context);
  static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
! 					  RelOptInfo *foreignrel, bool add_rel_alias,
! 					  List **params_list);
! static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
! 				   bool is_subquery, List **params_list);
! static void appendSubselectAlias(deparse_expr_cxt *context);
! static void getSubselectAliasInfo(Expr *node, int *tabno, int *colno,
! 					  RelOptInfo *foreignrel);
! static bool isSubqueryExpr(Expr *node, int *tabno, int *colno, RelOptInfo *foreignrel);
  
  
  /*
***************
*** 780,796 **** deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
  	context.foreignrel = rel;
  	context.params_list = params_list;
  
! 	/* Construct SELECT clause and FROM clause */
! 	deparseSelectSql(tlist, retrieved_attrs, &context);
! 
! 	/*
! 	 * Construct WHERE clause
! 	 */
! 	if (remote_conds)
! 	{
! 		appendStringInfo(buf, " WHERE ");
! 		appendConditions(remote_conds, &context);
! 	}
  
  	/* Add ORDER BY clause if we found any useful pathkeys */
  	if (pathkeys)
--- 791,798 ----
  	context.foreignrel = rel;
  	context.params_list = params_list;
  
! 	/* Construct SELECT clause, FROM clause, and WHERE clause */
! 	deparseSelectSql(tlist, remote_conds, retrieved_attrs, &context);
  
  	/* Add ORDER BY clause if we found any useful pathkeys */
  	if (pathkeys)
***************
*** 803,809 **** deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
  /*
   * Construct a simple SELECT statement that retrieves desired columns
   * of the specified foreign table, and append it to "buf".  The output
!  * contains just "SELECT ... FROM ....".
   *
   * We also create an integer List of the columns being retrieved, which is
   * returned to *retrieved_attrs.
--- 805,811 ----
  /*
   * Construct a simple SELECT statement that retrieves desired columns
   * of the specified foreign table, and append it to "buf".  The output
!  * contains just "SELECT ... FROM ... WHERE ...".
   *
   * We also create an integer List of the columns being retrieved, which is
   * returned to *retrieved_attrs.
***************
*** 812,818 **** deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
   * deparseSelectStmtForRel() for details.
   */
  static void
! deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
  {
  	StringInfo	buf = context->buf;
  	RelOptInfo *foreignrel = context->foreignrel;
--- 814,823 ----
   * deparseSelectStmtForRel() for details.
   */
  static void
! deparseSelectSql(List *tlist,
! 				 List *remote_conds,
! 				 List **retrieved_attrs,
! 				 deparse_expr_cxt *context)
  {
  	StringInfo	buf = context->buf;
  	RelOptInfo *foreignrel = context->foreignrel;
***************
*** 824,832 **** deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
  	 */
  	appendStringInfoString(buf, "SELECT ");
  
! 	if (foreignrel->reloptkind == RELOPT_JOINREL)
  	{
- 		/* For a join relation use the input tlist */
  		deparseExplicitTargetList(tlist, retrieved_attrs, context);
  	}
  	else
--- 829,842 ----
  	 */
  	appendStringInfoString(buf, "SELECT ");
  
! 	/*
! 	 * Note: tlist for a base relation may be non-NIL.  For example, if the
! 	 * base relation is an operand of a foreign join performing a full outer
! 	 * join and has non-NIL remote_conds, then we deparse the base relation
! 	 * as a subquery, so tlist for the base relation would be non-NIL.
! 	 */
! 	if (tlist != NIL)
  	{
  		deparseExplicitTargetList(tlist, retrieved_attrs, context);
  	}
  	else
***************
*** 843,848 **** deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
--- 853,860 ----
  		 */
  		Relation	rel = heap_open(rte->relid, NoLock);
  
+ 		Assert(foreignrel->reloptkind != RELOPT_JOINREL);
+ 
  		deparseTargetList(buf, root, foreignrel->relid, rel, false,
  						  fpinfo->attrs_used, false, retrieved_attrs);
  		heap_close(rel, NoLock);
***************
*** 855,860 **** deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context)
--- 867,881 ----
  	deparseFromExprForRel(buf, root, foreignrel,
  						  (foreignrel->reloptkind == RELOPT_JOINREL),
  						  context->params_list);
+ 
+ 	/*
+ 	 * Construct WHERE clause
+ 	 */
+ 	if (remote_conds)
+ 	{
+ 		appendStringInfoString(buf, " WHERE ");
+ 		appendConditions(remote_conds, context);
+ 	}
  }
  
  /*
***************
*** 965,975 **** deparseLockingClause(deparse_expr_cxt *context)
--- 986,1005 ----
  	StringInfo	buf = context->buf;
  	PlannerInfo *root = context->root;
  	RelOptInfo *rel = context->foreignrel;
+ 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
  	int			relid = -1;
  
  	while ((relid = bms_next_member(rel->relids, relid)) >= 0)
  	{
  		/*
+ 		 * Ignore relation if it's contained in a lower subquery because in
+ 		 * that case we've already considered locking for the relation when
+ 		 * deparsing the subquery.
+ 		 */
+ 		if (bms_is_member(relid, fpinfo->subqueried_relids))
+ 			continue;
+ 
+ 		/*
  		 * Add FOR UPDATE/SHARE if appropriate.  We apply locking during the
  		 * initial row fetch, rather than later on as is done for local
  		 * tables. The extra roundtrips involved in trying to duplicate the
***************
*** 1138,1150 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
  
  		if (i > 0)
  			appendStringInfoString(buf, ", ");
! 		deparseVar(var, context);
  
  		*retrieved_attrs = lappend_int(*retrieved_attrs, i + 1);
  
  		i++;
  	}
  
  	if (i == 0)
  		appendStringInfoString(buf, "NULL");
  }
--- 1168,1181 ----
  
  		if (i > 0)
  			appendStringInfoString(buf, ", ");
! 		deparseExpr((Expr *) var, context);
  
  		*retrieved_attrs = lappend_int(*retrieved_attrs, i + 1);
  
  		i++;
  	}
  
+ 	/* Don't generate bad syntax if no columns */
  	if (i == 0)
  		appendStringInfoString(buf, "NULL");
  }
***************
*** 1152,1191 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
  /*
   * Construct FROM clause for given relation
   *
!  * 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;
  
  	if (foreignrel->reloptkind == RELOPT_JOINREL)
  	{
! 		RelOptInfo *rel_o = fpinfo->outerrel;
! 		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)
  		{
  			deparse_expr_cxt context;
--- 1183,1223 ----
  /*
   * Construct FROM clause for given relation
   *
!  * For a join relation the clause of the following form is appended to buf:
!  * ((outer relation) <join type> (inner relation) ON (joinclauses))
!  * For a base relation the function just adds the schema-qualified tablename,
!  * with the appropriate alias if so requested.
   */
  static void
! deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
! 					  RelOptInfo *foreignrel, bool add_rel_alias,
! 					  List **params_list)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  
  	if (foreignrel->reloptkind == RELOPT_JOINREL)
  	{
! 		/* Begin the FROM clause entry */
! 		appendStringInfoChar(buf, '(');
  
  		/* Deparse outer relation */
! 		deparseRangeTblRef(buf, root,
! 						   fpinfo->outerrel,
! 						   fpinfo->outer_is_subquery,
! 						   params_list);
  
! 		/* Append join type */
! 		appendStringInfo(buf, " %s JOIN ",
! 						 get_jointype_name(fpinfo->jointype));
  
! 		/* Deparse inner relation */
! 		deparseRangeTblRef(buf, root,
! 						   fpinfo->innerrel,
! 						   fpinfo->inner_is_subquery,
! 						   params_list);
  
! 		/* Append join conditions */
! 		appendStringInfoString(buf, " ON ");
  		if (fpinfo->joinclauses)
  		{
  			deparse_expr_cxt context;
***************
*** 1195,1209 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
  			context.root = root;
  			context.params_list = params_list;
  
! 			appendStringInfo(buf, "(");
  			appendConditions(fpinfo->joinclauses, &context);
! 			appendStringInfo(buf, ")");
  		}
  		else
  			appendStringInfoString(buf, "(TRUE)");
  
! 		/* End the FROM clause entry. */
! 		appendStringInfo(buf, ")");
  	}
  	else
  	{
--- 1227,1244 ----
  			context.root = root;
  			context.params_list = params_list;
  
! 			appendStringInfoChar(buf, '(');
  			appendConditions(fpinfo->joinclauses, &context);
! 			appendStringInfoChar(buf, ')');
  		}
  		else
+ 		{
+ 			/* No join conditions; add "(TRUE)" */
  			appendStringInfoString(buf, "(TRUE)");
+ 		}
  
! 		/* End the FROM clause entry */
! 		appendStringInfoChar(buf, ')');
  	}
  	else
  	{
***************
*** 1222,1228 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
  		 * pulled up subqueries in the query being built for a pushed down
  		 * join.
  		 */
! 		if (use_alias)
  			appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid);
  
  		heap_close(rel, NoLock);
--- 1257,1263 ----
  		 * pulled up subqueries in the query being built for a pushed down
  		 * join.
  		 */
! 		if (add_rel_alias)
  			appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid);
  
  		heap_close(rel, NoLock);
***************
*** 1230,1235 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
--- 1265,1412 ----
  }
  
  /*
+  * Append operand relation of foreign join to buf.
+  */
+ static void
+ deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
+ 				   bool is_subquery, List **params_list)
+ {
+ 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
+ 
+ 	Assert(fpinfo->local_conds == NIL);
+ 
+ 	if (is_subquery)
+ 	{
+ 		List	   *tlist;
+ 		List	   *retrieved_attrs;
+ 		deparse_expr_cxt context;
+ 
+ 		context.buf = buf;
+ 		context.root = root;
+ 		context.foreignrel = foreignrel;
+ 		context.params_list = params_list;
+ 
+ 		tlist = build_tlist_to_deparse(foreignrel);
+ 		appendStringInfoChar(buf, '(');
+ 		deparseSelectSql(tlist,
+ 						 fpinfo->remote_conds,
+ 						 &retrieved_attrs,
+ 						 &context);
+ 		deparseLockingClause(&context);
+ 		appendStringInfoChar(buf, ')');
+ 		appendSubselectAlias(&context);
+ 	}
+ 	else
+ 		deparseFromExprForRel(buf, root, foreignrel, true, params_list);
+ }
+ 
+ /*
+  * Add a subselect alias to a subquery-in-FROM.
+  */
+ static void
+ appendSubselectAlias(deparse_expr_cxt *context)
+ {
+ 	StringInfo	buf = context->buf;
+ 	RelOptInfo *foreignrel = context->foreignrel;
+ 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
+ 	int			num_columns = list_length(foreignrel->reltarget->exprs);
+ 	int			i;
+ 
+ 	/* Append the table alias */
+ 	appendStringInfo(buf, " %s%d", SS_TAB_ALIAS_PREFIX, fpinfo->relation_index);
+ 
+ 	/* Append the column aliases */
+ 	appendStringInfoChar(buf, '(');
+ 	for (i = 1; i <= num_columns; i++)
+ 	{
+ 		if (i > 1)
+ 			appendStringInfoString(buf, ", ");
+ 
+ 		appendStringInfo(buf, "%s%d", SS_COL_ALIAS_PREFIX, i);
+ 	}
+ 	appendStringInfoChar(buf, ')');
+ }
+ 
+ /*
+  * Get info about the subselect alias to given expression.
+  *
+  * The subselect table and column numbers are returned to *tabno and *colno,
+  * respectively.
+  */
+ static void
+ getSubselectAliasInfo(Expr *node, int *tabno, int *colno,
+ 					  RelOptInfo *foreignrel)
+ {
+ 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
+ 	int			i;
+ 	ListCell   *lc;
+ 
+ 	/* Get the table number */
+ 	*tabno = fpinfo->relation_index;
+ 
+ 	/* Get the column number */
+ 	i = 1;
+ 	foreach(lc, foreignrel->reltarget->exprs)
+ 	{
+ 		if (equal(lfirst(lc), (Node *) node))
+ 		{
+ 			*colno = i;
+ 			return;
+ 		}
+ 		i++;
+ 	}
+ 	/* shouldn't get here */
+ 	elog(ERROR, "found expression not expected in subquery output");
+ }
+ 
+ /*
+  * Returns true if given expression is an output column of a subquery-in-FROM.
+  *
+  * The subselect table and column numbers are returned to *tabno and *colno,
+  * respectively, in that case.
+  */
+ static bool
+ isSubqueryExpr(Expr *node, int *tabno, int *colno, RelOptInfo *foreignrel)
+ {
+ 	Var		   *var = (Var *) node;
+ 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
+ 	RelOptInfo *outerrel = fpinfo->outerrel;
+ 	RelOptInfo *innerrel = fpinfo->innerrel;
+ 
+ 	if (foreignrel->reloptkind != RELOPT_JOINREL)
+ 		return false;
+ 
+ 	if (!fpinfo->subqueried_relids)
+ 		return false;
+ 
+ 	Assert(IsA(var, Var));
+ 
+ 	if (bms_is_member(var->varno, outerrel->relids))
+ 	{
+ 		if (fpinfo->outer_is_subquery)
+ 		{
+ 			getSubselectAliasInfo(node, tabno, colno, outerrel);
+ 			return true;
+ 		}
+ 		if (isSubqueryExpr(node, tabno, colno, outerrel))
+ 			return true;
+ 	}
+ 	else
+ 	{
+ 		Assert(bms_is_member(var->varno, innerrel->relids));
+ 
+ 		if (fpinfo->inner_is_subquery)
+ 		{
+ 			getSubselectAliasInfo(node, tabno, colno, innerrel);
+ 			return true;
+ 		}
+ 		if (isSubqueryExpr(node, tabno, colno, innerrel))
+ 			return true;
+ 	}
+ 	return false;
+ }
+ 
+ /*
   * deparse remote INSERT statement
   *
   * The statement text is appended to buf, and we also create an integer List
***************
*** 1811,1816 **** deparseExpr(Expr *node, deparse_expr_cxt *context)
--- 1988,2011 ----
  	if (node == NULL)
  		return;
  
+ 	/*
+ 	 * If the given expression is an output column of a subquery-in-FROM,
+ 	 * deparse the alias to the expression instead.
+ 	 */
+ 	if (IsA(node, Var))
+ 	{
+ 		int			tabno;
+ 		int			colno;
+ 
+ 		if (isSubqueryExpr(node, &tabno, &colno, context->foreignrel))
+ 		{
+ 			appendStringInfo(context->buf, "%s%d.%s%d",
+ 							 SS_TAB_ALIAS_PREFIX, tabno,
+ 							 SS_COL_ALIAS_PREFIX, colno);
+ 			return;
+ 		}
+ 	}
+ 
  	switch (nodeTag(node))
  	{
  		case T_Var:
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 456,463 **** SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1"
  -- foreign join so that the local table can be joined using merge join strategy.
  EXPLAIN (VERBOSE, COSTS OFF)
  	SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
!                                                                        QUERY PLAN                                                                        
! ---------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1."C 1"
     ->  Merge Right Join
--- 456,463 ----
  -- foreign join so that the local table can be joined using merge join strategy.
  EXPLAIN (VERBOSE, COSTS OFF)
  	SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
!                                                                              QUERY PLAN                                                                             
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1."C 1"
     ->  Merge Right Join
***************
*** 466,472 **** EXPLAIN (VERBOSE, COSTS OFF)
           ->  Foreign Scan
                 Output: t3.c1
                 Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
!                Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST
           ->  Index Only Scan using t1_pkey on "S 1"."T 1" t1
                 Output: t1."C 1"
  (11 rows)
--- 466,472 ----
           ->  Foreign Scan
                 Output: t3.c1
                 Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
!                Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (TRUE)) WHERE ((r2."C 1" = r3."C 1")) ORDER BY r2."C 1" ASC NULLS LAST
           ->  Index Only Scan using t1_pkey on "S 1"."T 1" t1
                 Output: t1."C 1"
  (11 rows)
***************
*** 979,992 **** ANALYZE ft5;
  -- join two tables
  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;
!                                                                                         QUERY PLAN                                                                                        
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3
     ->  Foreign Scan
           Output: t1.c1, t2.c1, t1.c3
           Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!          Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST
  (6 rows)
  
  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;
--- 979,992 ----
  -- join two tables
  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;
!                                                                                              QUERY PLAN                                                                                              
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3
     ->  Foreign Scan
           Output: t1.c1, t2.c1, t1.c3
           Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!          Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" 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)
  
  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;
***************
*** 1007,1014 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
  -- join three tables
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
!                                                                                             QUERY PLAN                                                                                             
! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c2, t3.c3, t1.c3
     ->  Sort
--- 1007,1014 ----
  -- join three tables
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
!                                                                                                       QUERY PLAN                                                                                                       
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c2, t3.c3, t1.c3
     ->  Sort
***************
*** 1017,1023 **** SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t
           ->  Foreign Scan
                 Output: t1.c1, t2.c2, t3.c3, t1.c3
                 Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3)
!                Remote SQL: SELECT r1."C 1", r1.c3, r2.c2, r4.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1))))
  (9 rows)
  
  SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
--- 1017,1023 ----
           ->  Foreign Scan
                 Output: t1.c1, t2.c2, t3.c3, t1.c3
                 Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3)
!                Remote SQL: SELECT r1."C 1", r1.c3, r2.c2, r4.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) INNER JOIN "S 1"."T 3" r4 ON (TRUE)) WHERE ((r1."C 1" = r4.c1)) AND ((r1."C 1" = r2."C 1"))
  (9 rows)
  
  SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
***************
*** 1223,1245 **** SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.
  -- full outer join with restrictions on the joining relations
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1;
!                                            QUERY PLAN                                           
! ------------------------------------------------------------------------------------------------
!  Sort
     Output: ft4.c1, ft5.c1
!    Sort Key: ft4.c1, ft5.c1
!    ->  Hash Full Join
!          Output: ft4.c1, ft5.c1
!          Hash Cond: (ft4.c1 = ft5.c1)
!          ->  Foreign Scan on public.ft4
!                Output: ft4.c1, ft4.c2, ft4.c3
!                Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))
!          ->  Hash
!                Output: ft5.c1
!                ->  Foreign Scan on public.ft5
!                      Output: ft5.c1
!                      Remote SQL: SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))
! (14 rows)
  
  SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1;
   c1 | c1 
--- 1223,1235 ----
  -- full outer join with restrictions on the joining relations
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1;
!                                                                                                                                   QUERY PLAN                                                                                                                                   
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
!  Foreign Scan
     Output: ft4.c1, ft5.c1
!    Relations: (public.ft4) FULL JOIN (public.ft5)
!    Remote SQL: SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s5.c1 ASC NULLS LAST
! (4 rows)
  
  SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1;
   c1 | c1 
***************
*** 1257,1270 **** SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL
  -- full outer join + inner join
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
!                                                                                                                                            QUERY PLAN                                                                                                                                            
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t3.c1
     ->  Foreign Scan
           Output: t1.c1, t2.c1, t3.c1
           Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
!          Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST
  (6 rows)
  
  SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
--- 1247,1260 ----
  -- full outer join + inner join
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
!                                                                                                                                                                     QUERY PLAN                                                                                                                                                                    
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t3.c1
     ->  Foreign Scan
           Output: t1.c1, t2.c1, t3.c1
           Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
!          Remote SQL: SELECT s6.c1, s6.c2, r4.c1 FROM ((SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (TRUE)) WHERE ((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60))) s6(c1, c2) FULL JOIN "S 1"."T 3" r4 ON (((s6.c2 = r4.c1)))) ORDER BY s6.c1 ASC NULLS LAST, s6.c2 ASC NULLS LAST, r4.c1 ASC NULLS LAST
  (6 rows)
  
  SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
***************
*** 1282,1287 **** SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 a
--- 1272,1315 ----
      |    | 16
  (10 rows)
  
+ EXPLAIN (VERBOSE, COSTS OFF)
+ SELECT t1, t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
+                                                                                                                         QUERY PLAN                                                                                                                         
+ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Limit
+    Output: t1.*, t1.c1, t2.c1, t3.c1
+    ->  Sort
+          Output: t1.*, t1.c1, t2.c1, t3.c1
+          Sort Key: t1.c1, t2.c1, t3.c1
+          ->  Hash Full Join
+                Output: t1.*, t1.c1, t2.c1, t3.c1
+                Hash Cond: (t2.c1 = t3.c1)
+                ->  Foreign Scan
+                      Output: t1.*, t1.c1, t2.c1
+                      Relations: (public.ft4 t1) INNER JOIN (public.ft5 t2)
+                      Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r2.c1 FROM ("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (TRUE)) WHERE ((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60))
+                ->  Hash
+                      Output: t3.c1
+                      ->  Foreign Scan on public.ft4 t3
+                            Output: t3.c1
+                            Remote SQL: SELECT c1 FROM "S 1"."T 3"
+ (17 rows)
+ 
+ SELECT t1, t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
+        t1       | c1 | c1 | c1 
+ ----------------+----+----+----
+  (52,53,AAA052) | 52 | 51 |   
+  (58,59,AAA058) | 58 | 57 |   
+                 |    |    |  2
+                 |    |    |  4
+                 |    |    |  6
+                 |    |    |  8
+                 |    |    | 10
+                 |    |    | 12
+                 |    |    | 14
+                 |    |    | 16
+ (10 rows)
+ 
  -- full outer join three tables
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
***************
*** 1453,1466 **** SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT
  -- left outer join + right outer join
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
!                                                                                      QUERY PLAN                                                                                      
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c2, t3.c3
     ->  Foreign Scan
           Output: t1.c1, t2.c2, t3.c3
           Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2))
!          Remote SQL: SELECT r4.c3, r1."C 1", r2.c2 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1))))
  (6 rows)
  
  SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
--- 1481,1494 ----
  -- left outer join + right outer join
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
!                                                                                           QUERY PLAN                                                                                          
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c2, t3.c3
     ->  Foreign Scan
           Output: t1.c1, t2.c2, t3.c3
           Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2))
!          Remote SQL: SELECT r4.c3, r1."C 1", r2.c2 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ON (((r2."C 1" = r4.c1)) AND ((r1."C 1" = r2."C 1"))))
  (6 rows)
  
  SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
***************
*** 1513,1520 **** 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
--- 1541,1548 ----
  -- 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
***************
*** 1522,1528 **** 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 (((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)
--- 1550,1556 ----
           ->  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)
***************
*** 1557,1564 **** 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
--- 1585,1592 ----
  
  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
***************
*** 1566,1572 **** 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 (((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)
--- 1594,1600 ----
           ->  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)
***************
*** 1602,1609 **** 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
--- 1630,1637 ----
  -- 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
***************
*** 1611,1617 **** 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 (((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)
--- 1639,1645 ----
           ->  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)
***************
*** 1646,1653 **** 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
--- 1674,1681 ----
  
  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
***************
*** 1655,1661 **** 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 (((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)
--- 1683,1689 ----
           ->  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)
***************
*** 1691,1705 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
  -- join in CTE
  EXPLAIN (VERBOSE, COSTS OFF)
  WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10;
!                                                              QUERY PLAN                                                              
! -------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t.c1_1, t.c2_1, t.c1_3
     CTE t
       ->  Foreign Scan
             Output: t1.c1, t1.c3, t2.c1
             Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!            Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
     ->  Sort
           Output: t.c1_1, t.c2_1, t.c1_3
           Sort Key: t.c1_3, t.c1_1
--- 1719,1733 ----
  -- join in CTE
  EXPLAIN (VERBOSE, COSTS OFF)
  WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10;
!                                                                    QUERY PLAN                                                                   
! ------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t.c1_1, t.c2_1, t.c1_3
     CTE t
       ->  Foreign Scan
             Output: t1.c1, t1.c3, t2.c1
             Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!            Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
     ->  Sort
           Output: t.c1_1, t.c2_1, t.c1_3
           Sort Key: t.c1_3, t.c1_1
***************
*** 1725,1738 **** 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 (((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
--- 1753,1766 ----
  -- 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
***************
*** 1955,1962 **** SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8
  -- into one of the joining sides.
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
!                                                                       QUERY PLAN                                                                       
! -------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3
     ->  Sort
--- 1983,1990 ----
  -- into one of the joining sides.
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
!                                                                             QUERY PLAN                                                                            
! ------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, t2.c1, t1.c3
     ->  Sort
***************
*** 1966,1972 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.
                 Output: t1.c1, t2.c1, t1.c3
                 Filter: (t1.c8 = t2.c8)
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
  (10 rows)
  
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
--- 1994,2000 ----
                 Output: t1.c1, t2.c1, t1.c3
                 Filter: (t1.c8 = t2.c8)
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
  (10 rows)
  
  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
***************
*** 1987,1994 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.
  -- Aggregate after UNION, for testing setrefs
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
!                                                                      QUERY PLAN                                                                     
! ----------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, (avg((t1.c1 + t2.c1)))
     ->  Sort
--- 2015,2022 ----
  -- Aggregate after UNION, for testing setrefs
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
!                                                                           QUERY PLAN                                                                           
! ---------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1.c1, (avg((t1.c1 + t2.c1)))
     ->  Sort
***************
*** 2004,2014 **** SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2
                             ->  Foreign Scan
                                   Output: t1.c1, t2.c1
                                   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                                  Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
                             ->  Foreign Scan
                                   Output: t1_1.c1, t2_1.c1
                                   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                                  Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
  (20 rows)
  
  SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
--- 2032,2042 ----
                             ->  Foreign Scan
                                   Output: t1.c1, t2.c1
                                   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                                  Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
                             ->  Foreign Scan
                                   Output: t1_1.c1, t2_1.c1
                                   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!                                  Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1"))
  (20 rows)
  
  SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
***************
*** 2029,2036 **** SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2
  -- join with lateral reference
  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;
!                                                                              QUERY PLAN                                                                             
! --------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1."C 1"
     ->  Nested Loop
--- 2057,2064 ----
  -- join with lateral reference
  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;
!                                                                                   QUERY PLAN                                                                                   
! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
   Limit
     Output: t1."C 1"
     ->  Nested Loop
***************
*** 2043,2049 **** SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM
                 ->  Foreign Scan
                       Output: t2.c1, t3.c1
                       Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
!                      Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer))))
  (13 rows)
  
  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;
--- 2071,2077 ----
                 ->  Foreign Scan
                       Output: t2.c1, t3.c1
                       Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
!                      Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer))
  (13 rows)
  
  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;
***************
*** 2095,2102 **** SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 O
  -- 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)
--- 2123,2130 ----
  -- 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)
***************
*** 2108,2114 **** SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
           ->  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 (((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;
--- 2136,2142 ----
           ->  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;
***************
*** 2123,2134 **** SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
  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 (((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;
--- 2151,2162 ----
  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;
***************
*** 2288,2299 **** DROP ROLE regress_view_owner;
  -- simple join
  PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
  EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2);
!                                                           QUERY PLAN                                                          
! ------------------------------------------------------------------------------------------------------------------------------
   Foreign Scan
     Output: t1.c3, t2.c3
     Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!    Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = 2)) AND ((r1."C 1" = 1))))
  (4 rows)
  
  EXECUTE st1(1, 1);
--- 2316,2327 ----
  -- simple join
  PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
  EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2);
!                                                                QUERY PLAN                                                                
! -----------------------------------------------------------------------------------------------------------------------------------------
   Foreign Scan
     Output: t1.c3, t2.c3
     Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
!    Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r2."C 1" = 2)) AND ((r1."C 1" = 1))
  (4 rows)
  
  EXECUTE st1(1, 1);
***************
*** 2917,2930 **** 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 (((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)
--- 2945,2958 ----
  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)
***************
*** 3060,3073 **** 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 (((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)
--- 3088,3101 ----
  
  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)
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 403,408 **** static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
--- 403,409 ----
  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);
  
***************
*** 655,660 **** postgresGetForeignRelSize(PlannerInfo *root,
--- 656,669 ----
  	if (*refname && strcmp(refname, relname) != 0)
  		appendStringInfo(fpinfo->relation_name, " %s",
  						 quote_identifier(rte->eref->aliasname));
+ 
+ 	/* Set the subquery information */
+ 	fpinfo->outer_is_subquery = false;
+ 	fpinfo->inner_is_subquery = false;
+ 	fpinfo->subqueried_relids = NULL;
+ 
+ 	/* Set the relation index */
+ 	fpinfo->relation_index = baserel->relid;
  }
  
  /*
***************
*** 3947,3952 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 3956,3962 ----
  	ListCell   *lc;
  	List	   *joinclauses;
  	List	   *otherclauses;
+ 	Relids		relids;
  
  	/*
  	 * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins.
***************
*** 4011,4020 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  	 * 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))
--- 4021,4030 ----
  	 * 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.
  	 */
+ 	relids = joinrel->relids;
  	foreach(lc, root->placeholder_list)
  	{
  		PlaceHolderInfo *phinfo = lfirst(lc);
  
  		if (bms_is_subset(phinfo->ph_eval_at, relids) &&
  			bms_nonempty_difference(relids, phinfo->ph_eval_at))
***************
*** 4049,4056 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  	/*
  	 * Pull the other remote conditions from the joining relations into join
  	 * clauses or other remote clauses (remote_conds) of this relation
! 	 * wherever possible. This avoids building subqueries at every join step,
! 	 * which is not currently supported by the deparser logic.
  	 *
  	 * For an inner join, clauses from both the relations are added to the
  	 * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from
--- 4059,4065 ----
  	/*
  	 * Pull the other remote conditions from the joining relations into join
  	 * clauses or other remote clauses (remote_conds) of this relation
! 	 * wherever possible. This avoids building subqueries at every join step.
  	 *
  	 * For an inner join, clauses from both the relations are added to the
  	 * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from
***************
*** 4061,4068 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  	 *
  	 * For a FULL OUTER JOIN, the other clauses from either relation can not
  	 * be added to the joinclauses or remote_conds, since each relation acts
! 	 * as an outer relation for the other. Consider such full outer join as
! 	 * unshippable because of the reasons mentioned above in this comment.
  	 *
  	 * The joining sides can not have local conditions, thus no need to test
  	 * shippability of the clauses being pulled up.
--- 4070,4076 ----
  	 *
  	 * For a FULL OUTER JOIN, the other clauses from either relation can not
  	 * be added to the joinclauses or remote_conds, since each relation acts
! 	 * as an outer relation for the other.
  	 *
  	 * The joining sides can not have local conditions, thus no need to test
  	 * shippability of the clauses being pulled up.
***************
*** 4091,4097 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  			break;
  
  		case JOIN_FULL:
! 			if (fpinfo_i->remote_conds || fpinfo_o->remote_conds)
  				return false;
  			break;
  
--- 4099,4113 ----
  			break;
  
  		case JOIN_FULL:
! 			/*
! 			 * We can't do anything here, and if there are any non-Vars in the
! 			 * outerrel/innerrel's reltarget, give up pushing down this join
! 			 * because we currently don't support deparsing such a relation as
! 			 * a subquery.
! 			 */
! 			if (reltarget_has_non_vars(outerrel))
! 				return false;
! 			if (reltarget_has_non_vars(innerrel))
  				return false;
  			break;
  
***************
*** 4100,4117 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  			elog(ERROR, "unsupported join type %d", jointype);
  	}
  
- 	/*
- 	 * For an inner join, all restrictions can be treated alike. Treating the
- 	 * pushed down conditions as join conditions allows a top level full outer
- 	 * join to be deparsed without requiring subqueries.
- 	 */
- 	if (jointype == JOIN_INNER)
- 	{
- 		Assert(!fpinfo->joinclauses);
- 		fpinfo->joinclauses = fpinfo->remote_conds;
- 		fpinfo->remote_conds = NIL;
- 	}
- 
  	/* Mark that this join can be pushed down safely */
  	fpinfo->pushdown_safe = true;
  
--- 4116,4121 ----
***************
*** 4173,4181 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 4177,4242 ----
  					 get_jointype_name(fpinfo->jointype),
  					 fpinfo_i->relation_name->data);
  
+ 	/*
+ 	 * Set the subquery information
+ 	 *
+ 	 * If the relation performs a full outer join, then the input rel with
+ 	 * non-NIL remote_conds needs to be deparsed as a subquery.
+ 	 */
+ 	relids = NULL;
+ 	if (jointype == JOIN_FULL && fpinfo_o->remote_conds)
+ 	{
+ 		fpinfo->outer_is_subquery = true;
+ 		relids = bms_add_members(relids, outerrel->relids);
+ 	}
+ 	else
+ 	{
+ 		fpinfo->outer_is_subquery = false;
+ 		relids = bms_add_members(relids, fpinfo_o->subqueried_relids);
+ 	}
+ 	if (jointype == JOIN_FULL && fpinfo_i->remote_conds)
+ 	{
+ 		fpinfo->inner_is_subquery = true;
+ 		relids = bms_add_members(relids, innerrel->relids);
+ 	}
+ 	else
+ 	{
+ 		fpinfo->inner_is_subquery = false;
+ 		relids = bms_add_members(relids, fpinfo_i->subqueried_relids);
+ 	}
+ 	fpinfo->subqueried_relids = relids;
+ 
+ 	/* Set the relation index */
+ 	fpinfo->relation_index =
+ 		list_length(root->parse->rtable) + list_length(root->join_rel_list);
+ 
  	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 such Vars.
+  */
+ 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)
*** a/contrib/postgres_fdw/postgres_fdw.h
--- b/contrib/postgres_fdw/postgres_fdw.h
***************
*** 87,97 **** typedef struct PgFdwRelationInfo
--- 87,108 ----
  	 */
  	StringInfo	relation_name;
  
+ 	/*
+ 	 * Index of the relation.  It is used for creating a subselect alias when
+ 	 * deparsing the relation as a subquery.
+ 	 */
+ 	Index		relation_index;
+ 
  	/* Join information */
  	RelOptInfo *outerrel;
  	RelOptInfo *innerrel;
  	JoinType	jointype;
  	List	   *joinclauses;
+ 
+ 	/* Subquery information */
+ 	bool		outer_is_subquery;
+ 	bool		inner_is_subquery;
+ 	Relids		subqueried_relids;
  } PgFdwRelationInfo;
  
  /* in postgres_fdw.c */
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 398,403 **** SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL
--- 398,406 ----
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
  SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
+ EXPLAIN (VERBOSE, COSTS OFF)
+ SELECT t1, t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
+ SELECT t1, t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
  -- full outer join three tables
  EXPLAIN (VERBOSE, COSTS OFF)
  SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
-- 
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