*** a/contrib/postgres_fdw/deparse.c --- b/contrib/postgres_fdw/deparse.c *************** *** 135,140 **** static void deparseColumnRef(StringInfo buf, int varno, int varattno, --- 135,141 ---- static void deparseRelation(StringInfo buf, Relation rel); static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void deparseVar(Var *node, deparse_expr_cxt *context); + static void deparsePlaceHolderVar(PlaceHolderVar *node, deparse_expr_cxt *context); static void deparseConst(Const *node, deparse_expr_cxt *context); static void deparseParam(Param *node, deparse_expr_cxt *context); static void deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context); *************** *** 324,329 **** foreign_expr_walker(Node *node, --- 325,341 ---- } } break; + case T_PlaceHolderVar: + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + /* + * Check if the contained expr is safe to execute remotely. + */ + return foreign_expr_walker((Node *) phv->phexpr, + glob_cxt, outer_cxt); + } + break; case T_Const: { Const *c = (Const *) node; *************** *** 731,742 **** build_tlist_to_deparse(RelOptInfo *foreignrel) * 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; } --- 743,752 ---- * We require columns specified in foreignrel->reltarget->exprs and those * required for evaluating the local conditions. */ ! tlist = add_to_flat_tlist(tlist, foreignrel->reltarget->exprs); tlist = add_to_flat_tlist(tlist, pull_var_clause((Node *) fpinfo->local_conds, ! PVC_INCLUDE_PLACEHOLDERS)); return tlist; } *************** *** 1115,1127 **** 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, ", "); ! deparseVar(var, context); *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); --- 1125,1142 ---- Assert(IsA(tle, TargetEntry)); var = (Var *) tle->expr; ! /* We expect only Var or PlaceHolderVar nodes here */ ! if (!IsA(var, Var) && !IsA(var, PlaceHolderVar)) ! elog(ERROR, "unexpected node type in target list: %d", ! (int) nodeTag(var)); if (i > 0) appendStringInfoString(buf, ", "); ! ! if (IsA(var, Var)) ! deparseVar(var, context); ! else ! deparsePlaceHolderVar((PlaceHolderVar *) var, context); *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); *************** *** 1794,1799 **** deparseExpr(Expr *node, deparse_expr_cxt *context) --- 1809,1817 ---- case T_Var: deparseVar((Var *) node, context); break; + case T_PlaceHolderVar: + deparsePlaceHolderVar((PlaceHolderVar *) node, context); + break; case T_Const: deparseConst((Const *) node, context); break; *************** *** 1883,1888 **** deparseVar(Var *node, deparse_expr_cxt *context) --- 1901,1915 ---- } /* + * Deparse given PlaceHolderVar node into context->buf. + */ + static void + deparsePlaceHolderVar(PlaceHolderVar *node, deparse_expr_cxt *context) + { + deparseExpr(node->phexpr, context); + } + + /* * Deparse given constant value into context->buf. * * This function has to be kept in sync with ruleutils.c's get_const_expr. *** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 2236,2243 **** 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 (COSTS false, VERBOSE) 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) --- 2236,2243 ---- -- ok to push {ft1, ft2} but not {ft1, ft2, ft4} EXPLAIN (COSTS false, VERBOSE) 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) *************** *** 2247,2255 **** SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT -> 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 (((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; --- 2247,2255 ---- -> 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", 13 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; *** a/contrib/postgres_fdw/postgres_fdw.c --- b/contrib/postgres_fdw/postgres_fdw.c *************** *** 486,494 **** postgresGetForeignRelSize(PlannerInfo *root, fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); baserel->fdw_private = (void *) fpinfo; - /* Base foreign tables need to be push down always. */ - fpinfo->pushdown_safe = true; - /* Look up foreign-table catalog info. */ fpinfo->table = GetForeignTable(foreigntableid); fpinfo->server = GetForeignServer(fpinfo->table->serverid); --- 486,491 ---- *************** *** 642,647 **** postgresGetForeignRelSize(PlannerInfo *root, --- 639,677 ---- } /* + * Detect whether the foreign table is allowed to be remotely joined with + * any other foreign table or join of foreign tables belonging to the same + * foreign server and using the same user mapping, if any. + * + * Note: if the relation is an appendrel child, it isn't in the main join + * tree, so the flag is not used. + */ + fpinfo->allow_upper_foreign_join = false; + if (baserel->reloptkind == RELOPT_BASEREL) + { + if (fpinfo->local_conds == NIL) + { + bool has_ph_vars = false; + + foreach(lc, baserel->reltarget->exprs) + { + Node *node = (Node *) lfirst(lc); + + Assert(IsA(node, Var) || IsA(node, PlaceHolderVar)); + + if (IsA(node, PlaceHolderVar)) + { + has_ph_vars = true; + break; + } + } + + if (!has_ph_vars) + fpinfo->allow_upper_foreign_join = true; + } + } + + /* * 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 *************** *** 3985,4006 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, return false; /* ! * If either of the joining relations is marked as unsafe to pushdown, the ! * join can not be pushed down. */ fpinfo = (PgFdwRelationInfo *) joinrel->fdw_private; fpinfo_o = (PgFdwRelationInfo *) outerrel->fdw_private; fpinfo_i = (PgFdwRelationInfo *) innerrel->fdw_private; ! if (!fpinfo_o || !fpinfo_o->pushdown_safe || ! !fpinfo_i || !fpinfo_i->pushdown_safe) ! return false; ! ! /* ! * If joining relations have local conditions, those conditions are ! * required to be applied before joining the relations. Hence the join can ! * not be pushed down. ! */ ! if (fpinfo_o->local_conds || fpinfo_i->local_conds) return false; /* Separate restrict list into join quals and quals on join relation */ --- 4015,4029 ---- return false; /* ! * If either of the joining relations is not allowed to be remotely joined ! * with any other foreign table or join of foreign tables, the join can't ! * be pushed down. */ fpinfo = (PgFdwRelationInfo *) joinrel->fdw_private; fpinfo_o = (PgFdwRelationInfo *) outerrel->fdw_private; fpinfo_i = (PgFdwRelationInfo *) innerrel->fdw_private; ! if (!fpinfo_o || !fpinfo_o->allow_upper_foreign_join || ! !fpinfo_i || !fpinfo_i->allow_upper_foreign_join) return false; /* Separate restrict list into join quals and quals on join relation */ *************** *** 4028,4053 **** 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; --- 4051,4056 ---- *************** *** 4139,4146 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, fpinfo->remote_conds = NIL; } ! /* Mark that this join can be pushed down safely */ ! fpinfo->pushdown_safe = true; /* * If user is willing to estimate cost for a scan of either of the joining --- 4142,4178 ---- fpinfo->remote_conds = NIL; } ! /* ! * Detect whether the join is allowed to be remotely joined with any other ! * foreign table or join of foreign tables belonging to the same foreign ! * server and using the same user mapping, if any. ! */ ! if (fpinfo->local_conds == NIL) ! { ! bool has_ph_vars = false; ! ! foreach(lc, joinrel->reltarget->exprs) ! { ! Node *node = (Node *) lfirst(lc); ! ! Assert(IsA(node, Var) || IsA(node, PlaceHolderVar)); ! ! if (IsA(node, PlaceHolderVar)) ! { ! has_ph_vars = true; ! ! /* ! * If the PHV is unsafe to execute remotely, give up pushing ! * down the join. ! */ ! if (!is_foreign_expr(root, joinrel, (Expr *) node)) ! return false; ! } ! } ! ! if (!has_ph_vars) ! fpinfo->allow_upper_foreign_join = true; ! } /* * If user is willing to estimate cost for a scan of either of the joining *************** *** 4258,4264 **** postgresGetForeignJoinPaths(PlannerInfo *root, * the entry. */ fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); ! fpinfo->pushdown_safe = false; joinrel->fdw_private = fpinfo; /* attrs_used is only for base relations. */ fpinfo->attrs_used = NULL; --- 4290,4296 ---- * the entry. */ fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); ! fpinfo->allow_upper_foreign_join = false; joinrel->fdw_private = fpinfo; /* attrs_used is only for base relations. */ fpinfo->attrs_used = NULL; *************** *** 4558,4568 **** conversion_error_callback(void *arg) 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); ! relname = get_rel_name(rte->relid); ! attname = get_relid_attribute_name(rte->relid, var->varattno); } if (attname && relname) --- 4590,4603 ---- errpos->cur_attno - 1); Assert(IsA(tle, TargetEntry)); var = (Var *) tle->expr; ! Assert(IsA(var, Var) || IsA(var, PlaceHolderVar)); ! if (IsA(var, Var)) ! { ! rte = rt_fetch(var->varno, estate->es_range_table); ! relname = get_rel_name(rte->relid); ! attname = get_relid_attribute_name(rte->relid, var->varattno); ! } } if (attname && relname) *** a/contrib/postgres_fdw/postgres_fdw.h --- b/contrib/postgres_fdw/postgres_fdw.h *************** *** 27,38 **** typedef struct PgFdwRelationInfo { /* - * True means that the relation can be pushed down. Always true for simple - * foreign scan. - */ - bool pushdown_safe; - - /* * Restriction clauses, divided into safe and unsafe to pushdown subsets. * * For a base foreign relation this is a list of clauses along-with --- 27,32 ---- *************** *** 92,97 **** typedef struct PgFdwRelationInfo --- 86,102 ---- RelOptInfo *innerrel; JoinType jointype; List *joinclauses; + + /* + * Flag to indicate whether the relation is allowed to be remotely joined + * with any other foreign table or join of foreign tables belonging to the + * same foreign server and using the same user mapping. If the relation's + * reltarget doesn't contain anything other than Vars, and the local_conds + * is NIL, then that's possible and so the flag is set to true. (Note + * that the former is needed since the deparsing logic currently can't + * build a remote query that involves subqueries.) + */ + bool allow_upper_foreign_join; } PgFdwRelationInfo; /* in postgres_fdw.c */