diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 029c20b2ca3..cbbf0bba4bd 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -5227,6 +5227,45 @@ SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE Remote SQL: SELECT r5."C 1", r6.c1 FROM ("S 1"."T 1" r5 INNER JOIN "S 1"."T 3" r6 ON (((r5."C 1" = r6.c1)))) ORDER BY r5."C 1" ASC NULLS LAST (13 rows) +-- Can't push down semi-join with a non-shippable join clause, as evaluating +-- it locally would require inner rel vars in the remote targetlist +CREATE FUNCTION f_noship(int) RETURNS int LANGUAGE plpgsql IMMUTABLE + AS $$ BEGIN RETURN $1; END $$; +SET enable_hashjoin TO off; +SET enable_mergejoin TO off; +SET enable_hashagg TO off; +SET enable_sort TO off; +EXPLAIN (verbose, costs off) +SELECT count(*) FROM ft2 + WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1)); + QUERY PLAN +------------------------------------------------------------ + Aggregate + Output: count(*) + -> Nested Loop Semi Join + Join Filter: (f_noship(ft2.c1) = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Materialize + Output: ft4.c1 + -> Foreign Scan on public.ft4 + Output: ft4.c1 + Remote SQL: SELECT c1 FROM "S 1"."T 3" +(12 rows) + +SELECT count(*) FROM ft2 + WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1)); + count +------- + 50 +(1 row) + +RESET enable_hashjoin; +RESET enable_mergejoin; +RESET enable_hashagg; +RESET enable_sort; +DROP FUNCTION f_noship(int); -- =================================================================== -- test writable foreign table stuff -- =================================================================== diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 6b52011eeab..b07aa11b1a9 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -6573,21 +6573,32 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) } /* - * Check if reltarget is safe enough to push down semi-join. Reltarget is not - * safe, if it contains references to inner rel relids, which do not belong to - * outer rel. + * Check if it's safe to push down a semi-join. We deparse a semi-join as an + * EXISTS() subquery, so the remote targetlist, which consists of the + * joinrel's reltarget plus the vars needed by local conditions (see + * build_tlist_to_deparse()), mustn't reference the inner rel. */ static bool -semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel) +semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, + RelOptInfo *outerrel, RelOptInfo *innerrel, + List *local_conds) { List *vars; ListCell *lc; - bool ok = true; Assert(joinrel->reltarget); vars = pull_var_clause((Node *) joinrel->reltarget->exprs, PVC_INCLUDE_PLACEHOLDERS); + foreach(lc, local_conds) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + vars = list_concat(vars, + pull_var_clause((Node *) rinfo->clause, + PVC_INCLUDE_PLACEHOLDERS)); + } + foreach(lc, vars) { Var *var = (Var *) lfirst(lc); @@ -6597,18 +6608,11 @@ semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, if (bms_is_member(var->varno, innerrel->relids)) { - /* - * The planner can create semi-join, which refers to inner rel - * vars in its target list. However, we deparse semi-join as an - * exists() subquery, so can't handle references to inner rel in - * the target list. - */ Assert(!bms_is_member(var->varno, outerrel->relids)); - ok = false; - break; + return false; } } - return ok; + return true; } /* @@ -6637,12 +6641,6 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, jointype != JOIN_SEMI) return false; - /* - * We can't push down semi-join if its reltarget is not safe - */ - if ((jointype == JOIN_SEMI) && !semijoin_target_ok(root, joinrel, outerrel, innerrel)) - return false; - /* * If either of the joining relations is marked as unsafe to pushdown, the * join can not be pushed down. @@ -6709,6 +6707,15 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, } } + /* + * We can't push down a semi-join if its remote targetlist would reference + * the inner rel. + */ + if (jointype == JOIN_SEMI && + !semijoin_target_ok(root, joinrel, outerrel, innerrel, + fpinfo->local_conds)) + return false; + /* * deparseExplicitTargetList() isn't smart enough to handle anything other * than a Var. In particular, if there's some PlaceHolderVar that would diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 3520739b8a8..a92ed9cf8b0 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1558,6 +1558,25 @@ SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE SELECT ft2.c1 FROM ft2 JOIN ft4 ON ft2.c1 = ft4.c1) ORDER BY ft1.c1 LIMIT 5; +-- Can't push down semi-join with a non-shippable join clause, as evaluating +-- it locally would require inner rel vars in the remote targetlist +CREATE FUNCTION f_noship(int) RETURNS int LANGUAGE plpgsql IMMUTABLE + AS $$ BEGIN RETURN $1; END $$; +SET enable_hashjoin TO off; +SET enable_mergejoin TO off; +SET enable_hashagg TO off; +SET enable_sort TO off; +EXPLAIN (verbose, costs off) +SELECT count(*) FROM ft2 + WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1)); +SELECT count(*) FROM ft2 + WHERE EXISTS (SELECT 1 FROM ft4 WHERE ft4.c1 = f_noship(ft2.c1)); +RESET enable_hashjoin; +RESET enable_mergejoin; +RESET enable_hashagg; +RESET enable_sort; +DROP FUNCTION f_noship(int); + -- =================================================================== -- test writable foreign table stuff -- ===================================================================