*** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 1701,1722 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t 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) ! -> Sort Output: t1.c1, t1.c3, t1.* ! Sort Key: t1.c1 ! -> Foreign Scan on public.ft1 t1 ! Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE ! -> Sort Output: t2.c1, t2.* ! Sort Key: t2.c1 ! -> Foreign Scan on public.ft2 t2 ! Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ! (23 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 FOR UPDATE OF t1; c1 | c1 --- 1701,1716 ---- 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 ! -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* ! Join Filter: (t1.c1 = t2.c1) ! -> Foreign Scan on public.ft1 t1 Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE ! -> Foreign Scan on public.ft2 t2 Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ! (17 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 FOR UPDATE OF t1; c1 | c1 *************** *** 1745,1766 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t 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) ! -> Sort Output: t1.c1, t1.c3, t1.* ! Sort Key: t1.c1 ! -> Foreign Scan on public.ft1 t1 ! Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE ! -> Sort Output: t2.c1, t2.* ! Sort Key: t2.c1 ! -> Foreign Scan on public.ft2 t2 ! Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE ! (23 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 FOR UPDATE; c1 | c1 --- 1739,1754 ---- 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 ! -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* ! Join Filter: (t1.c1 = t2.c1) ! -> Foreign Scan on public.ft1 t1 Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE ! -> Foreign Scan on public.ft2 t2 Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE ! (17 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 FOR UPDATE; c1 | c1 *************** *** 1790,1811 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t 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) ! -> Sort Output: t1.c1, t1.c3, t1.* ! Sort Key: t1.c1 ! -> Foreign Scan on public.ft1 t1 ! Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE ! -> Sort Output: t2.c1, t2.* ! Sort Key: t2.c1 ! -> Foreign Scan on public.ft2 t2 ! Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ! (23 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 FOR SHARE OF t1; c1 | c1 --- 1778,1793 ---- 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 ! -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* ! Join Filter: (t1.c1 = t2.c1) ! -> Foreign Scan on public.ft1 t1 Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE ! -> Foreign Scan on public.ft2 t2 Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ! (17 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 FOR SHARE OF t1; c1 | c1 *************** *** 1834,1855 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t 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) ! -> Sort Output: t1.c1, t1.c3, t1.* ! Sort Key: t1.c1 ! -> Foreign Scan on public.ft1 t1 ! Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE ! -> Sort Output: t2.c1, t2.* ! Sort Key: t2.c1 ! -> Foreign Scan on public.ft2 t2 ! Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE ! (23 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 FOR SHARE; c1 | c1 --- 1816,1831 ---- 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 ! -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* ! Join Filter: (t1.c1 = t2.c1) ! -> Foreign Scan on public.ft1 t1 Output: t1.c1, t1.c3, t1.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE ! -> Foreign Scan on public.ft2 t2 Output: t2.c1, t2.* ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE ! (17 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 FOR SHARE; c1 | c1 *************** *** 1866,1871 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t --- 1842,1908 ---- 110 | 110 (10 rows) + -- FOR UPDATE/SHARE in situations where a full join is pushed down + EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2.c1, t3.c1 FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (ft4 t2 FULL JOIN ft5 t3 ON (t2.c1 = t3.c1)) ON (TRUE) ORDER BY t1.c1, t2.c1, t3.c1 FOR UPDATE OF t1; + QUERY PLAN + --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: "T 3".c1, t2.c1, t3.c1, "T 3".ctid, t2.*, t3.* + -> Nested Loop + Output: "T 3".c1, t2.c1, t3.c1, "T 3".ctid, t2.*, t3.* + -> Foreign Scan + Output: t2.c1, t2.*, t3.c1, t3.* + Relations: (public.ft4 t2) FULL JOIN (public.ft5 t3) + Remote SQL: SELECT r2.c1, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r3.c1, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END FROM ("S 1"."T 3" r2 FULL JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r3.c1 ASC NULLS LAST + -> Hash Full Join + Output: t2.c1, t2.*, t3.c1, t3.* + Hash Cond: (t2.c1 = t3.c1) + -> Foreign Scan on public.ft4 t2 + Output: t2.c1, t2.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Hash + Output: t3.c1, t3.* + -> Foreign Scan on public.ft5 t3 + Output: t3.c1, t3.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + -> Materialize + Output: "T 3".c1, "T 3".ctid + -> Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".ctid + Filter: ("T 3".c1 = 50) + (24 rows) + + EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2.c1, t3.c1 FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (ft4 t2 FULL JOIN ft5 t3 ON (FALSE)) ON (TRUE) ORDER BY t1.c1, t2.c1, t3.c1 FOR UPDATE OF t1; + QUERY PLAN + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: "T 3".c1, t2.c1, t3.c1, "T 3".ctid, t2.*, t3.* + -> Nested Loop + Output: "T 3".c1, t2.c1, t3.c1, "T 3".ctid, t2.*, t3.* + -> Foreign Scan + Output: t2.c1, t2.*, t3.c1, t3.* + Relations: (public.ft4 t2) FULL JOIN (public.ft5 t3) + Remote SQL: SELECT r2.c1, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r3.c1, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END FROM ("S 1"."T 3" r2 FULL JOIN "S 1"."T 4" r3 ON ((false))) ORDER BY r2.c1 ASC NULLS LAST, r3.c1 ASC NULLS LAST + -> Merge Full Join + Output: t2.c1, t2.*, t3.c1, t3.* + Join Filter: false + -> Foreign Scan on public.ft4 t2 + Output: t2.c1, t2.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Materialize + Output: t3.c1, t3.* + -> Foreign Scan on public.ft5 t3 + Output: t3.c1, t3.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" + -> Materialize + Output: "T 3".c1, "T 3".ctid + -> Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".ctid + Filter: ("T 3".c1 = 50) + (24 rows) + -- 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; *************** *** 4298,4315 **** UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT 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) -> Foreign Scan on public.ft2 Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" FOR UPDATE ! -> Hash Output: ft1.*, ft1.c1 ! -> Foreign Scan on public.ft1 ! Output: ft1.*, ft1.c1 ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9)) ! (17 rows) 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; --- 4335,4350 ---- 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 ! -> Nested Loop Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid, ft1.* ! Join Filter: (ft2.c2 = ft1.c1) -> Foreign Scan on public.ft2 Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" FOR UPDATE ! -> Foreign Scan on public.ft1 Output: ft1.*, ft1.c1 ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9)) ! (15 rows) 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; *************** *** 4441,4458 **** DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; 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) -> Foreign Scan on public.ft2 Output: ft2.ctid, ft2.c2 Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" FOR UPDATE ! -> Hash Output: ft1.*, ft1.c1 ! -> Foreign Scan on public.ft1 ! Output: ft1.*, ft1.c1 ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2)) ! (17 rows) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; --- 4476,4491 ---- 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 ! -> Nested Loop Output: ft2.ctid, ft1.* ! Join Filter: (ft2.c2 = ft1.c1) -> Foreign Scan on public.ft2 Output: ft2.ctid, ft2.c2 Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" FOR UPDATE ! -> Foreign Scan on public.ft1 Output: ft1.*, ft1.c1 ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2)) ! (15 rows) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; *** a/contrib/postgres_fdw/postgres_fdw.c --- b/contrib/postgres_fdw/postgres_fdw.c *************** *** 4493,4514 **** postgresGetForeignJoinPaths(PlannerInfo *root, /* * If there is a possibility that EvalPlanQual will be executed, we need * to be able to reconstruct the row using scans of the base relations. ! * GetExistingLocalJoinPath will find a suitable path for this purpose in ! * the path list of the joinrel, if one exists. We must be careful to ! * call it before adding any ForeignPath, since the ForeignPath might ! * dominate the only suitable local path available. We also do it before ! * reconstruct the row for EvalPlanQual(). Find an alternative local path ! * calling foreign_join_ok(), since that function updates fpinfo and marks ! * it as pushable if the join is found to be pushable. */ if (root->parse->commandType == CMD_DELETE || root->parse->commandType == CMD_UPDATE || root->rowMarks) { ! epq_path = GetExistingLocalJoinPath(joinrel); if (!epq_path) { ! elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found"); return; } } --- 4493,4529 ---- /* * If there is a possibility that EvalPlanQual will be executed, we need * to be able to reconstruct the row using scans of the base relations. ! * CreateLocalJoinPath will build an alternative local join path for this ! * purpose. We must be careful to call it before calling foreign_join_ok, ! * since that function updates fpinfo and marks it as pushable if the join ! * is found to be pushable. */ if (root->parse->commandType == CMD_DELETE || root->parse->commandType == CMD_UPDATE || root->rowMarks) { ! Path *outer_path = outerrel->cheapest_total_path; ! Path *inner_path = innerrel->cheapest_total_path; ! ! /* ! * The cheapest total paths should be unparameterized, because (1) if ! * the input relation is a base relation, we should have created an ! * unparameterized foreign scan path for the base relation and (2) if ! * the input relation is a join relation, we should have created at ! * least one unparameterized local join path (where unparameterized ! * foreign scan paths are chosen for base relations involved in the ! * join relation) or foreign join path for the join relation (else we ! * shouldn't get here). ! */ ! Assert(outer_path->param_info == NULL); ! Assert(inner_path->param_info == NULL); ! ! /* Create an unparameterized local join path */ ! epq_path = CreateLocalJoinPath(root, joinrel, outer_path, inner_path, ! NULL, jointype, extra); if (!epq_path) { ! elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks could not be created"); return; } } *************** *** 4517,4523 **** postgresGetForeignJoinPaths(PlannerInfo *root, if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra)) { ! /* Free path required for EPQ if we copied one; we don't need it now */ if (epq_path) pfree(epq_path); return; --- 4532,4538 ---- if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra)) { ! /* Free path required for EPQ if we created one; we don't need it now */ if (epq_path) pfree(epq_path); return; *** a/contrib/postgres_fdw/sql/postgres_fdw.sql --- b/contrib/postgres_fdw/sql/postgres_fdw.sql *************** *** 493,498 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t --- 493,503 ---- 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; 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; + -- FOR UPDATE/SHARE in situations where a full join is pushed down + EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2.c1, t3.c1 FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (ft4 t2 FULL JOIN ft5 t3 ON (t2.c1 = t3.c1)) ON (TRUE) ORDER BY t1.c1, t2.c1, t3.c1 FOR UPDATE OF t1; + EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2.c1, t3.c1 FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (ft4 t2 FULL JOIN ft5 t3 ON (FALSE)) ON (TRUE) ORDER BY t1.c1, t2.c1, t3.c1 FOR UPDATE OF t1; -- 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; *** a/doc/src/sgml/fdwhandler.sgml --- b/doc/src/sgml/fdwhandler.sgml *************** *** 995,1005 **** RecheckForeignScan (ForeignScanState *node, TupleTableSlot *slot); can be executed and the resulting tuple can be stored in the slot. This plan need not be efficient since no base table will return more than one row; for example, it may implement all joins as nested loops. ! The function GetExistingLocalJoinPath may be used to search ! existing paths for a suitable local join path, which can be used as the ! alternative local join plan. GetExistingLocalJoinPath ! searches for an unparameterized path in the path list of the specified ! join relation. (If it does not find such a path, it returns NULL, in which case a foreign data wrapper may build the local path by itself or may choose not to create access paths for that join.) --- 995,1003 ---- can be executed and the resulting tuple can be stored in the slot. This plan need not be efficient since no base table will return more than one row; for example, it may implement all joins as nested loops. ! The function CreateLocalJoinPath may be used to build ! a suitable local join path, which can be used to create an alternative ! local join plan. (If it does not build such a path, it returns NULL, in which case a foreign data wrapper may build the local path by itself or may choose not to create access paths for that join.) *** a/src/backend/foreign/foreign.c --- b/src/backend/foreign/foreign.c *************** *** 22,27 **** --- 22,30 ---- #include "foreign/foreign.h" #include "lib/stringinfo.h" #include "miscadmin.h" + #include "optimizer/cost.h" + #include "optimizer/pathnode.h" + #include "optimizer/paths.h" #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/rel.h" *************** *** 689,801 **** get_foreign_server_oid(const char *servername, bool missing_ok) return oid; } /* ! * Get a copy of an existing local path for a given join relation. ! * ! * This function is usually helpful to obtain an alternate local path for EPQ ! * checks. ! * ! * Right now, this function only supports unparameterized foreign joins, so we ! * only search for unparameterized path in the given list of paths. Since we ! * are searching for a path which can be used to construct an alternative local ! * plan for a foreign join, we look for only MergeJoin, HashJoin or NestLoop ! * paths. ! * ! * If the inner or outer subpath of the chosen path is a ForeignScan, we ! * replace it with its outer subpath. For this reason, and also because the ! * planner might free the original path later, the path returned by this ! * function is a shallow copy of the original. There's no need to copy ! * the substructure, so we don't. ! * ! * Since the plan created using this path will presumably only be used to ! * execute EPQ checks, efficiency of the path is not a concern. But since the ! * path list in RelOptInfo is anyway sorted by total cost we are likely to ! * choose the most efficient path, which is all for the best. */ ! extern Path * ! GetExistingLocalJoinPath(RelOptInfo *joinrel) { ! ListCell *lc; ! Assert(IS_JOIN_REL(joinrel)); ! foreach(lc, joinrel->pathlist) { ! Path *path = (Path *) lfirst(lc); ! JoinPath *joinpath = NULL; ! /* Skip parameterized paths. */ ! if (path->param_info != NULL) ! continue; ! switch (path->pathtype) ! { ! case T_HashJoin: ! { ! HashPath *hash_path = makeNode(HashPath); ! memcpy(hash_path, path, sizeof(HashPath)); ! joinpath = (JoinPath *) hash_path; ! } ! break; ! case T_NestLoop: { ! NestPath *nest_path = makeNode(NestPath); ! ! memcpy(nest_path, path, sizeof(NestPath)); ! joinpath = (JoinPath *) nest_path; } ! break; ! ! case T_MergeJoin: { ! MergePath *merge_path = makeNode(MergePath); ! memcpy(merge_path, path, sizeof(MergePath)); ! joinpath = (JoinPath *) merge_path; } ! break; ! ! default: ! ! /* ! * Just skip anything else. We don't know if corresponding ! * plan would build the output row from whole-row references ! * of base relations and execute the EPQ checks. ! */ ! break; ! } ! ! /* This path isn't good for us, check next. */ ! if (!joinpath) ! continue; ! ! /* ! * If either inner or outer path is a ForeignPath corresponding to a ! * pushed down join, replace it with the fdw_outerpath, so that we ! * maintain path for EPQ checks built entirely of local join ! * strategies. ! */ ! if (IsA(joinpath->outerjoinpath, ForeignPath)) ! { ! ForeignPath *foreign_path; ! ! foreign_path = (ForeignPath *) joinpath->outerjoinpath; ! if (IS_JOIN_REL(foreign_path->path.parent)) ! joinpath->outerjoinpath = foreign_path->fdw_outerpath; ! } ! ! if (IsA(joinpath->innerjoinpath, ForeignPath)) ! { ! ForeignPath *foreign_path; ! ! foreign_path = (ForeignPath *) joinpath->innerjoinpath; ! if (IS_JOIN_REL(foreign_path->path.parent)) ! joinpath->innerjoinpath = foreign_path->fdw_outerpath; ! } ! ! return (Path *) joinpath; } ! return NULL; } --- 692,852 ---- return oid; } + /* ! * Build an alternative local join path to reconstruct a join tuple for the ! * foreign-join in EvalPlanQual */ ! Path * ! CreateLocalJoinPath(PlannerInfo *root, ! RelOptInfo *joinrel, ! Path *outer_path, ! Path *inner_path, ! Relids required_outer, ! JoinType jointype, ! JoinPathExtraData *extra) { ! Path *result; ! /* Should be called only if consider_foreignjoin */ ! Assert(extra->consider_foreignjoin); ! switch (jointype) { ! case JOIN_INNER: ! case JOIN_LEFT: ! case JOIN_SEMI: ! case JOIN_ANTI: ! { ! JoinCostWorkspace workspace; ! /* outer_path should not require rels from inner_path */ ! if (PATH_PARAM_BY_REL(outer_path, inner_path->parent)) ! elog(ERROR, "outer paths should not be parameterized by inner relations"); ! /* Get an initial estimate */ ! initial_cost_nestloop(root, &workspace, jointype, ! outer_path, inner_path, extra); ! /* Generate a nestloop path */ ! result = (Path *) create_nestloop_path(root, ! joinrel, ! jointype, ! &workspace, ! extra, ! outer_path, ! inner_path, ! extra->restrictlist, ! NIL, ! required_outer); ! } ! break; ! case JOIN_RIGHT: ! case JOIN_FULL: ! { ! JoinCostWorkspace workspace; ! /* Neither path should require rels from the other path */ ! if (PATH_PARAM_BY_REL(outer_path, inner_path->parent) || ! PATH_PARAM_BY_REL(inner_path, outer_path->parent)) ! elog(ERROR, "neither path should not be parameterized by the other input relation"); ! /* Generate a hashjoin or mergejoin path, if possible */ ! if (extra->hashclauses) { ! /* Get an initial estimate */ ! initial_cost_hashjoin(root, &workspace, jointype, ! extra->hashclauses, ! outer_path, inner_path, extra); ! /* Generate a hashjoin path */ ! result = (Path *) create_hashjoin_path(root, ! joinrel, ! jointype, ! &workspace, ! extra, ! outer_path, ! inner_path, ! extra->restrictlist, ! required_outer, ! extra->hashclauses); } ! else if (extra->mergejoin_allowed) { ! /* ! * If special case: for "x FULL JOIN y ON true" or "x FULL ! * JOIN y ON false", there will be no join clauses at all; ! * create a clauseless mergejoin path. Else create a ! * mergejoin path by explicitly sorting both the outer and ! * inner relations. ! */ ! if (!extra->mergeclause_list) ! { ! /* Get an initial estimate */ ! initial_cost_mergejoin(root, &workspace, jointype, NIL, ! outer_path, inner_path, ! NIL, NIL, extra); ! /* Generate a mergejoin path */ ! result = (Path *) create_mergejoin_path(root, ! joinrel, ! jointype, ! &workspace, ! extra, ! outer_path, ! inner_path, ! extra->restrictlist, ! NIL, ! required_outer, ! NIL, NIL, NIL); ! } ! else ! { ! List *outerkeys = extra->outersortkeys; ! List *innerkeys = extra->innersortkeys; ! /* ! * It's possible that the cheapest-total paths will ! * already be sorted properly; if so, suppress an ! * explicit sort. ! */ ! if (outerkeys && ! pathkeys_contained_in(outerkeys, ! outer_path->pathkeys)) ! outerkeys = NIL; ! if (innerkeys && ! pathkeys_contained_in(innerkeys, ! inner_path->pathkeys)) ! innerkeys = NIL; ! /* Get an initial estimate */ ! initial_cost_mergejoin(root, &workspace, jointype, ! extra->mergeclauses, ! outer_path, inner_path, ! outerkeys, innerkeys, extra); ! /* Generate a mergejoin path */ ! result = (Path *) create_mergejoin_path(root, ! joinrel, ! jointype, ! &workspace, ! extra, ! outer_path, ! inner_path, ! extra->restrictlist, ! NIL, ! required_outer, ! extra->mergeclauses, ! outerkeys, ! innerkeys); ! } } ! else ! result = NULL; ! } ! break; ! default: ! /* other values not expected here */ ! elog(ERROR, "unrecognized join type: %d", ! (int) jointype); ! result = NULL; /* keep compiler quiet */ ! break; } ! ! return result; } *** a/src/backend/optimizer/path/joinpath.c --- b/src/backend/optimizer/path/joinpath.c *************** *** 26,34 **** /* Hook for plugins to get control in add_paths_to_joinrel() */ set_join_pathlist_hook_type set_join_pathlist_hook = NULL; - #define PATH_PARAM_BY_REL(path, rel) \ - ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids)) - static void try_partial_mergejoin_path(PlannerInfo *root, RelOptInfo *joinrel, Path *outer_path, --- 26,31 ---- *************** *** 113,125 **** add_paths_to_joinrel(PlannerInfo *root, List *restrictlist) { JoinPathExtraData extra; - bool mergejoin_allowed = true; ListCell *lc; extra.restrictlist = restrictlist; extra.mergeclause_list = NIL; extra.sjinfo = sjinfo; extra.param_source_rels = NULL; /* * See if the inner relation is provably unique for this outer rel. --- 110,127 ---- List *restrictlist) { JoinPathExtraData extra; ListCell *lc; extra.restrictlist = restrictlist; extra.mergeclause_list = NIL; + extra.mergejoin_allowed = true; extra.sjinfo = sjinfo; extra.param_source_rels = NULL; + extra.consider_foreignjoin = false; + extra.hashclauses = NIL; + extra.mergeclauses = NIL; + extra.outersortkeys = NIL; + extra.innersortkeys = NIL; /* * See if the inner relation is provably unique for this outer rel. *************** *** 177,183 **** add_paths_to_joinrel(PlannerInfo *root, innerrel, restrictlist, jointype, ! &mergejoin_allowed); /* * If it's SEMI, ANTI, or inner_unique join, compute correction factors --- 179,185 ---- innerrel, restrictlist, jointype, ! &extra.mergejoin_allowed); /* * If it's SEMI, ANTI, or inner_unique join, compute correction factors *************** *** 237,246 **** add_paths_to_joinrel(PlannerInfo *root, joinrel->lateral_relids); /* * 1. Consider mergejoin paths where both relations must be explicitly * sorted. Skip this if we can't mergejoin. */ ! if (mergejoin_allowed) sort_inner_and_outer(root, joinrel, outerrel, innerrel, jointype, &extra); --- 239,257 ---- joinrel->lateral_relids); /* + * Decide whether to give the FDW a chance to push down the join to the + * foreign server; if inner and outer relations are foreign tables (or + * joins) belonging to the same server and assigned to the same user to + * check access permissions as, we give the FDW that chance. + */ + if (joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPaths) + extra.consider_foreignjoin = true; + + /* * 1. Consider mergejoin paths where both relations must be explicitly * sorted. Skip this if we can't mergejoin. */ ! if (extra.mergejoin_allowed) sort_inner_and_outer(root, joinrel, outerrel, innerrel, jointype, &extra); *************** *** 251,257 **** add_paths_to_joinrel(PlannerInfo *root, * (That's okay because we know that nestloop can't handle right/full * joins at all, so it wouldn't work in the prohibited cases either.) */ ! if (mergejoin_allowed) match_unsorted_outer(root, joinrel, outerrel, innerrel, jointype, &extra); --- 262,268 ---- * (That's okay because we know that nestloop can't handle right/full * joins at all, so it wouldn't work in the prohibited cases either.) */ ! if (extra.mergejoin_allowed) match_unsorted_outer(root, joinrel, outerrel, innerrel, jointype, &extra); *************** *** 268,274 **** add_paths_to_joinrel(PlannerInfo *root, * those made by match_unsorted_outer when add_paths_to_joinrel() is * invoked with the two rels given in the other order. */ ! if (mergejoin_allowed) match_unsorted_inner(root, joinrel, outerrel, innerrel, jointype, &extra); #endif --- 279,285 ---- * those made by match_unsorted_outer when add_paths_to_joinrel() is * invoked with the two rels given in the other order. */ ! if (extra.mergejoin_allowed) match_unsorted_inner(root, joinrel, outerrel, innerrel, jointype, &extra); #endif *************** *** 283,294 **** add_paths_to_joinrel(PlannerInfo *root, jointype, &extra); /* ! * 5. If inner and outer relations are foreign tables (or joins) belonging ! * to the same server and assigned to the same user to check access ! * permissions as, give the FDW a chance to push down joins. */ ! if (joinrel->fdwroutine && ! joinrel->fdwroutine->GetForeignJoinPaths) joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel, outerrel, innerrel, jointype, &extra); --- 294,302 ---- jointype, &extra); /* ! * 5. Give the FDW a chance to push down the join to the foreign server. */ ! if (extra.consider_foreignjoin) joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel, outerrel, innerrel, jointype, &extra); *************** *** 780,785 **** clause_sides_match_join(RestrictInfo *rinfo, RelOptInfo *outerrel, --- 788,800 ---- * 'innerrel' is the inner join relation * 'jointype' is the type of join to do * 'extra' contains additional input values + * + * This function returns the following info into fields of the + * JoinPathExtraData struct for possible use by the FDW: + * + * mergeclauses RestrictInfos to use as merge clauses in a mergejoin + * outersortkeys Sort pathkeys for the outer relation of the mergejoin + * innersortkeys Sort pathkeys for the inner relation of the mergejoin */ static void sort_inner_and_outer(PlannerInfo *root, *************** *** 966,971 **** sort_inner_and_outer(PlannerInfo *root, --- 981,994 ---- innerkeys, jointype, extra); + + /* Save first mergejoin data for possible use by the FDW */ + if (extra->consider_foreignjoin && outerkeys == all_pathkeys) + { + extra->mergeclauses = cur_mergeclauses; + extra->outersortkeys = outerkeys; + extra->innersortkeys = innerkeys; + } } } *************** *** 1578,1583 **** consider_parallel_nestloop(PlannerInfo *root, --- 1601,1611 ---- * 'innerrel' is the inner join relation * 'jointype' is the type of join to do * 'extra' contains additional input values + * + * This function returns the following info into a field of the + * JoinPathExtraData struct for possible use by the FDW: + * + * hashclauses RestrictInfos to use as hash clauses in a hashjoin */ static void hash_inner_and_outer(PlannerInfo *root, *************** *** 1786,1791 **** hash_inner_and_outer(PlannerInfo *root, --- 1814,1823 ---- hashclauses, jointype, extra); } } + + /* Save hashclauses for possible use by the FDW */ + if (extra->consider_foreignjoin && hashclauses) + extra->hashclauses = hashclauses; } /* *** a/src/include/foreign/fdwapi.h --- b/src/include/foreign/fdwapi.h *************** *** 237,242 **** extern FdwRoutine *GetFdwRoutineByRelId(Oid relid); extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy); extern bool IsImportableForeignTable(const char *tablename, ImportForeignSchemaStmt *stmt); ! extern Path *GetExistingLocalJoinPath(RelOptInfo *joinrel); #endif /* FDWAPI_H */ --- 237,244 ---- extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy); extern bool IsImportableForeignTable(const char *tablename, ImportForeignSchemaStmt *stmt); ! extern Path *CreateLocalJoinPath(PlannerInfo *root, RelOptInfo *joinrel, ! Path *outer_path, Path *inner_path, Relids required_outer, ! JoinType jointype, JoinPathExtraData *extra); #endif /* FDWAPI_H */ *** a/src/include/nodes/relation.h --- b/src/include/nodes/relation.h *************** *** 973,978 **** typedef struct Path --- 973,982 ---- #define PATH_REQ_OUTER(path) \ ((path)->param_info ? (path)->param_info->ppi_req_outer : (Relids) NULL) + /* Macro for determining whether path is parameterized by rel */ + #define PATH_PARAM_BY_REL(path, rel) \ + ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids)) + /*---------- * IndexPath represents an index scan over a single index. * *************** *** 2169,2188 **** typedef struct SemiAntiJoinFactors --- 2173,2208 ---- * clauses that apply to this join * mergeclause_list is a list of RestrictInfo nodes for available * mergejoin clauses in this join + * mergejoin_allowed is a flag to indicate whether mergejoins are allowed * inner_unique is true if each outer tuple provably matches no more * than one inner tuple * sjinfo is extra info about special joins for selectivity estimation * semifactors is as shown above (only valid for SEMI/ANTI/inner_unique joins) * param_source_rels are OK targets for parameterization of result paths + * + * Remaining fields are set only for possible use by the FDW: + * + * consider_foreignjoin is a flag to indicate whether to give the FDW + * a chance to push down the join to the foreign server + * hashclauses are the RestrictInfos to use as hash clauses in a hashjoin + * mergeclauses are the RestrictInfos to use as merge clauses in a mergejoin + * outersortkeys are the sort pathkeys for the outer side of the mergejoin + * innersortkeys are the sort pathkeys for the inner side of the mergejoin */ typedef struct JoinPathExtraData { List *restrictlist; List *mergeclause_list; + bool mergejoin_allowed; bool inner_unique; SpecialJoinInfo *sjinfo; SemiAntiJoinFactors semifactors; Relids param_source_rels; + bool consider_foreignjoin; + List *hashclauses; + List *mergeclauses; + List *outersortkeys; + List *innersortkeys; } JoinPathExtraData; /*