Thanks Robert for the patch.
> OK, here's a patch. What I did is: > > 1. For a regular FDW scan, zero the xmin, xmax, and cid of the tuple > before returning it from postgres_fdw, so that we don't expose the > datum-tuple fields. I can't see any reason this isn't safe, but I > might be missing something. > > 2. When a join is pushed down, deparse system columns using something > like "CASE WHEN r1.* IS NOT NULL THEN 0 END", except for the table OID > column, which gets deparsed with the table OID in place of 0. This > delivers the correct behavior in the presence of outer joins. > > Review appreciated. > It looks good to me. It might be good to explain why we don't add CASE .. END statement when qualify_col is false, i.e. "qualify_col being false indicates that there is only one relation involved thus no join." BTW, I noticed that we are deparsing whole-row reference as ROW(list of columns from local definition of foreign table), which has the same problem with outer joins. It won't be NULL when the rest of the row from that relation is NULL in an outer join. It too needs to be encapsulated in CASE WHEN .. END expression. PFA patch with that fix included and also some testcases for system columns as well as whole-row references. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index bdc410d..35c27e7 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -1564,27 +1564,52 @@ deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs) * If it has a column_name FDW option, use that instead of attribute name. * * If qualify_col is true, qualify column name with the alias of relation. */ static void deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root, bool qualify_col) { RangeTblEntry *rte; - /* varattno can be a whole-row reference, ctid or a regular table column */ if (varattno == SelfItemPointerAttributeNumber) { + /* We support fetching the remote side's CTID. */ if (qualify_col) ADD_REL_QUALIFIER(buf, varno); appendStringInfoString(buf, "ctid"); } + else if (varattno < 0) + { + /* + * All other system attributes are fetched as 0, except for table OID, + * which is fetched as the local table OID. However, we must be + * careful; the table could be beneath an outer join, in which case + * it must go to NULL whenever the rest of the row does. + */ + Oid fetchval = 0; + + if (varattno == TableOidAttributeNumber) + { + rte = planner_rt_fetch(varno, root); + fetchval = rte->relid; + } + + if (qualify_col) + { + appendStringInfoString(buf, "CASE WHEN "); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfo(buf, "* IS NOT NULL THEN %u END", fetchval); + } + else + appendStringInfo(buf, "%u", fetchval); + } else if (varattno == 0) { /* Whole row reference */ Relation rel; Bitmapset *attrs_used; /* Required only to be passed down to deparseTargetList(). */ List *retrieved_attrs; /* Get RangeTblEntry from array in PlannerInfo. */ @@ -1599,24 +1624,43 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root, /* * The local name of the foreign table can not be recognized by the * foreign server and the table it references on foreign server might * have different column ordering or different columns than those * declared locally. Hence we have to deparse whole-row reference as * ROW(columns referenced locally). Construct this by deparsing a * "whole row" attribute. */ attrs_used = bms_add_member(NULL, 0 - FirstLowInvalidHeapAttributeNumber); + + /* + * In case the whole-row reference is under an outer join then it has to + * go NULL whenver the rest of the row goes NULL. Deparsing a join query + * would always involve multiple relations, thus qualify_col would be + * true. + */ + if (qualify_col) + { + appendStringInfoString(buf, "CASE WHEN "); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfo(buf, "* IS NOT NULL THEN "); + } + appendStringInfoString(buf, "ROW("); deparseTargetList(buf, root, varno, rel, false, attrs_used, qualify_col, &retrieved_attrs); appendStringInfoString(buf, ")"); + + /* Complete the CASE WHEN statement started above. */ + if (qualify_col) + appendStringInfo(buf," END"); + heap_close(rel, NoLock); bms_free(attrs_used); } else { char *colname = NULL; List *options; ListCell *lc; /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 50f1261..8eccc3f 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -1368,30 +1368,30 @@ SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 | 3 | 9 | 15 | 21 (10 rows) -- join two tables with FOR UPDATE clause -- tests whole-row reference for row marks EXPLAIN (COSTS false, VERBOSE) 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> 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, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) 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 + Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN r1.* 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.* 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) -> 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 @@ -1412,30 +1412,30 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t 105 | 105 106 | 106 107 | 107 108 | 108 109 | 109 110 | 110 (10 rows) EXPLAIN (COSTS false, VERBOSE) 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> 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, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) 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 + Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN r1.* 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.* 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) -> 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 @@ -1457,30 +1457,30 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t 106 | 106 107 | 107 108 | 108 109 | 109 110 | 110 (10 rows) -- join two tables with FOR SHARE clause EXPLAIN (COSTS false, VERBOSE) 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> 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, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) 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 + Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN r1.* 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.* 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) -> 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 @@ -1501,30 +1501,30 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t 105 | 105 106 | 106 107 | 107 108 | 108 109 | 109 110 | 110 (10 rows) EXPLAIN (COSTS false, VERBOSE) 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 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> 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, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r2."C 1", ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) 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 + Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN r1.* 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.* 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) -> 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 @@ -1580,30 +1580,209 @@ WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 106 | 106 107 | 107 108 | 108 109 | 109 110 | 110 (10 rows) -- ctid with whole-row reference EXPLAIN (COSTS false, VERBOSE) 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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, ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8), r1."C 1", r1.c3, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) 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 + Remote SQL: SELECT r1.ctid, CASE WHEN r1.* 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.* 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) +-- system columns other than ctid whose values of foreign server are meaningless +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t1.tableoid::regclass, t2.cmax, t2.tableoid::regclass, t2.c1 FROM ft4 t1 JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t1.cmin, t1.xmin, t1.xmax, ((t1.tableoid)::regclass), t2.cmax, ((t2.tableoid)::regclass), t2.c1 + -> Foreign Scan + Output: t1.c1, t1.cmin, t1.xmin, t1.xmax, (t1.tableoid)::regclass, t2.cmax, (t2.tableoid)::regclass, t2.c1 + Relations: (public.ft4 t1) INNER JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r1.* IS NOT NULL THEN 16444 END, CASE WHEN r2.* IS NOT NULL THEN 0 END, CASE WHEN r2.* IS NOT NULL THEN 16447 END, r2.c1 FROM ("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (TRUE)) WHERE ((r1.c1 = r2.c1)) ORDER BY r1.c1 ASC NULLS LAST +(6 rows) + +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t1.tableoid::regclass, t2.cmax, t2.tableoid::regclass, t2.c1 FROM ft4 t1 JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; + c1 | cmin | xmin | xmax | tableoid | cmax | tableoid | c1 +----+------+------+------+----------+------+----------+---- + 66 | 0 | 0 | 0 | ft4 | 0 | ft5 | 66 + 72 | 0 | 0 | 0 | ft4 | 0 | ft5 | 72 + 78 | 0 | 0 | 0 | ft4 | 0 | ft5 | 78 + 84 | 0 | 0 | 0 | ft4 | 0 | ft5 | 84 + 90 | 0 | 0 | 0 | ft4 | 0 | ft5 | 90 +(5 rows) + +-- system columns other than ctid whose values of foreign server are meaningless +-- on nullable side of join +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 + -> Foreign Scan + Output: t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r2.* IS NOT NULL THEN 0 END, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST +(6 rows) + +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; + c1 | cmin | xmin | xmax | cmax | c1 +----+------+------+------+------+---- + 22 | 0 | 0 | 0 | | + 24 | 0 | 0 | 0 | 0 | 24 + 26 | 0 | 0 | 0 | | + 28 | 0 | 0 | 0 | | + 30 | 0 | 0 | 0 | 0 | 30 +(5 rows) + +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 + -> Foreign Scan + Output: t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r1.* IS NOT NULL THEN 0 END, CASE WHEN r2.* IS NOT NULL THEN 0 END, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST +(6 rows) + +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + c1 | cmin | xmin | xmax | cmax | c1 +-----+------+------+------+------+---- + 92 | 0 | 0 | 0 | | + 94 | 0 | 0 | 0 | | + 96 | 0 | 0 | 0 | 0 | 96 + 98 | 0 | 0 | 0 | | + 100 | 0 | 0 | 0 | | + | | | | 0 | 3 + | | | | 0 | 9 + | | | | 0 | 15 + | | | | 0 | 21 + | | | | 0 | 27 +(10 rows) + +-- tableoid on nullable side of join +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, ((t1.tableoid)::regclass), ((t2.tableoid)::regclass), t2.c1 + -> Foreign Scan + Output: t1.c1, (t1.tableoid)::regclass, (t2.tableoid)::regclass, t2.c1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, CASE WHEN r1.* IS NOT NULL THEN 16444 END, CASE WHEN r2.* IS NOT NULL THEN 16447 END, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST +(6 rows) + +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; + c1 | tableoid | tableoid | c1 +----+----------+----------+---- + 22 | ft4 | | + 24 | ft4 | ft5 | 24 + 26 | ft4 | | + 28 | ft4 | | + 30 | ft4 | ft5 | 30 +(5 rows) + +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, ((t1.tableoid)::regclass), ((t2.tableoid)::regclass), t2.c1 + -> Foreign Scan + Output: t1.c1, (t1.tableoid)::regclass, (t2.tableoid)::regclass, t2.c1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, CASE WHEN r1.* IS NOT NULL THEN 16444 END, CASE WHEN r2.* IS NOT NULL THEN 16447 END, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST +(6 rows) + +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + c1 | tableoid | tableoid | c1 +-----+----------+----------+---- + 92 | ft4 | | + 94 | ft4 | | + 96 | ft4 | ft5 | 96 + 98 | ft4 | | + 100 | ft4 | | + | | ft5 | 3 + | | ft5 | 9 + | | ft5 | 15 + | | ft5 | 21 + | | ft5 | 27 +(10 rows) + +-- whole-row reference on nullable side of OUTER join +EXPLAIN (COSTS false, VERBOSE) +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.*, t1.c1, t2.*, t2.c1 + -> Foreign Scan + Output: t1.*, t1.c1, t2.*, t2.c1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT CASE WHEN r1.* IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, CASE WHEN r2.* IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST +(6 rows) + +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + t1 | c1 | t2 | c1 +----------------+----+----------------+---- + (22,23,AAA022) | 22 | | + (24,25,AAA024) | 24 | (24,25,AAA024) | 24 + (26,27,AAA026) | 26 | | + (28,29,AAA028) | 28 | | + (30,31,AAA030) | 30 | (30,31,AAA030) | 30 + (32,33,AAA032) | 32 | | + (34,35,AAA034) | 34 | | + (36,37,AAA036) | 36 | (36,37,AAA036) | 36 + (38,39,AAA038) | 38 | | + (40,41,AAA040) | 40 | | +(10 rows) + +EXPLAIN (COSTS false, VERBOSE) +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.*, t1.c1, t2.*, t2.c1 + -> Foreign Scan + Output: t1.*, t1.c1, t2.*, t2.c1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT CASE WHEN r1.* IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, CASE WHEN r2.* IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST +(6 rows) + +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + t1 | c1 | t2 | c1 +------------------+-----+----------------+---- + (92,93,AAA092) | 92 | | + (94,95,AAA094) | 94 | | + (96,97,AAA096) | 96 | (96,97,AAA096) | 96 + (98,99,AAA098) | 98 | | + (100,101,AAA100) | 100 | | + | | (3,4,AAA003) | 3 + | | (9,10,AAA009) | 9 + | | (15,16,AAA015) | 15 + | | (21,22,AAA021) | 21 + | | (27,28,AAA027) | 27 +(10 rows) + -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; QUERY PLAN --------------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Merge Semi Join Output: t1.c1 Merge Cond: (t1.c1 = t2.c1) @@ -2685,28 +2864,28 @@ UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo 1007 | 507 | 0000700007_update7 | | | | ft2 | 1017 | 507 | 0001700017_update7 | | | | ft2 | (102 rows) 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) 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 + Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c8, r1.ctid, CASE WHEN r2.* 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) -> 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 @@ -2828,28 +3007,28 @@ DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; 975 | Tue Mar 17 00:00:00 1970 PST 985 | Fri Mar 27 00:00:00 1970 PST 995 | Mon Apr 06 00:00:00 1970 PST 1005 | 1015 | 1105 | (103 rows) 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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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, ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) 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 + Remote SQL: SELECT r1.ctid, CASE WHEN r2.* 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) -> 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 diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index ee0220a..066cffb 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -4403,20 +4403,32 @@ make_tuple_from_result_row(PGresult *res, /* * If we have a CTID to return, install it in both t_self and t_ctid. * t_self is the normal place, but if the tuple is converted to a * composite Datum, t_self will be lost; setting t_ctid allows CTID to be * preserved during EvalPlanQual re-evaluations (see ROW_MARK_COPY code). */ if (ctid) tuple->t_self = tuple->t_data->t_ctid = *ctid; + /* + * Stomp on the xmin, xmax, and cmin fields from the tuple created by + * heap_form_tuple. heap_form_tuple actually creates the tuple with + * DatumTupleFields, not HeapTupleFields, but the executor expects + * HeapTupleFields and will happily extract system columns on that + * assumption. If we don't do this then, for example, the tuple length + * ends up in the xmin field, which isn't what we want. + */ + HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId); + HeapTupleHeaderSetXmin(tuple->t_data, InvalidTransactionId); + HeapTupleHeaderSetCmin(tuple->t_data, InvalidTransactionId); + /* Clean up */ MemoryContextReset(temp_context); return tuple; } /* * Callback function which is called when error occurs during column value * conversion. Print names of column and relation. */ diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index f420b23..6b240c4 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -419,20 +419,46 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t EXPLAIN (COSTS false, VERBOSE) 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; -- join in CTE EXPLAIN (COSTS false, VERBOSE) 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; 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; -- ctid with whole-row reference EXPLAIN (COSTS false, VERBOSE) 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; +-- system columns other than ctid whose values of foreign server are meaningless +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t1.tableoid::regclass, t2.cmax, t2.tableoid::regclass, t2.c1 FROM ft4 t1 JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t1.tableoid::regclass, t2.cmax, t2.tableoid::regclass, t2.c1 FROM ft4 t1 JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; +-- system columns other than ctid whose values of foreign server are meaningless +-- on nullable side of join +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +SELECT t1.c1, t1.cmin, t1.xmin, t1.xmax, t2.cmax, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +-- tableoid on nullable side of join +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 5; +EXPLAIN (COSTS false, VERBOSE) +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +SELECT t1.c1, t1.tableoid::regclass, t2.tableoid::regclass, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +-- whole-row reference on nullable side of OUTER join +EXPLAIN (COSTS false, VERBOSE) +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +EXPLAIN (COSTS false, VERBOSE) +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +SELECT t1, t1.c1, t2, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; -- CROSS JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE)
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers