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

Reply via email to