From 131c7e70b4596027992a2f72bfd3765f0fff1b7c Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Tue, 14 Jun 2016 11:48:27 -0400 Subject: postgres_fdw: Check PlaceHolderVars before pushing down a join. As discovered by Andreas Seltenreich via sqlsmith, it's possible for a remote join to need to generate a target list which contains a PlaceHolderVar which would need to be evaluated on the remote server. This happens when we try to push down a join tree which contains outer joins and the nullable side of the join contains a subquery which evauates some expression which can go to NULL above the level of the join. Since the deparsing logic can't build a remote query that involves subqueries, it fails while trying to produce an SQL query that can be sent to the remote side. Detect such cases and don't try to push down the join at all. It's actually fine to push down the join if the PlaceHolderVar needs to be evaluated at the current join level. This patch makes a small change to build_tlist_to_deparse so that this case will work. Amit Langote, Ashutosh Bapat, and me. --- contrib/postgres_fdw/postgres_fdw.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) (limited to 'contrib/postgres_fdw/postgres_fdw.c') diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 4e31b8e40dd..4b6ec14c65b 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -3952,14 +3952,6 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) * Assess whether the join between inner and outer relations can be pushed down * to the foreign server. As a side effect, save information we obtain in this * function to PgFdwRelationInfo passed in. - * - * Joins that satisfy conditions below are safe to push down. - * - * 1) Join type is INNER or OUTER (one of LEFT/RIGHT/FULL) - * 2) Both outer and inner portions are safe to push-down - * 3) All join conditions are safe to push down - * 4) No relation has local filter (this can be relaxed for INNER JOIN, if we - * can move unpushable clauses upwards in the join tree). */ static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, @@ -4036,6 +4028,26 @@ 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; @@ -4116,9 +4128,9 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, } /* - * For an inner join, as explained above 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. + * 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) { -- cgit v1.2.3