-- ===================================================================
-- create FDW objects
-- ===================================================================
CREATE EXTENSION postgres_fdw;
CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw;
DO $d$
    BEGIN
        EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
            OPTIONS (dbname '$$||current_database()||$$',
                     port '$$||current_setting('port')||$$'
            )$$;
        EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw
            OPTIONS (dbname '$$||current_database()||$$',
                     port '$$||current_setting('port')||$$'
            )$$;
    END;
$d$;
CREATE USER MAPPING FOR public SERVER testserver1
	OPTIONS (user 'value', password 'value');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;
-- ===================================================================
-- create objects used through FDW loopback server
-- ===================================================================
CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz');
CREATE SCHEMA "S 1";
CREATE TABLE "S 1"."T 1" (
	"C 1" int NOT NULL,
	c2 int NOT NULL,
	c3 text,
	c4 timestamptz,
	c5 timestamp,
	c6 varchar(10),
	c7 char(10),
	c8 user_enum,
	CONSTRAINT t1_pkey PRIMARY KEY ("C 1")
);
CREATE TABLE "S 1"."T 2" (
	c1 int NOT NULL,
	c2 text,
	CONSTRAINT t2_pkey PRIMARY KEY (c1)
);
CREATE TABLE "S 1"."T 3" (
	c1 int NOT NULL,
	c2 int NOT NULL,
	c3 text,
	CONSTRAINT t3_pkey PRIMARY KEY (c1)
);
CREATE TABLE "S 1"."T 4" (
	c1 int NOT NULL,
	c2 int NOT NULL,
	c3 text,
	CONSTRAINT t4_pkey PRIMARY KEY (c1)
);
-- Disable autovacuum for these tables to avoid unexpected effects of that
ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false');
ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false');
ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false');
ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false');
INSERT INTO "S 1"."T 1"
	SELECT id,
	       id % 10,
	       to_char(id, 'FM00000'),
	       '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
	       '1970-01-01'::timestamp + ((id % 100) || ' days')::interval,
	       id % 10,
	       id % 10,
	       'foo'::user_enum
	FROM generate_series(1, 1000) id;
INSERT INTO "S 1"."T 2"
	SELECT id,
	       'AAA' || to_char(id, 'FM000')
	FROM generate_series(1, 100) id;
INSERT INTO "S 1"."T 3"
	SELECT id,
	       id + 1,
	       'AAA' || to_char(id, 'FM000')
	FROM generate_series(1, 100) id;
DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0;	-- delete for outer join tests
INSERT INTO "S 1"."T 4"
	SELECT id,
	       id + 1,
	       'AAA' || to_char(id, 'FM000')
	FROM generate_series(1, 100) id;
DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0;	-- delete for outer join tests
ANALYZE "S 1"."T 1";
ANALYZE "S 1"."T 2";
ANALYZE "S 1"."T 3";
ANALYZE "S 1"."T 4";
-- ===================================================================
-- create foreign tables
-- ===================================================================
CREATE FOREIGN TABLE ft1 (
	c0 int,
	c1 int NOT NULL,
	c2 int NOT NULL,
	c3 text,
	c4 timestamptz,
	c5 timestamp,
	c6 varchar(10),
	c7 char(10) default 'ft1',
	c8 user_enum
) SERVER loopback;
ALTER FOREIGN TABLE ft1 DROP COLUMN c0;
CREATE FOREIGN TABLE ft2 (
	c1 int NOT NULL,
	c2 int NOT NULL,
	cx int,
	c3 text,
	c4 timestamptz,
	c5 timestamp,
	c6 varchar(10),
	c7 char(10) default 'ft2',
	c8 user_enum
) SERVER loopback;
ALTER FOREIGN TABLE ft2 DROP COLUMN cx;
CREATE FOREIGN TABLE ft4 (
	c1 int NOT NULL,
	c2 int NOT NULL,
	c3 text
) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3');
CREATE FOREIGN TABLE ft5 (
	c1 int NOT NULL,
	c2 int NOT NULL,
	c3 text
) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4');
CREATE FOREIGN TABLE ft6 (
	c1 int NOT NULL,
	c2 int NOT NULL,
	c3 text
) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4');
-- ===================================================================
-- tests for validator
-- ===================================================================
-- requiressl, krbsrvname and gsslib are omitted because they depend on
-- configure options
ALTER SERVER testserver1 OPTIONS (
	use_remote_estimate 'false',
	updatable 'true',
	fdw_startup_cost '123.456',
	fdw_tuple_cost '0.123',
	service 'value',
	connect_timeout 'value',
	dbname 'value',
	host 'value',
	hostaddr 'value',
	port 'value',
	--client_encoding 'value',
	application_name 'value',
	--fallback_application_name 'value',
	keepalives 'value',
	keepalives_idle 'value',
	keepalives_interval 'value',
	tcp_user_timeout 'value',
	-- requiressl 'value',
	sslcompression 'value',
	sslmode 'value',
	sslcert 'value',
	sslkey 'value',
	sslrootcert 'value',
	sslcrl 'value'
	--requirepeer 'value',
	-- krbsrvname 'value',
	-- gsslib 'value',
	--replication 'value'
);
-- Error, invalid list syntax
ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar');
ERROR:  parameter "extensions" must be a list of extension names
-- OK but gets a warning
ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar');
WARNING:  extension "foo" is not installed
WARNING:  extension "bar" is not installed
ALTER SERVER testserver1 OPTIONS (DROP extensions);
ALTER USER MAPPING FOR public SERVER testserver1
	OPTIONS (DROP user, DROP password);
ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
\det+
                              List of foreign tables
 Schema | Table |  Server   |              FDW options              | Description 
--------+-------+-----------+---------------------------------------+-------------
 public | ft1   | loopback  | (schema_name 'S 1', table_name 'T 1') | 
 public | ft2   | loopback  | (schema_name 'S 1', table_name 'T 1') | 
 public | ft4   | loopback  | (schema_name 'S 1', table_name 'T 3') | 
 public | ft5   | loopback  | (schema_name 'S 1', table_name 'T 4') | 
 public | ft6   | loopback2 | (schema_name 'S 1', table_name 'T 4') | 
(5 rows)

-- Test that alteration of server options causes reconnection
-- Remote's errors might be non-English, so hide them to ensure stable results
\set VERBOSITY terse
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1;  -- should work
  c3   |              c4              
-------+------------------------------
 00001 | Fri Jan 02 00:00:00 1970 PST
(1 row)

ALTER SERVER loopback OPTIONS (SET dbname 'no such database');
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1;  -- should fail
ERROR:  could not connect to server "loopback"
DO $d$
    BEGIN
        EXECUTE $$ALTER SERVER loopback
            OPTIONS (SET dbname '$$||current_database()||$$')$$;
    END;
$d$;
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1;  -- should work again
  c3   |              c4              
-------+------------------------------
 00001 | Fri Jan 02 00:00:00 1970 PST
(1 row)

-- Test that alteration of user mapping options causes reconnection
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback
  OPTIONS (ADD user 'no such user');
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1;  -- should fail
ERROR:  could not connect to server "loopback"
ALTER USER MAPPING FOR CURRENT_USER SERVER loopback
  OPTIONS (DROP user);
SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1;  -- should work again
  c3   |              c4              
-------+------------------------------
 00001 | Fri Jan 02 00:00:00 1970 PST
(1 row)

\set VERBOSITY default
-- Now we should be able to run ANALYZE.
-- To exercise multiple code paths, we use local stats on ft1
-- and remote-estimate mode on ft2.
ANALYZE ft1;
ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true');
-- ===================================================================
-- simple queries
-- ===================================================================
-- single table without alias
EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
     QUERY PLAN      
---------------------
 Foreign Scan on ft1
(1 row)

SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
 c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-----+----+-------+------------------------------+--------------------------+----+------------+-----
 101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 102 |  2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
 103 |  3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
 104 |  4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
 105 |  5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
 106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 107 |  7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
 108 |  8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
 109 |  9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | 9          | foo
 110 |  0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0  | 0          | foo
(10 rows)

-- single table with alias - also test that tableoid sort is not pushed to remote side
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10;
                                     QUERY PLAN                                      
-------------------------------------------------------------------------------------
 Limit
   Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid
   ->  Sort
         Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid
         Sort Key: t1.c3, t1.c1, t1.tableoid
         ->  Foreign Scan on public.ft1 t1
               Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid
               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
(8 rows)

SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10;
 c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-----+----+-------+------------------------------+--------------------------+----+------------+-----
 101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
 102 |  2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
 103 |  3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
 104 |  4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
 105 |  5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
 106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 107 |  7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
 108 |  8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
 109 |  9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | 9          | foo
 110 |  0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0  | 0          | foo
(10 rows)

-- whole-row reference
EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
                                                                          QUERY PLAN                                                                          
--------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: t1.*, c3, c1
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint
(3 rows)

SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
                                             t1                                             
--------------------------------------------------------------------------------------------
 (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1         ",foo)
 (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2         ",foo)
 (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3         ",foo)
 (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4         ",foo)
 (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5         ",foo)
 (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6         ",foo)
 (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7         ",foo)
 (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8         ",foo)
 (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9         ",foo)
 (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0         ",foo)
(10 rows)

-- empty result
SELECT * FROM ft1 WHERE false;
 c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 
----+----+----+----+----+----+----+----
(0 rows)

-- with WHERE clause
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
                                                                   QUERY PLAN                                                                   
------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text))
(3 rows)

SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
 c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-----+----+-------+------------------------------+--------------------------+----+------------+-----
 101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

-- with FOR UPDATE/SHARE
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
                                                QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.*
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE
(3 rows)

SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
 c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-----+----+-------+------------------------------+--------------------------+----+------------+-----
 101 |  1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
                                               QUERY PLAN                                                
---------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.*
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE
(3 rows)

SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
 c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-----+----+-------+------------------------------+--------------------------+----+------------+-----
 102 |  2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
(1 row)

-- aggregate
SELECT COUNT(*) FROM ft1 t1;
 count 
-------
  1000
(1 row)

-- subquery
SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
  4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
  5 |  5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
  7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  8 |  8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  9 |  9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | 9          | foo
 10 |  0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0  | 0          | foo
(10 rows)

-- subquery+MAX
SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
  c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
------+----+-------+------------------------------+--------------------------+----+------------+-----
 1000 |  0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0  | 0          | foo
(1 row)

-- used in CTE
WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
 c1 | c2 |  c3   |              c4              
----+----+-------+------------------------------
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST
  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST
  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST
  4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST
  5 |  5 | 00005 | Tue Jan 06 00:00:00 1970 PST
  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST
  7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST
  8 |  8 | 00008 | Fri Jan 09 00:00:00 1970 PST
  9 |  9 | 00009 | Sat Jan 10 00:00:00 1970 PST
 10 |  0 | 00010 | Sun Jan 11 00:00:00 1970 PST
(10 rows)

-- fixed values
SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
 ?column? | ?column? 
----------+----------
 fixed    | 
(1 row)

-- Test forcing the remote server to produce sorted data for a merge join.
SET enable_hashjoin TO false;
SET enable_nestloop TO false;
-- inner join; expressions in the clauses appear in the equivalence class list
EXPLAIN (VERBOSE, COSTS OFF)
	SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2."C 1"
   ->  Merge Join
         Output: t1.c1, t2."C 1"
         Inner Unique: true
         Merge Cond: (t1.c1 = t2."C 1")
         ->  Foreign Scan on public.ft2 t1
               Output: t1.c1
               Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
         ->  Index Only Scan using t1_pkey on "S 1"."T 1" t2
               Output: t2."C 1"
(11 rows)

SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
 c1  | C 1 
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

-- outer join; expressions in the clauses do not appear in equivalence class
-- list but no output change as compared to the previous query
EXPLAIN (VERBOSE, COSTS OFF)
	SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2."C 1"
   ->  Merge Left Join
         Output: t1.c1, t2."C 1"
         Inner Unique: true
         Merge Cond: (t1.c1 = t2."C 1")
         ->  Foreign Scan on public.ft2 t1
               Output: t1.c1
               Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
         ->  Index Only Scan using t1_pkey on "S 1"."T 1" t2
               Output: t2."C 1"
(11 rows)

SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10;
 c1  | C 1 
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

-- A join between local table and foreign join. ORDER BY clause is added to the
-- foreign join so that the local table can be joined using merge join strategy.
EXPLAIN (VERBOSE, COSTS OFF)
	SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
                                                                       QUERY PLAN                                                                        
---------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1."C 1"
   ->  Merge Right Join
         Output: t1."C 1"
         Inner Unique: true
         Merge Cond: (t3.c1 = t1."C 1")
         ->  Foreign Scan
               Output: t3.c1
               Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
               Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST
         ->  Index Only Scan using t1_pkey on "S 1"."T 1" t1
               Output: t1."C 1"
(12 rows)

SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
 C 1 
-----
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
(10 rows)

-- Test similar to above, except that the full join prevents any equivalence
-- classes from being merged. This produces single relation equivalence classes
-- included in join restrictions.
EXPLAIN (VERBOSE, COSTS OFF)
	SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
                                                                            QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1."C 1", t2.c1, t3.c1
   ->  Merge Right Join
         Output: t1."C 1", t2.c1, t3.c1
         Inner Unique: true
         Merge Cond: (t3.c1 = t1."C 1")
         ->  Foreign Scan
               Output: t3.c1, t2.c1
               Relations: (public.ft2 t3) LEFT JOIN (public.ft1 t2)
               Remote SQL: SELECT r3."C 1", r2."C 1" FROM ("S 1"."T 1" r3 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST
         ->  Index Only Scan using t1_pkey on "S 1"."T 1" t1
               Output: t1."C 1"
(12 rows)

SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
 C 1 | c1  | c1  
-----+-----+-----
 101 | 101 | 101
 102 | 102 | 102
 103 | 103 | 103
 104 | 104 | 104
 105 | 105 | 105
 106 | 106 | 106
 107 | 107 | 107
 108 | 108 | 108
 109 | 109 | 109
 110 | 110 | 110
(10 rows)

-- Test similar to above with all full outer joins
EXPLAIN (VERBOSE, COSTS OFF)
	SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
                                                                            QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1."C 1", t2.c1, t3.c1
   ->  Merge Full Join
         Output: t1."C 1", t2.c1, t3.c1
         Inner Unique: true
         Merge Cond: (t3.c1 = t1."C 1")
         ->  Foreign Scan
               Output: t2.c1, t3.c1
               Relations: (public.ft1 t2) FULL JOIN (public.ft2 t3)
               Remote SQL: SELECT r2."C 1", r3."C 1" FROM ("S 1"."T 1" r2 FULL JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST
         ->  Index Only Scan using t1_pkey on "S 1"."T 1" t1
               Output: t1."C 1"
(12 rows)

SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10;
 C 1 | c1  | c1  
-----+-----+-----
 101 | 101 | 101
 102 | 102 | 102
 103 | 103 | 103
 104 | 104 | 104
 105 | 105 | 105
 106 | 106 | 106
 107 | 107 | 107
 108 | 108 | 108
 109 | 109 | 109
 110 | 110 | 110
(10 rows)

RESET enable_hashjoin;
RESET enable_nestloop;
-- ===================================================================
-- WHERE with remotely-executable conditions
-- ===================================================================
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1;         -- Var, OpExpr(b), Const
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
                                                  QUERY PLAN                                                  
--------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL;        -- NullTest
                                           QUERY PLAN                                            
-------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL;    -- NullTest
                                             QUERY PLAN                                              
-----------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
                                                     QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1;          -- OpExpr(l)
                                             QUERY PLAN                                              
-----------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1")))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE 1 = c1!;           -- OpExpr(r)
                                                QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((1::numeric = ("C 1" !)))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
                                                                 QUERY PLAN                                                                 
--------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL)))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)])))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef
                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1])))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar';  -- check special chars
                                                 QUERY PLAN                                                  
-------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar'::text))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo';  -- can't be sent to remote
                               QUERY PLAN                                
-------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Filter: (t1.c8 = 'foo'::user_enum)
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
(4 rows)

-- parameterized remote path for foreign table
EXPLAIN (VERBOSE, COSTS OFF)
  SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2;
                                                 QUERY PLAN                                                  
-------------------------------------------------------------------------------------------------------------
 Nested Loop
   Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
   ->  Index Scan using t1_pkey on "S 1"."T 1" a
         Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8
         Index Cond: (a."C 1" = 47)
   ->  Foreign Scan on public.ft2 b
         Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1"))
(8 rows)

SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  | c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----
 47 |  7 | 00047 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo |  7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
(1 row)

-- check both safe and unsafe join conditions
EXPLAIN (VERBOSE, COSTS OFF)
  SELECT * FROM ft2 a, ft2 b
  WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
                                                 QUERY PLAN                                                  
-------------------------------------------------------------------------------------------------------------
 Nested Loop
   Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
   ->  Foreign Scan on public.ft2 a
         Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8
         Filter: (a.c8 = 'foo'::user_enum)
         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6))
   ->  Foreign Scan on public.ft2 b
         Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8
         Filter: (upper((a.c7)::text) = (b.c7)::text)
         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1"))
(10 rows)

SELECT * FROM ft2 a, ft2 b
WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
 c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  | c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-----+----+-------+------------------------------+--------------------------+----+------------+-----+-----+----+-------+------------------------------+--------------------------+----+------------+-----
   6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo |   6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
  16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo |  16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
  26 |  6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo |  26 |  6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
  36 |  6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo |  36 |  6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
  46 |  6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo |  46 |  6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
  56 |  6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo |  56 |  6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
  66 |  6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo |  66 |  6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
  76 |  6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo |  76 |  6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
  86 |  6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo |  86 |  6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
  96 |  6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo |  96 |  6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 106 |  6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 116 |  6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 116 |  6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 126 |  6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 126 |  6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 136 |  6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 136 |  6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 146 |  6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 146 |  6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 156 |  6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 156 |  6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 166 |  6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 166 |  6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 176 |  6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 176 |  6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 186 |  6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 186 |  6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 196 |  6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 196 |  6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 206 |  6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 206 |  6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 216 |  6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 216 |  6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 226 |  6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 226 |  6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 236 |  6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 236 |  6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 246 |  6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 246 |  6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 256 |  6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 256 |  6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 266 |  6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 266 |  6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 276 |  6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 276 |  6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 286 |  6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 286 |  6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 296 |  6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 296 |  6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 306 |  6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 306 |  6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 316 |  6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 316 |  6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 326 |  6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 326 |  6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 336 |  6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 336 |  6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 346 |  6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 346 |  6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 356 |  6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 356 |  6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 366 |  6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 366 |  6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 376 |  6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 376 |  6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 386 |  6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 386 |  6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 396 |  6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 396 |  6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 406 |  6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 406 |  6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 416 |  6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 416 |  6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 426 |  6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 426 |  6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 436 |  6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 436 |  6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 446 |  6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 446 |  6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 456 |  6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 456 |  6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 466 |  6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 466 |  6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 476 |  6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 476 |  6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 486 |  6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 486 |  6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 496 |  6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 496 |  6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 506 |  6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 506 |  6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 516 |  6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 516 |  6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 526 |  6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 526 |  6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 536 |  6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 536 |  6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 546 |  6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 546 |  6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 556 |  6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 556 |  6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 566 |  6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 566 |  6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 576 |  6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 576 |  6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 586 |  6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 586 |  6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 596 |  6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 596 |  6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 606 |  6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 606 |  6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 616 |  6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 616 |  6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 626 |  6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 626 |  6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 636 |  6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 636 |  6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 646 |  6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 646 |  6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 656 |  6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 656 |  6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 666 |  6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 666 |  6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 676 |  6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 676 |  6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 686 |  6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 686 |  6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 696 |  6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 696 |  6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 706 |  6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 706 |  6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 716 |  6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 716 |  6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 726 |  6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 726 |  6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 736 |  6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 736 |  6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 746 |  6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 746 |  6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 756 |  6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 756 |  6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 766 |  6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 766 |  6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 776 |  6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 776 |  6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 786 |  6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 786 |  6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 796 |  6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 796 |  6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 806 |  6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 806 |  6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 816 |  6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 816 |  6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 826 |  6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 826 |  6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 836 |  6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 836 |  6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 846 |  6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 846 |  6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 856 |  6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 856 |  6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 866 |  6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 866 |  6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 876 |  6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 876 |  6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 886 |  6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 886 |  6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 896 |  6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 896 |  6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
 906 |  6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo | 906 |  6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
 916 |  6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 916 |  6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
 926 |  6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 926 |  6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo
 936 |  6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 936 |  6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo
 946 |  6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 946 |  6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo
 956 |  6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 956 |  6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo
 966 |  6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 966 |  6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo
 976 |  6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 976 |  6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo
 986 |  6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 986 |  6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo
 996 |  6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 996 |  6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo
(100 rows)

-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters
SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5));
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
  4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
(4 rows)

SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5));
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
  4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
(4 rows)

-- we should not push order by clause with volatile expressions or unsafe
-- collations
EXPLAIN (VERBOSE, COSTS OFF)
	SELECT * FROM ft2 ORDER BY ft2.c1, random();
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Sort
   Output: c1, c2, c3, c4, c5, c6, c7, c8, (random())
   Sort Key: ft2.c1, (random())
   ->  Foreign Scan on public.ft2
         Output: c1, c2, c3, c4, c5, c6, c7, c8, random()
         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
(6 rows)

EXPLAIN (VERBOSE, COSTS OFF)
	SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C";
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Sort
   Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c3)::text)
   Sort Key: ft2.c1, ft2.c3 COLLATE "C"
   ->  Foreign Scan on public.ft2
         Output: c1, c2, c3, c4, c5, c6, c7, c8, c3
         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
(6 rows)

-- user-defined operator/function
CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
BEGIN
RETURN abs($1);
END
$$ LANGUAGE plpgsql IMMUTABLE;
CREATE OPERATOR === (
    LEFTARG = int,
    RIGHTARG = int,
    PROCEDURE = int4eq,
    COMMUTATOR = ===
);
-- built-in operators and functions can be shipped for remote execution
EXPLAIN (VERBOSE, COSTS OFF)
  SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
                                QUERY PLAN                                 
---------------------------------------------------------------------------
 Foreign Scan
   Output: (count(c3))
   Relations: Aggregate on (public.ft1 t1)
   Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2)))
(4 rows)

SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
 count 
-------
     9
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
  SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2;
                              QUERY PLAN                              
----------------------------------------------------------------------
 Foreign Scan
   Output: (count(c3))
   Relations: Aggregate on (public.ft1 t1)
   Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2))
(4 rows)

SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2;
 count 
-------
     9
(1 row)

-- by default, user-defined ones cannot
EXPLAIN (VERBOSE, COSTS OFF)
  SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
                        QUERY PLAN                         
-----------------------------------------------------------
 Aggregate
   Output: count(c3)
   ->  Foreign Scan on public.ft1 t1
         Output: c3
         Filter: (t1.c1 = postgres_fdw_abs(t1.c2))
         Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
(6 rows)

SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
 count 
-------
     9
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
  SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
                        QUERY PLAN                         
-----------------------------------------------------------
 Aggregate
   Output: count(c3)
   ->  Foreign Scan on public.ft1 t1
         Output: c3
         Filter: (t1.c1 === t1.c2)
         Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
(6 rows)

SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
 count 
-------
     9
(1 row)

-- ORDER BY can be shipped, though
EXPLAIN (VERBOSE, COSTS OFF)
  SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
                                                QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
 Limit
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   ->  Foreign Scan on public.ft1 t1
         Output: c1, c2, c3, c4, c5, c6, c7, c8
         Filter: (t1.c1 === t1.c2)
         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST
(6 rows)

SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

-- but let's put them in an extension ...
ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int);
ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int);
ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
-- ... now they can be shipped
EXPLAIN (VERBOSE, COSTS OFF)
  SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
                                          QUERY PLAN                                           
-----------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(c3))
   Relations: Aggregate on (public.ft1 t1)
   Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = public.postgres_fdw_abs(c2)))
(4 rows)

SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
 count 
-------
     9
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
  SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
                                       QUERY PLAN                                        
-----------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(c3))
   Relations: Aggregate on (public.ft1 t1)
   Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2))
(4 rows)

SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
 count 
-------
     9
(1 row)

-- and both ORDER BY and LIMIT can be shipped
EXPLAIN (VERBOSE, COSTS OFF)
  SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
                                                                         QUERY PLAN                                                                         
------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) ORDER BY c2 ASC NULLS LAST LIMIT 1::bigint
(3 rows)

SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

-- ===================================================================
-- JOIN queries
-- ===================================================================
-- Analyze ft4 and ft5 so that we have better statistics. These tables do not
-- have use_remote_estimate set.
ANALYZE ft4;
ANALYZE ft5;
-- join two tables
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;
                                                                                                       QUERY PLAN                                                                                                       
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c1, t1.c3
   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 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 LIMIT 10::bigint OFFSET 100::bigint
(4 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;
 c1  | c1  
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

-- join three tables
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
                                                                                                                                   QUERY PLAN                                                                                                                                    
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3, t1.c3
   Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 22 |  2 | AAA022
 24 |  4 | AAA024
 26 |  6 | AAA026
 28 |  8 | AAA028
 30 |  0 | AAA030
 32 |  2 | AAA032
 34 |  4 | AAA034
 36 |  6 | AAA036
 38 |  8 | AAA038
 40 |  0 | AAA040
(10 rows)

-- left outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, 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                                                                                           
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c1
   Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2)
   Remote SQL: SELECT r1.c1, 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 LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
 c1 | c1 
----+----
 22 |   
 24 | 24
 26 |   
 28 |   
 30 | 30
 32 |   
 34 |   
 36 | 36
 38 |   
 40 |   
(10 rows)

-- left outer join three tables
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 11 |  1 | 
 12 |  2 | AAA012
 13 |  3 | 
 14 |  4 | AAA014
 15 |  5 | 
 16 |  6 | AAA016
 17 |  7 | 
 18 |  8 | AAA018
 19 |  9 | 
 20 |  0 | AAA020
(10 rows)

-- left outer join + placement of clauses.
-- clauses within the nullable side are not pulled up, but top level clause on
-- non-nullable side is pushed into non-nullable side
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10;
                                                                          QUERY PLAN                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t1.c2, ft5.c1, ft5.c2
   Relations: (public.ft4 t1) LEFT JOIN (public.ft5)
   Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10))
(4 rows)

SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10;
 c1 | c2 | c1 | c2 
----+----+----+----
  2 |  3 |    |   
  4 |  5 |    |   
  6 |  7 |  6 |  7
  8 |  9 |    |   
(4 rows)

-- clauses within the nullable side are not pulled up, but the top level clause
-- on nullable side is not pushed down into nullable side
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1)
			WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10;
                                                                                              QUERY PLAN                                                                                               
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t1.c2, ft5.c1, ft5.c2
   Relations: (public.ft4 t1) LEFT JOIN (public.ft5)
   Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10))
(4 rows)

SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1)
			WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10;
 c1 | c2 | c1 | c2 
----+----+----+----
  2 |  3 |    |   
  4 |  5 |    |   
  6 |  7 |  6 |  7
  8 |  9 |    |   
(4 rows)

-- right outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10;
                                                                                           QUERY PLAN                                                                                           
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c1
   Relations: (public.ft4 t2) LEFT JOIN (public.ft5 t1)
   Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10;
 c1 | c1 
----+----
    | 22
 24 | 24
    | 26
    | 28
 30 | 30
    | 32
    | 34
 36 | 36
    | 38
    | 40
(10 rows)

-- right outer join three tables
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 22 |  2 | AAA022
 24 |  4 | AAA024
 26 |  6 | AAA026
 28 |  8 | AAA028
 30 |  0 | AAA030
 32 |  2 | AAA032
 34 |  4 | AAA034
 36 |  6 | AAA036
 38 |  8 | AAA038
 40 |  0 | AAA040
(10 rows)

-- full outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, 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                                                                                           
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c1
   Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2)
   Remote SQL: SELECT r1.c1, 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 LIMIT 10::bigint OFFSET 45::bigint
(4 rows)

SELECT t1.c1, 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  | c1 
-----+----
  92 |   
  94 |   
  96 | 96
  98 |   
 100 |   
     |  3
     |  9
     | 15
     | 21
     | 27
(10 rows)

-- full outer join with restrictions on the joining relations
-- a. the joining relations are both base relations
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1;
                                                                                                                                  QUERY PLAN                                                                                                                                   
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: ft4.c1, ft5.c1
   Relations: (public.ft4) FULL JOIN (public.ft5)
   Remote SQL: SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s5.c1 ASC NULLS LAST
(4 rows)

SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1;
 c1 | c1 
----+----
 50 |   
 52 |   
 54 | 54
 56 |   
 58 |   
 60 | 60
    | 51
    | 57
(8 rows)

EXPLAIN (VERBOSE, COSTS OFF)
SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10;
                                                                                                             QUERY PLAN                                                                                                              
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: 1
   Relations: (public.ft4) FULL JOIN (public.ft5)
   Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10;
 ?column? 
----------
        1
        1
        1
        1
        1
        1
        1
        1
        1
        1
(10 rows)

-- b. one of the joining relations is a base relation and the other is a join
-- relation
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b;
                                                                                                                                                                                     QUERY PLAN                                                                                                                                                                                      
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: ft4.c1, t2.c1, t3.c1
   Relations: (public.ft4) FULL JOIN ((public.ft4 t2) LEFT JOIN (public.ft5 t3))
   Remote SQL: SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s8.c1 ASC NULLS LAST, s8.c2 ASC NULLS LAST
(4 rows)

SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b;
 c1 | a  | b  
----+----+----
 50 | 50 |   
 52 | 52 |   
 54 | 54 | 54
 56 | 56 |   
 58 | 58 |   
 60 | 60 | 60
(6 rows)

-- c. test deparsing the remote query as nested subqueries
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b;
                                                                                                                                                                                                                                                     QUERY PLAN                                                                                                                                                                                                                                                     
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: ft4.c1, ft4_1.c1, ft5.c1
   Relations: (public.ft4) FULL JOIN ((public.ft4) FULL JOIN (public.ft5))
   Remote SQL: SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s10.c1 ASC NULLS LAST, s10.c2 ASC NULLS LAST
(4 rows)

SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b;
 c1 | a  | b  
----+----+----
 50 | 50 |   
 52 | 52 |   
 54 | 54 | 54
 56 | 56 |   
 58 | 58 |   
 60 | 60 | 60
    |    | 51
    |    | 57
(8 rows)

-- d. test deparsing rowmarked relations as subqueries
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1;
                                                                                                                                                                                             QUERY PLAN                                                                                                                                                                                             
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 LockRows
   Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, ft4.*, ft5.*
   ->  Nested Loop
         Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, ft4.*, ft5.*
         ->  Foreign Scan
               Output: ft4.c1, ft4.*, ft5.c1, ft5.*
               Relations: (public.ft4) FULL JOIN (public.ft5)
               Remote SQL: SELECT s8.c1, s8.c2, s9.c1, s9.c2 FROM ((SELECT c1, ROW(c1, c2, c3) FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1, c2) FULL JOIN (SELECT c1, ROW(c1, c2, c3) FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1, c2) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL))) ORDER BY s8.c1 ASC NULLS LAST, s9.c1 ASC NULLS LAST
               ->  Sort
                     Output: ft4.c1, ft4.*, ft5.c1, ft5.*
                     Sort Key: ft4.c1, ft5.c1
                     ->  Hash Full Join
                           Output: ft4.c1, ft4.*, ft5.c1, ft5.*
                           Hash Cond: (ft4.c1 = ft5.c1)
                           Filter: ((ft4.c1 IS NULL) OR (ft4.c1 IS NOT NULL))
                           ->  Foreign Scan on public.ft4
                                 Output: ft4.c1, ft4.*
                                 Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))
                           ->  Hash
                                 Output: ft5.c1, ft5.*
                                 ->  Foreign Scan on public.ft5
                                       Output: ft5.c1, ft5.*
                                       Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))
         ->  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)
(28 rows)

SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1;
 c1 | a  | b  
----+----+----
 50 | 50 |   
 50 | 52 |   
 50 | 54 | 54
 50 | 56 |   
 50 | 58 |   
 50 | 60 | 60
 50 |    | 51
 50 |    | 57
(8 rows)

-- full outer join + inner join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
                                                                                                                                                 QUERY PLAN                                                                                                                                                 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c1, t3.c1
   Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
(4 rows)

SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
 c1 | c1 | c1 
----+----+----
 52 | 51 |   
 58 | 57 |   
    |    |  2
    |    |  4
    |    |  6
    |    |  8
    |    | 10
    |    | 12
    |    | 14
    |    | 16
(10 rows)

-- full outer join three tables
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 11 |  1 | 
 12 |  2 | AAA012
 13 |  3 | 
 14 |  4 | AAA014
 15 |  5 | 
 16 |  6 | AAA016
 17 |  7 | 
 18 |  8 | AAA018
 19 |  9 | 
 20 |  0 | AAA020
(10 rows)

-- full outer join + right outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 22 |  2 | AAA022
 24 |  4 | AAA024
 26 |  6 | AAA026
 28 |  8 | AAA028
 30 |  0 | AAA030
 32 |  2 | AAA032
 34 |  4 | AAA034
 36 |  6 | AAA036
 38 |  8 | AAA038
 40 |  0 | AAA040
(10 rows)

-- right outer join + full outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) FULL JOIN (public.ft4 t3)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 11 |  1 | 
 12 |  2 | AAA012
 13 |  3 | 
 14 |  4 | AAA014
 15 |  5 | 
 16 |  6 | AAA016
 17 |  7 | 
 18 |  8 | AAA018
 19 |  9 | 
 20 |  0 | AAA020
(10 rows)

-- full outer join + left outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 11 |  1 | 
 12 |  2 | AAA012
 13 |  3 | 
 14 |  4 | AAA014
 15 |  5 | 
 16 |  6 | AAA016
 17 |  7 | 
 18 |  8 | AAA018
 19 |  9 | 
 20 |  0 | AAA020
(10 rows)

-- left outer join + full outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 11 |  1 | 
 12 |  2 | AAA012
 13 |  3 | 
 14 |  4 | AAA014
 15 |  5 | 
 16 |  6 | AAA016
 17 |  7 | 
 18 |  8 | AAA018
 19 |  9 | 
 20 |  0 | AAA020
(10 rows)

-- right outer join + left outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) LEFT JOIN (public.ft4 t3)
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 11 |  1 | 
 12 |  2 | AAA012
 13 |  3 | 
 14 |  4 | AAA014
 15 |  5 | 
 16 |  6 | AAA016
 17 |  7 | 
 18 |  8 | AAA018
 19 |  9 | 
 20 |  0 | AAA020
(10 rows)

-- left outer join + right outer join
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
                                                                                                    QUERY PLAN                                                                                                    
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t3.c3
   Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2))
   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10;
 c1 | c2 |   c3   
----+----+--------
 22 |  2 | AAA022
 24 |  4 | AAA024
 26 |  6 | AAA026
 28 |  8 | AAA028
 30 |  0 | AAA030
 32 |  2 | AAA032
 34 |  4 | AAA034
 36 |  6 | AAA036
 38 |  8 | AAA038
 40 |  0 | AAA040
(10 rows)

-- full outer join + WHERE clause, only matched rows
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
                                                                            QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2.c1
   ->  Sort
         Output: t1.c1, t2.c1
         Sort Key: t1.c1, t2.c1
         ->  Foreign Scan
               Output: t1.c1, t2.c1
               Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2)
               Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL)))
(9 rows)

SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
 c1 | c1 
----+----
 66 | 66
 72 | 72
 78 | 78
 84 | 84
 90 | 90
 96 | 96
    |  3
    |  9
    | 15
    | 21
(10 rows)

-- full outer join + WHERE clause with shippable extensions set
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10;
                                                                                                 QUERY PLAN                                                                                                 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c2, t1.c3
   Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2)
   Remote SQL: SELECT r1."C 1", r2.c2, r1.c3 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) WHERE ((public.postgres_fdw_abs(r1."C 1") > 0)) LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

ALTER SERVER loopback OPTIONS (DROP extensions);
-- full outer join + WHERE clause with shippable extensions not set
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10;
                                                          QUERY PLAN                                                           
-------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2.c2, t1.c3
   ->  Foreign Scan
         Output: t1.c1, t2.c2, t1.c3
         Filter: (postgres_fdw_abs(t1.c1) > 0)
         Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2)
         Remote SQL: SELECT r1."C 1", r2.c2, r1.c3 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
(7 rows)

ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
-- join two tables with FOR UPDATE clause
-- tests whole-row reference for row marks
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 UPDATE OF t1;
                                                                                                                                                                                                                           QUERY PLAN                                                                                                                                                                                                                            
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 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", r2."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, 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 LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1
(4 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  
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

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 UPDATE;
                                                                                                                                                                                                                                    QUERY PLAN                                                                                                                                                                                                                                    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 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", r2."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, 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 LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 FOR UPDATE OF r2
(4 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  
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

-- join two tables with FOR SHARE clause
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 OF t1;
                                                                                                                                                                                                                           QUERY PLAN                                                                                                                                                                                                                           
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 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", r2."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, 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 LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1
(4 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  
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

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;
                                                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                                                   
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 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", r2."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, 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 LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 FOR SHARE OF r2
(4 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  
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

-- join in CTE
EXPLAIN (VERBOSE, COSTS OFF)
WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (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;
                                                             QUERY PLAN                                                              
-------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t.c1_1, t.c2_1, t.c1_3
   CTE t
     ->  Foreign Scan
           Output: t1.c1, t1.c3, t2.c1
           Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
           Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
   ->  Sort
         Output: t.c1_1, t.c2_1, t.c1_3
         Sort Key: t.c1_3, t.c1_1
         ->  CTE Scan on t
               Output: t.c1_1, t.c2_1, t.c1_3
(12 rows)

WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (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;
 c1_1 | c2_1 
------+------
  101 |  101
  102 |  102
  103 |  103
  104 |  104
  105 |  105
  106 |  106
  107 |  107
  108 |  108
  109 |  109
  110 |  110
(10 rows)

-- ctid with whole-row reference
EXPLAIN (VERBOSE, COSTS OFF)
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                                                                                                                                                                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 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, 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, 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, r1."C 1", r1.c3 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 LIMIT 10::bigint OFFSET 100::bigint
(4 rows)

-- SEMI JOIN, not pushed down
EXPLAIN (VERBOSE, COSTS OFF)
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)
         ->  Foreign Scan on public.ft1 t1
               Output: t1.c1
               Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
         ->  Foreign Scan on public.ft2 t2
               Output: t2.c1
               Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
(11 rows)

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;
 c1  
-----
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
(10 rows)

-- ANTI JOIN, not pushed down
EXPLAIN (VERBOSE, COSTS OFF)
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;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Limit
   Output: t1.c1
   ->  Merge Anti Join
         Output: t1.c1
         Merge Cond: (t1.c1 = t2.c2)
         ->  Foreign Scan on public.ft1 t1
               Output: t1.c1
               Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST
         ->  Foreign Scan on public.ft2 t2
               Output: t2.c2
               Remote SQL: SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST
(11 rows)

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;
 c1  
-----
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
(10 rows)

-- CROSS JOIN can be pushed down
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
                                                                                           QUERY PLAN                                                                                            
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c1, t2.c1
   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
   Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint
(4 rows)

SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
 c1 | c1  
----+-----
  1 | 101
  1 | 102
  1 | 103
  1 | 104
  1 | 105
  1 | 106
  1 | 107
  1 | 108
  1 | 109
  1 | 110
(10 rows)

-- different server, not pushed down. No result expected.
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2.c1
   ->  Merge Join
         Output: t1.c1, t2.c1
         Merge Cond: (t2.c1 = t1.c1)
         ->  Foreign Scan on public.ft6 t2
               Output: t2.c1, t2.c2, t2.c3
               Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
         ->  Materialize
               Output: t1.c1, t1.c2, t1.c3
               ->  Foreign Scan on public.ft5 t1
                     Output: t1.c1, t1.c2, t1.c3
                     Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST
(13 rows)

SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
 c1 | c1 
----+----
(0 rows)

-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS
-- JOIN since c8 in both tables has same value.
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
                               QUERY PLAN                                
-------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2.c1
   ->  Sort
         Output: t1.c1, t2.c1
         Sort Key: t1.c1, t2.c1
         ->  Merge Left Join
               Output: t1.c1, t2.c1
               Merge Cond: (t1.c8 = t2.c8)
               ->  Sort
                     Output: t1.c1, t1.c8
                     Sort Key: t1.c8
                     ->  Foreign Scan on public.ft1 t1
                           Output: t1.c1, t1.c8
                           Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1"
               ->  Sort
                     Output: t2.c1, t2.c8
                     Sort Key: t2.c8
                     ->  Foreign Scan on public.ft2 t2
                           Output: t2.c1, t2.c8
                           Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1"
(20 rows)

SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
 c1 | c1  
----+-----
  1 | 101
  1 | 102
  1 | 103
  1 | 104
  1 | 105
  1 | 106
  1 | 107
  1 | 108
  1 | 109
  1 | 110
(10 rows)

-- unsafe conditions on one side (c8 has a UDT), not pushed down.
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
                                 QUERY PLAN                                  
-----------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2.c1, t1.c3
   ->  Sort
         Output: t1.c1, t2.c1, t1.c3
         Sort Key: t1.c3, t1.c1
         ->  Hash Right Join
               Output: t1.c1, t2.c1, t1.c3
               Hash Cond: (t2.c1 = t1.c1)
               ->  Foreign Scan on public.ft2 t2
                     Output: t2.c1
                     Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
               ->  Hash
                     Output: t1.c1, t1.c3
                     ->  Foreign Scan on public.ft1 t1
                           Output: t1.c1, t1.c3
                           Filter: (t1.c8 = 'foo'::user_enum)
                           Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1"
(17 rows)

SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 c1  | c1  
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

-- join where unsafe to pushdown condition in WHERE clause has a column not
-- in the SELECT clause. In this test unsafe clause needs to have column
-- references from both joining sides so that the clause is not pushed down
-- into one of the joining sides.
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
                                                                      QUERY PLAN                                                                       
-------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1.c1, t2.c1, t1.c3
   ->  Sort
         Output: t1.c1, t2.c1, t1.c3
         Sort Key: t1.c3, t1.c1
         ->  Foreign Scan
               Output: t1.c1, t2.c1, t1.c3
               Filter: (t1.c8 = t2.c8)
               Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
               Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
(10 rows)

SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 c1  | c1  
-----+-----
 101 | 101
 102 | 102
 103 | 103
 104 | 104
 105 | 105
 106 | 106
 107 | 107
 108 | 108
 109 | 109
 110 | 110
(10 rows)

-- Aggregate after UNION, for testing setrefs
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
                                                                     QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1.c1, (avg((t1.c1 + t2.c1)))
   ->  Sort
         Output: t1.c1, (avg((t1.c1 + t2.c1)))
         Sort Key: t1.c1
         ->  HashAggregate
               Output: t1.c1, avg((t1.c1 + t2.c1))
               Group Key: t1.c1
               ->  HashAggregate
                     Output: t1.c1, t2.c1
                     Group Key: t1.c1, t2.c1
                     ->  Append
                           ->  Foreign Scan
                                 Output: t1.c1, t2.c1
                                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                                 Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
                           ->  Foreign Scan
                                 Output: t1_1.c1, t2_1.c1
                                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                                 Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
(20 rows)

SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10;
 t1c1 |         avg          
------+----------------------
  101 | 202.0000000000000000
  102 | 204.0000000000000000
  103 | 206.0000000000000000
  104 | 208.0000000000000000
  105 | 210.0000000000000000
  106 | 212.0000000000000000
  107 | 214.0000000000000000
  108 | 216.0000000000000000
  109 | 218.0000000000000000
  110 | 220.0000000000000000
(10 rows)

-- join with lateral reference
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
                                                                             QUERY PLAN                                                                             
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit
   Output: t1."C 1"
   ->  Nested Loop
         Output: t1."C 1"
         ->  Index Scan using t1_pkey on "S 1"."T 1" t1
               Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
         ->  HashAggregate
               Output: t2.c1, t3.c1
               Group Key: t2.c1, t3.c1
               ->  Foreign Scan
                     Output: t2.c1, t3.c1
                     Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
                     Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer))))
(13 rows)

SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
 C 1 
-----
   1
   1
   1
   1
   1
   1
   1
   1
   1
   1
(10 rows)

-- non-Var items in targetlist of the nullable rel of a join preventing
-- push-down in some cases
-- unable to push {ft1, ft2}
EXPLAIN (VERBOSE, COSTS OFF)
SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Nested Loop Left Join
   Output: (13), ft2.c1
   Join Filter: (13 = ft2.c1)
   ->  Foreign Scan on public.ft2
         Output: ft2.c1
         Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST
   ->  Materialize
         Output: (13)
         ->  Foreign Scan on public.ft1
               Output: 13
               Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13))
(11 rows)

SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15;
 a  | c1 
----+----
    | 10
    | 11
    | 12
 13 | 13
    | 14
    | 15
(6 rows)

-- ok to push {ft1, ft2} but not {ft1, ft2, ft4}
EXPLAIN (VERBOSE, COSTS OFF)
SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
                                                                                    QUERY PLAN                                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop Left Join
   Output: ft4.c1, (13), ft1.c1, ft2.c1
   Join Filter: (ft4.c1 = ft1.c1)
   ->  Foreign Scan on public.ft4
         Output: ft4.c1, ft4.c2, ft4.c3
         Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15))
   ->  Materialize
         Output: ft1.c1, ft2.c1, (13)
         ->  Foreign Scan
               Output: ft1.c1, ft2.c1, 13
               Relations: (public.ft1) INNER JOIN (public.ft2)
               Remote SQL: SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST
(12 rows)

SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15;
 c1 | a  | b  | c  
----+----+----+----
 10 |    |    |   
 12 | 13 | 12 | 12
 14 |    |    |   
(3 rows)

-- join with nullable side with some columns with null values
UPDATE ft5 SET c3 = null where c1 % 9 = 0;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
                                                                                                                                QUERY PLAN                                                                                                                                 
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
   Relations: (public.ft5) INNER JOIN (public.ft4)
   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
(4 rows)

SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
      ft5       | c1 | c2 |   c3   | c1 | c2 
----------------+----+----+--------+----+----
 (12,13,AAA012) | 12 | 13 | AAA012 | 12 | 13
 (18,19,)       | 18 | 19 |        | 18 | 19
 (24,25,AAA024) | 24 | 25 | AAA024 | 24 | 25
 (30,31,AAA030) | 30 | 31 | AAA030 | 30 | 31
(4 rows)

-- multi-way join involving multiple merge joins
-- (this case used to have EPQ-related planning problems)
CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1));
INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id;
ANALYZE local_tbl;
SET enable_nestloop TO false;
SET enable_hashjoin TO false;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
    AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 LockRows
   Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
   ->  Merge Join
         Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
         Inner Unique: true
         Merge Cond: (ft1.c2 = local_tbl.c1)
         ->  Foreign Scan
               Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
               Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, 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", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, 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, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
               ->  Merge Join
                     Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                     Merge Cond: (ft1.c2 = ft5.c1)
                     ->  Merge Join
                           Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
                           Merge Cond: (ft1.c2 = ft4.c1)
                           ->  Sort
                                 Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                 Sort Key: ft1.c2
                                 ->  Merge Join
                                       Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                       Merge Cond: (ft1.c1 = ft2.c1)
                                       ->  Sort
                                             Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
                                             Sort Key: ft1.c1
                                             ->  Foreign Scan on public.ft1
                                                   Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
                                       ->  Materialize
                                             Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                             ->  Foreign Scan on public.ft2
                                                   Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
                           ->  Sort
                                 Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                 Sort Key: ft4.c1
                                 ->  Foreign Scan on public.ft4
                                       Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                       Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
                     ->  Sort
                           Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
                           Sort Key: ft5.c1
                           ->  Foreign Scan on public.ft5
                                 Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
                                 Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE
         ->  Index Scan using local_tbl_pkey on public.local_tbl
               Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
(47 rows)

SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
    AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  | c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  | c1 | c2 |   c3   | c1 | c2 |   c3   | c1 | c2 |  c3  
----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------
  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo |  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 26 |  6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 26 |  6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 36 |  6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 36 |  6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 46 |  6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 46 |  6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 56 |  6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 56 |  6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 66 |  6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 66 |  6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 76 |  6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 76 |  6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 86 |  6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 86 |  6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
 96 |  6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 96 |  6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
(10 rows)

RESET enable_nestloop;
RESET enable_hashjoin;
DROP TABLE local_tbl;
-- check join pushdown in situations where multiple userids are involved
CREATE ROLE regress_view_owner SUPERUSER;
CREATE USER MAPPING FOR regress_view_owner SERVER loopback;
GRANT SELECT ON ft4 TO regress_view_owner;
GRANT SELECT ON ft5 TO regress_view_owner;
CREATE VIEW v4 AS SELECT * FROM ft4;
CREATE VIEW v5 AS SELECT * FROM ft5;
ALTER VIEW v5 OWNER TO regress_view_owner;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can't be pushed down, different view owners
                              QUERY PLAN                              
----------------------------------------------------------------------
 Limit
   Output: ft4.c1, ft5.c2, ft5.c1
   ->  Sort
         Output: ft4.c1, ft5.c2, ft5.c1
         Sort Key: ft4.c1, ft5.c1
         ->  Hash Left Join
               Output: ft4.c1, ft5.c2, ft5.c1
               Hash Cond: (ft4.c1 = ft5.c1)
               ->  Foreign Scan on public.ft4
                     Output: ft4.c1, ft4.c2, ft4.c3
                     Remote SQL: SELECT c1 FROM "S 1"."T 3"
               ->  Hash
                     Output: ft5.c2, ft5.c1
                     ->  Foreign Scan on public.ft5
                           Output: ft5.c2, ft5.c1
                           Remote SQL: SELECT c1, c2 FROM "S 1"."T 4"
(16 rows)

SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
 c1 | c2 
----+----
 22 |   
 24 | 25
 26 |   
 28 |   
 30 | 31
 32 |   
 34 |   
 36 | 37
 38 |   
 40 |   
(10 rows)

ALTER VIEW v4 OWNER TO regress_view_owner;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can be pushed down
                                                                                              QUERY PLAN                                                                                               
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: ft4.c1, ft5.c2, ft5.c1
   Relations: (public.ft4) LEFT JOIN (public.ft5)
   Remote SQL: SELECT r6.c1, r9.c2, r9.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r9 ON (((r6.c1 = r9.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r9.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
 c1 | c2 
----+----
 22 |   
 24 | 25
 26 |   
 28 |   
 30 | 31
 32 |   
 34 |   
 36 | 37
 38 |   
 40 |   
(10 rows)

EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can't be pushed down, view owner not current user
                              QUERY PLAN                              
----------------------------------------------------------------------
 Limit
   Output: ft4.c1, t2.c2, t2.c1
   ->  Sort
         Output: ft4.c1, t2.c2, t2.c1
         Sort Key: ft4.c1, t2.c1
         ->  Hash Left Join
               Output: ft4.c1, t2.c2, t2.c1
               Hash Cond: (ft4.c1 = t2.c1)
               ->  Foreign Scan on public.ft4
                     Output: ft4.c1, ft4.c2, ft4.c3
                     Remote SQL: SELECT c1 FROM "S 1"."T 3"
               ->  Hash
                     Output: t2.c2, t2.c1
                     ->  Foreign Scan on public.ft5 t2
                           Output: t2.c2, t2.c1
                           Remote SQL: SELECT c1, c2 FROM "S 1"."T 4"
(16 rows)

SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
 c1 | c2 
----+----
 22 |   
 24 | 25
 26 |   
 28 |   
 30 | 31
 32 |   
 34 |   
 36 | 37
 38 |   
 40 |   
(10 rows)

ALTER VIEW v4 OWNER TO CURRENT_USER;
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;  -- can be pushed down
                                                                                              QUERY PLAN                                                                                               
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: ft4.c1, t2.c2, t2.c1
   Relations: (public.ft4) LEFT JOIN (public.ft5 t2)
   Remote SQL: SELECT r6.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r6 LEFT JOIN "S 1"."T 4" r2 ON (((r6.c1 = r2.c1)))) ORDER BY r6.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
(4 rows)

SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10;
 c1 | c2 
----+----
 22 |   
 24 | 25
 26 |   
 28 |   
 30 | 31
 32 |   
 34 |   
 36 | 37
 38 |   
 40 |   
(10 rows)

ALTER VIEW v4 OWNER TO regress_view_owner;
-- cleanup
DROP OWNED BY regress_view_owner;
DROP ROLE regress_view_owner;
-- ===================================================================
-- Aggregate and grouping queries
-- ===================================================================
-- Simple aggregates
explain (verbose, costs off)
select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2;
                                                                                              QUERY PLAN                                                                                               
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST
(4 rows)

select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2;
 count |  sum  |         avg          | min | max  | stddev | sum2  
-------+-------+----------------------+-----+------+--------+-------
   100 | 49600 | 496.0000000000000000 |   1 |  991 |      0 | 49600
   100 | 49700 | 497.0000000000000000 |   2 |  992 |      0 | 49700
   100 | 49800 | 498.0000000000000000 |   3 |  993 |      0 | 49800
   100 | 49900 | 499.0000000000000000 |   4 |  994 |      0 | 49900
   100 | 50500 | 505.0000000000000000 |   0 | 1000 |      0 | 50500
(5 rows)

explain (verbose, costs off)
select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
                                                                                                      QUERY PLAN                                                                                                       
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint
(4 rows)

select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
 count |  sum  |         avg          | min | max | stddev | sum2  
-------+-------+----------------------+-----+-----+--------+-------
   100 | 49600 | 496.0000000000000000 |   1 | 991 |      0 | 49600
(1 row)

-- Aggregate is not pushed down as aggregation contains random()
explain (verbose, costs off)
select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Aggregate
   Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1)
   ->  Foreign Scan on public.ft1
         Output: c1
         Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
(5 rows)

-- Aggregate over join query
explain (verbose, costs off)
select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6;
                                                                    QUERY PLAN                                                                    
--------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(*)), (sum(t1.c1)), (avg(t2.c1))
   Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft1 t2))
   Remote SQL: SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6))))
(4 rows)

select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6;
 count |   sum   |         avg          
-------+---------+----------------------
 10000 | 5010000 | 501.0000000000000000
(1 row)

-- Not pushed down due to local conditions present in underneath input rel
explain (verbose, costs off)
select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1;
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Aggregate
   Output: sum(t1.c1), count(t2.c1)
   ->  Foreign Scan
         Output: t1.c1, t2.c1
         Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision)
         Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
         Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1"))))
(7 rows)

-- GROUP BY clause having expressions
explain (verbose, costs off)
select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2;
                                                    QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: ((c2 / 2)), ((sum(c2) * (c2 / 2)))
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST
(4 rows)

select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2;
 ?column? | ?column? 
----------+----------
        0 |        0
        1 |      500
        2 |     1800
        3 |     3900
        4 |     6800
(5 rows)

-- Aggregates in subquery are pushed down.
explain (verbose, costs off)
select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x;
                                                                 QUERY PLAN                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate
   Output: count(ft1.c2), sum(ft1.c2)
   ->  Foreign Scan
         Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision))
         Relations: Aggregate on (public.ft1)
         Remote SQL: SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST
(6 rows)

select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x;
 count | sum  
-------+------
  1000 | 4500
(1 row)

-- Aggregate is still pushed down by taking unshippable expression out
explain (verbose, costs off)
select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2;
                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Sort
   Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2
   Sort Key: ((ft1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(ft1.c1) * ft1.c2))
   ->  Foreign Scan
         Output: (c2 * ((random() <= '1'::double precision))::integer), ((sum(c1) * c2)), c2
         Relations: Aggregate on (public.ft1)
         Remote SQL: SELECT (sum("C 1") * c2), c2 FROM "S 1"."T 1" GROUP BY 2
(7 rows)

select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2;
 sum1 |  sum2  
------+--------
    0 |      0
    1 |  49600
    2 |  99400
    3 | 149400
    4 | 199600
    5 | 250000
    6 | 300600
    7 | 351400
    8 | 402400
    9 | 453600
(10 rows)

-- Aggregate with unshippable GROUP BY clause are not pushed
explain (verbose, costs off)
select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Sort
   Output: ((c2 * ((random() <= '1'::double precision))::integer))
   Sort Key: ((ft2.c2 * ((random() <= '1'::double precision))::integer))
   ->  HashAggregate
         Output: ((c2 * ((random() <= '1'::double precision))::integer))
         Group Key: (ft2.c2 * ((random() <= '1'::double precision))::integer)
         ->  Foreign Scan on public.ft2
               Output: (c2 * ((random() <= '1'::double precision))::integer)
               Remote SQL: SELECT c2 FROM "S 1"."T 1"
(9 rows)

-- GROUP BY clause in various forms, cardinal, alias and constant expression
explain (verbose, costs off)
select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Sort
   Output: (count(c2)), c2, 5, 7.0, 9
   Sort Key: ft1.c2
   ->  Foreign Scan
         Output: (count(c2)), c2, 5, 7.0, 9
         Relations: Aggregate on (public.ft1)
         Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5
(7 rows)

select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2;
  w  | x | y |  z  
-----+---+---+-----
 100 | 0 | 5 | 7.0
 100 | 1 | 5 | 7.0
 100 | 2 | 5 | 7.0
 100 | 3 | 5 | 7.0
 100 | 4 | 5 | 7.0
 100 | 5 | 5 | 7.0
 100 | 6 | 5 | 7.0
 100 | 7 | 5 | 7.0
 100 | 8 | 5 | 7.0
 100 | 9 | 5 | 7.0
(10 rows)

-- GROUP BY clause referring to same column multiple times
-- Also, ORDER BY contains an aggregate function
explain (verbose, costs off)
select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1);
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: c2, c2, (sum(c1))
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST
(4 rows)

select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1);
 c2 | c2 
----+----
  7 |  7
  8 |  8
  9 |  9
(3 rows)

-- Testing HAVING clause shippability
explain (verbose, costs off)
select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2;
                                                                         QUERY PLAN                                                                         
------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: c2, (sum(c1))
   Relations: Aggregate on (public.ft2)
   Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST
(4 rows)

select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2;
 c2 |  sum  
----+-------
  1 | 49600
  2 | 49700
(2 rows)

-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down
explain (verbose, costs off)
select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Aggregate
   Output: count(*)
   ->  Foreign Scan
         Output: ft1.c5, NULL::bigint, (sqrt((ft1.c2)::double precision))
         Filter: (((((avg(ft1.c1)) / (avg(ft1.c1))))::double precision * random()) <= '1'::double precision)
         Relations: Aggregate on (public.ft1)
         Remote SQL: SELECT c5, NULL::bigint, sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric))
(7 rows)

select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x;
 count 
-------
    49
(1 row)

-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down
explain (verbose, costs off)
select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1;
                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Sort
   Output: (sum(c1)), c2
   Sort Key: (sum(ft1.c1))
   ->  HashAggregate
         Output: sum(c1), c2
         Group Key: ft1.c2
         Filter: (avg((ft1.c1 * ((random() <= '1'::double precision))::integer)) > '100'::numeric)
         ->  Foreign Scan on public.ft1
               Output: c1, c2
               Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1"
(10 rows)

-- Remote aggregate in combination with a local Param (for the output
-- of an initplan) can be trouble, per bug #15781
explain (verbose, costs off)
select exists(select 1 from pg_enum), sum(c1) from ft1;
                    QUERY PLAN                    
--------------------------------------------------
 Foreign Scan
   Output: $0, (sum(ft1.c1))
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1"
   InitPlan 1 (returns $0)
     ->  Seq Scan on pg_catalog.pg_enum
(6 rows)

select exists(select 1 from pg_enum), sum(c1) from ft1;
 exists |  sum   
--------+--------
 t      | 500500
(1 row)

explain (verbose, costs off)
select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1;
                    QUERY PLAN                     
---------------------------------------------------
 GroupAggregate
   Output: ($0), sum(ft1.c1)
   Group Key: $0
   InitPlan 1 (returns $0)
     ->  Seq Scan on pg_catalog.pg_enum
   ->  Foreign Scan on public.ft1
         Output: $0, ft1.c1
         Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
(8 rows)

select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1;
 exists |  sum   
--------+--------
 t      | 500500
(1 row)

-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates
-- ORDER BY within aggregate, same column used to order
explain (verbose, costs off)
select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1;
                                                                                            QUERY PLAN                                                                                            
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (array_agg(c1 ORDER BY c1)), c2
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST
(4 rows)

select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1;
           array_agg            
--------------------------------
 {1,11,21,31,41,51,61,71,81,91}
 {2,12,22,32,42,52,62,72,82,92}
 {3,13,23,33,43,53,63,73,83,93}
 {4,14,24,34,44,54,64,74,84,94}
 {5,15,25,35,45,55,65,75,85,95}
 {6,16,26,36,46,56,66,76,86,96}
 {7,17,27,37,47,57,67,77,87,97}
 {8,18,28,38,48,58,68,78,88,98}
 {9,19,29,39,49,59,69,79,89,99}
 {10,20,30,40,50,60,70,80,90}
(10 rows)

-- ORDER BY within aggregate, different column used to order also using DESC
explain (verbose, costs off)
select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50;
                                                       QUERY PLAN                                                        
-------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (array_agg(c5 ORDER BY c1 DESC))
   Relations: Aggregate on (public.ft2)
   Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6))
(4 rows)

select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50;
                                                                array_agg                                                                 
------------------------------------------------------------------------------------------------------------------------------------------
 {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"}
(1 row)

-- DISTINCT within aggregate
explain (verbose, costs off)
select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1;
                                                                                                                               QUERY PLAN                                                                                                                               
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3))
   Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2))
   Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST
(4 rows)

select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1;
  array_agg   
--------------
 {0,1,2,3,4}
 {1,2,3,NULL}
(2 rows)

-- DISTINCT combined with ORDER BY within aggregate
explain (verbose, costs off)
select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1;
                                                                                                                                                                     QUERY PLAN                                                                                                                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3))
   Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2))
   Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST
(4 rows)

select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1;
  array_agg   
--------------
 {0,1,2,3,4}
 {1,2,3,NULL}
(2 rows)

explain (verbose, costs off)
select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1;
                                                                                                                                                                      QUERY PLAN                                                                                                                                                                      
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3))
   Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2))
   Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST
(4 rows)

select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1;
  array_agg   
--------------
 {3,2,1,NULL}
 {4,3,2,1,0}
(2 rows)

-- FILTER within aggregate
explain (verbose, costs off)
select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last;
                                                                                         QUERY PLAN                                                                                         
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT sum("C 1") FILTER (WHERE (("C 1" < 100) AND (c2 > 5))), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") FILTER (WHERE (("C 1" < 100) AND (c2 > 5))) ASC NULLS LAST
(4 rows)

select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last;
 sum 
-----
 510
 520
 530
 540
    
    
    
    
    
    
(10 rows)

-- DISTINCT, ORDER BY and FILTER within aggregate
explain (verbose, costs off)
select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2;
                                                                                        QUERY PLAN                                                                                        
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (sum((c1 % 3))), (sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2))), c2
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY (("C 1" % 3)) ASC NULLS LAST) FILTER (WHERE (("C 1" % 3) < 2)), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY 3
(4 rows)

select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2;
 sum | sum | c2 
-----+-----+----
  99 |   1 |  6
(1 row)

-- Outer query is aggregation query
explain (verbose, costs off)
select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1;
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Unique
   Output: ((SubPlan 1))
   ->  Sort
         Output: ((SubPlan 1))
         Sort Key: ((SubPlan 1))
         ->  Foreign Scan
               Output: (SubPlan 1)
               Relations: Aggregate on (public.ft2 t2)
               Remote SQL: SELECT count(*) FILTER (WHERE ((c2 = 6) AND ("C 1" < 10))) FROM "S 1"."T 1" WHERE (((c2 % 6) = 0))
               SubPlan 1
                 ->  Foreign Scan on public.ft1 t1
                       Output: (count(*) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10))))
                       Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6))
(13 rows)

select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1;
 count 
-------
     1
(1 row)

-- Inner query is aggregation query
explain (verbose, costs off)
select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1;
                                                                      QUERY PLAN                                                                      
------------------------------------------------------------------------------------------------------------------------------------------------------
 Unique
   Output: ((SubPlan 1))
   ->  Sort
         Output: ((SubPlan 1))
         Sort Key: ((SubPlan 1))
         ->  Foreign Scan on public.ft2 t2
               Output: (SubPlan 1)
               Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (((c2 % 6) = 0))
               SubPlan 1
                 ->  Foreign Scan
                       Output: (count(t1.c1) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10))))
                       Relations: Aggregate on (public.ft1 t1)
                       Remote SQL: SELECT count("C 1") FILTER (WHERE (($1::integer = 6) AND ($2::integer < 10))) FROM "S 1"."T 1" WHERE (("C 1" = 6))
(13 rows)

select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1;
 count 
-------
     0
     1
(2 rows)

-- Aggregate not pushed down as FILTER condition is not pushable
explain (verbose, costs off)
select sum(c1) filter (where (c1 / c1) * random() <= 1) from ft1 group by c2 order by 1;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Sort
   Output: (sum(c1) FILTER (WHERE ((((c1 / c1))::double precision * random()) <= '1'::double precision))), c2
   Sort Key: (sum(ft1.c1) FILTER (WHERE ((((ft1.c1 / ft1.c1))::double precision * random()) <= '1'::double precision)))
   ->  HashAggregate
         Output: sum(c1) FILTER (WHERE ((((c1 / c1))::double precision * random()) <= '1'::double precision)), c2
         Group Key: ft1.c2
         ->  Foreign Scan on public.ft1
               Output: c1, c2
               Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1"
(9 rows)

explain (verbose, costs off)
select sum(c2) filter (where c2 in (select c2 from ft1 where c2 < 5)) from ft1;
                            QUERY PLAN                             
-------------------------------------------------------------------
 Aggregate
   Output: sum(ft1.c2) FILTER (WHERE (hashed SubPlan 1))
   ->  Foreign Scan on public.ft1
         Output: ft1.c2
         Remote SQL: SELECT c2 FROM "S 1"."T 1"
   SubPlan 1
     ->  Foreign Scan on public.ft1 ft1_1
           Output: ft1_1.c2
           Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 5))
(9 rows)

-- Ordered-sets within aggregate
explain (verbose, costs off)
select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2;
                                                                                                                                                                           QUERY PLAN                                                                                                                                                                           
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort
   Output: c2, (rank('10'::character varying) WITHIN GROUP (ORDER BY c6)), (percentile_cont((((c2)::numeric / '10'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision)))
   Sort Key: ft1.c2
   ->  Foreign Scan
         Output: c2, (rank('10'::character varying) WITHIN GROUP (ORDER BY c6)), (percentile_cont((((c2)::numeric / '10'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision)))
         Relations: Aggregate on (public.ft1)
         Remote SQL: SELECT c2, rank('10'::character varying) WITHIN GROUP (ORDER BY c6 ASC NULLS LAST), percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 HAVING ((percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) < 500::double precision))
(7 rows)

select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2;
 c2 | rank | percentile_cont 
----+------+-----------------
  0 |  101 |              10
  1 |  101 |             100
  2 |    1 |             200
  3 |    1 |             300
  4 |    1 |             400
(5 rows)

-- Using multiple arguments within aggregates
explain (verbose, costs off)
select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1;
                                                                             QUERY PLAN                                                                             
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: c1, (rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2)), c2
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT "C 1", rank("C 1", c2) WITHIN GROUP (ORDER BY "C 1" ASC NULLS LAST, c2 ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" = 6)) GROUP BY 1, 3
(4 rows)

select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1;
 c1 | rank 
----+------
  6 |    1
(1 row)

-- User defined function for user defined aggregate, VARIADIC
create function least_accum(anyelement, variadic anyarray)
returns anyelement language sql as
  'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)';
create aggregate least_agg(variadic items anyarray) (
  stype = anyelement, sfunc = least_accum
);
-- Disable hash aggregation for plan stability.
set enable_hashagg to false;
-- Not pushed down due to user defined aggregate
explain (verbose, costs off)
select c2, least_agg(c1) from ft1 group by c2 order by c2;
                                    QUERY PLAN                                    
----------------------------------------------------------------------------------
 GroupAggregate
   Output: c2, least_agg(VARIADIC ARRAY[c1])
   Group Key: ft1.c2
   ->  Foreign Scan on public.ft1
         Output: c2, c1
         Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST
(6 rows)

-- Add function and aggregate into extension
alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray);
alter extension postgres_fdw add aggregate least_agg(variadic items anyarray);
alter server loopback options (set extensions 'postgres_fdw');
-- Now aggregate will be pushed.  Aggregate will display VARIADIC argument.
explain (verbose, costs off)
select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2;
                                                      QUERY PLAN                                                       
-----------------------------------------------------------------------------------------------------------------------
 Sort
   Output: c2, (least_agg(VARIADIC ARRAY[c1]))
   Sort Key: ft1.c2
   ->  Foreign Scan
         Output: c2, (least_agg(VARIADIC ARRAY[c1]))
         Relations: Aggregate on (public.ft1)
         Remote SQL: SELECT c2, public.least_agg(VARIADIC ARRAY["C 1"]) FROM "S 1"."T 1" WHERE ((c2 < 100)) GROUP BY 1
(7 rows)

select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2;
 c2 | least_agg 
----+-----------
  0 |        10
  1 |         1
  2 |         2
  3 |         3
  4 |         4
  5 |         5
  6 |         6
  7 |         7
  8 |         8
  9 |         9
(10 rows)

-- Remove function and aggregate from extension
alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray);
alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray);
alter server loopback options (set extensions 'postgres_fdw');
-- Not pushed down as we have dropped objects from extension.
explain (verbose, costs off)
select c2, least_agg(c1) from ft1 group by c2 order by c2;
                                    QUERY PLAN                                    
----------------------------------------------------------------------------------
 GroupAggregate
   Output: c2, least_agg(VARIADIC ARRAY[c1])
   Group Key: ft1.c2
   ->  Foreign Scan on public.ft1
         Output: c2, c1
         Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST
(6 rows)

-- Cleanup
reset enable_hashagg;
drop aggregate least_agg(variadic items anyarray);
drop function least_accum(anyelement, variadic anyarray);
-- Testing USING OPERATOR() in ORDER BY within aggregate.
-- For this, we need user defined operators along with operator family and
-- operator class.  Create those and then add them in extension.  Note that
-- user defined objects are considered unshippable unless they are part of
-- the extension.
create operator public.<^ (
 leftarg = int4,
 rightarg = int4,
 procedure = int4eq
);
create operator public.=^ (
 leftarg = int4,
 rightarg = int4,
 procedure = int4lt
);
create operator public.>^ (
 leftarg = int4,
 rightarg = int4,
 procedure = int4gt
);
create operator family my_op_family using btree;
create function my_op_cmp(a int, b int) returns int as
  $$begin return btint4cmp(a, b); end $$ language plpgsql;
create operator class my_op_class for type int using btree family my_op_family as
 operator 1 public.<^,
 operator 3 public.=^,
 operator 5 public.>^,
 function 1 my_op_cmp(int, int);
-- This will not be pushed as user defined sort operator is not part of the
-- extension yet.
explain (verbose, costs off)
select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2;
                                         QUERY PLAN                                         
--------------------------------------------------------------------------------------------
 GroupAggregate
   Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2
   Group Key: ft2.c2
   ->  Foreign Scan on public.ft2
         Output: c1, c2
         Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6))
(6 rows)

-- Update local stats on ft2
ANALYZE ft2;
-- Add into extension
alter extension postgres_fdw add operator class my_op_class using btree;
alter extension postgres_fdw add function my_op_cmp(a int, b int);
alter extension postgres_fdw add operator family my_op_family using btree;
alter extension postgres_fdw add operator public.<^(int, int);
alter extension postgres_fdw add operator public.=^(int, int);
alter extension postgres_fdw add operator public.>^(int, int);
alter server loopback options (set extensions 'postgres_fdw');
-- Now this will be pushed as sort operator is part of the extension.
explain (verbose, costs off)
select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2;
                                                                           QUERY PLAN                                                                           
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (array_agg(c1 ORDER BY c1 USING <^ NULLS LAST)), c2
   Relations: Aggregate on (public.ft2)
   Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" USING OPERATOR(public.<^) NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) GROUP BY 2
(4 rows)

select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2;
           array_agg            
--------------------------------
 {6,16,26,36,46,56,66,76,86,96}
(1 row)

-- Remove from extension
alter extension postgres_fdw drop operator class my_op_class using btree;
alter extension postgres_fdw drop function my_op_cmp(a int, b int);
alter extension postgres_fdw drop operator family my_op_family using btree;
alter extension postgres_fdw drop operator public.<^(int, int);
alter extension postgres_fdw drop operator public.=^(int, int);
alter extension postgres_fdw drop operator public.>^(int, int);
alter server loopback options (set extensions 'postgres_fdw');
-- This will not be pushed as sort operator is now removed from the extension.
explain (verbose, costs off)
select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2;
                                         QUERY PLAN                                         
--------------------------------------------------------------------------------------------
 GroupAggregate
   Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2
   Group Key: ft2.c2
   ->  Foreign Scan on public.ft2
         Output: c1, c2
         Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6))
(6 rows)

-- Cleanup
drop operator class my_op_class using btree;
drop function my_op_cmp(a int, b int);
drop operator family my_op_family using btree;
drop operator public.>^(int, int);
drop operator public.=^(int, int);
drop operator public.<^(int, int);
-- Input relation to aggregate push down hook is not safe to pushdown and thus
-- the aggregate cannot be pushed down to foreign server.
explain (verbose, costs off)
select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2);
                                        QUERY PLAN                                         
-------------------------------------------------------------------------------------------
 Aggregate
   Output: count(t1.c3)
   ->  Nested Loop Left Join
         Output: t1.c3
         Join Filter: ((t1.c1)::double precision = (random() * (t2.c2)::double precision))
         ->  Foreign Scan on public.ft2 t1
               Output: t1.c3, t1.c1
               Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1"
         ->  Materialize
               Output: t2.c2
               ->  Foreign Scan on public.ft2 t2
                     Output: t2.c2
                     Remote SQL: SELECT c2 FROM "S 1"."T 1"
(13 rows)

-- Subquery in FROM clause having aggregate
explain (verbose, costs off)
select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2;
                                          QUERY PLAN                                           
-----------------------------------------------------------------------------------------------
 Sort
   Output: (count(*)), x.b
   Sort Key: (count(*)), x.b
   ->  HashAggregate
         Output: count(*), x.b
         Group Key: x.b
         ->  Hash Join
               Output: x.b
               Inner Unique: true
               Hash Cond: (ft1.c2 = x.a)
               ->  Foreign Scan on public.ft1
                     Output: ft1.c2
                     Remote SQL: SELECT c2 FROM "S 1"."T 1"
               ->  Hash
                     Output: x.b, x.a
                     ->  Subquery Scan on x
                           Output: x.b, x.a
                           ->  Foreign Scan
                                 Output: ft1_1.c2, (sum(ft1_1.c1))
                                 Relations: Aggregate on (public.ft1)
                                 Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1
(21 rows)

select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2;
 count |   b   
-------+-------
   100 | 49600
   100 | 49700
   100 | 49800
   100 | 49900
   100 | 50000
   100 | 50100
   100 | 50200
   100 | 50300
   100 | 50400
   100 | 50500
(10 rows)

-- FULL join with IS NULL check in HAVING
explain (verbose, costs off)
select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2;
                                                                                                                                    QUERY PLAN                                                                                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1
   Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2))
   Remote SQL: SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST
(4 rows)

select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2;
         avg         | sum 
---------------------+-----
 51.0000000000000000 |    
                     |   3
                     |   9
(3 rows)

-- Aggregate over FULL join needing to deparse the joining relations as
-- subqueries.
explain (verbose, costs off)
select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1);
                                                                                                                  QUERY PLAN                                                                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(*)), (sum(ft4.c1)), (avg(ft5.c1))
   Relations: Aggregate on ((public.ft4) FULL JOIN (public.ft5))
   Remote SQL: SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1))))
(4 rows)

select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1);
 count | sum |         avg         
-------+-----+---------------------
     8 | 330 | 55.5000000000000000
(1 row)

-- ORDER BY expression is part of the target list but not pushed down to
-- foreign server.
explain (verbose, costs off)
select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1;
                                   QUERY PLAN                                   
--------------------------------------------------------------------------------
 Sort
   Output: (((sum(c2)) * ((random() <= '1'::double precision))::integer))
   Sort Key: (((sum(ft1.c2)) * ((random() <= '1'::double precision))::integer))
   ->  Foreign Scan
         Output: ((sum(c2)) * ((random() <= '1'::double precision))::integer)
         Relations: Aggregate on (public.ft1)
         Remote SQL: SELECT sum(c2) FROM "S 1"."T 1"
(7 rows)

select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1;
 sum  
------
 4500
(1 row)

-- LATERAL join, with parameterization
set enable_hashagg to false;
explain (verbose, costs off)
select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1;
                                              QUERY PLAN                                              
------------------------------------------------------------------------------------------------------
 Sort
   Output: t1.c2, qry.sum
   Sort Key: t1.c2
   ->  Nested Loop
         Output: t1.c2, qry.sum
         ->  Index Scan using t1_pkey on "S 1"."T 1" t1
               Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
               Index Cond: (t1."C 1" < 100)
               Filter: (t1.c2 < 3)
         ->  Subquery Scan on qry
               Output: qry.sum, t2.c1
               Filter: ((t1.c2 * 2) = qry.sum)
               ->  Foreign Scan
                     Output: (sum((t2.c1 + t1."C 1"))), t2.c1
                     Relations: Aggregate on (public.ft2 t2)
                     Remote SQL: SELECT sum(("C 1" + $1::integer)), "C 1" FROM "S 1"."T 1" GROUP BY 2
(16 rows)

select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1;
 c2 | sum 
----+-----
  1 |   2
  2 |   4
(2 rows)

reset enable_hashagg;
-- bug #15613: bad plan for foreign table scan with lateral reference
EXPLAIN (VERBOSE, COSTS OFF)
SELECT ref_0.c2, subq_1.*
FROM
    "S 1"."T 1" AS ref_0,
    LATERAL (
        SELECT ref_0."C 1" c1, subq_0.*
        FROM (SELECT ref_0.c2, ref_1.c3
              FROM ft1 AS ref_1) AS subq_0
             RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
    ) AS subq_1
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
ORDER BY ref_0."C 1";
                                               QUERY PLAN                                                
---------------------------------------------------------------------------------------------------------
 Nested Loop
   Output: ref_0.c2, ref_0."C 1", (ref_0.c2), ref_1.c3, ref_0."C 1"
   ->  Nested Loop
         Output: ref_0.c2, ref_0."C 1", ref_1.c3, (ref_0.c2)
         ->  Index Scan using t1_pkey on "S 1"."T 1" ref_0
               Output: ref_0."C 1", ref_0.c2, ref_0.c3, ref_0.c4, ref_0.c5, ref_0.c6, ref_0.c7, ref_0.c8
               Index Cond: (ref_0."C 1" < 10)
         ->  Foreign Scan on public.ft1 ref_1
               Output: ref_1.c3, ref_0.c2
               Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'::text))
   ->  Materialize
         Output: ref_3.c3
         ->  Foreign Scan on public.ft2 ref_3
               Output: ref_3.c3
               Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'::text))
(15 rows)

SELECT ref_0.c2, subq_1.*
FROM
    "S 1"."T 1" AS ref_0,
    LATERAL (
        SELECT ref_0."C 1" c1, subq_0.*
        FROM (SELECT ref_0.c2, ref_1.c3
              FROM ft1 AS ref_1) AS subq_0
             RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3)
    ) AS subq_1
WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001'
ORDER BY ref_0."C 1";
 c2 | c1 | c2 |  c3   
----+----+----+-------
  1 |  1 |  1 | 00001
  2 |  2 |  2 | 00001
  3 |  3 |  3 | 00001
  4 |  4 |  4 | 00001
  5 |  5 |  5 | 00001
  6 |  6 |  6 | 00001
  7 |  7 |  7 | 00001
  8 |  8 |  8 | 00001
  9 |  9 |  9 | 00001
(9 rows)

-- Check with placeHolderVars
explain (verbose, costs off)
select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b);
                                                                        QUERY PLAN                                                                        
----------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate
   Output: sum(q.a), count(q.b)
   ->  Nested Loop Left Join
         Output: q.a, q.b
         Inner Unique: true
         Join Filter: ((ft4.c1)::numeric <= q.b)
         ->  Foreign Scan on public.ft4
               Output: ft4.c1, ft4.c2, ft4.c3
               Remote SQL: SELECT c1 FROM "S 1"."T 3"
         ->  Materialize
               Output: q.a, q.b
               ->  Subquery Scan on q
                     Output: q.a, q.b
                     ->  Foreign Scan
                           Output: 13, (avg(ft1.c1)), NULL::bigint
                           Relations: Aggregate on ((public.ft2) LEFT JOIN (public.ft1))
                           Remote SQL: SELECT 13, avg(r1."C 1"), NULL::bigint FROM ("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1"))))
(17 rows)

select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b);
 sum | count 
-----+-------
 650 |    50
(1 row)

-- Not supported cases
-- Grouping sets
explain (verbose, costs off)
select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Sort
   Output: c2, (sum(c1))
   Sort Key: ft1.c2
   ->  MixedAggregate
         Output: c2, sum(c1)
         Hash Key: ft1.c2
         Group Key: ()
         ->  Foreign Scan on public.ft1
               Output: c2, c1
               Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3))
(10 rows)

select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last;
 c2 |  sum   
----+--------
  0 |  50500
  1 |  49600
  2 |  49700
    | 149800
(4 rows)

explain (verbose, costs off)
select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Sort
   Output: c2, (sum(c1))
   Sort Key: ft1.c2
   ->  MixedAggregate
         Output: c2, sum(c1)
         Hash Key: ft1.c2
         Group Key: ()
         ->  Foreign Scan on public.ft1
               Output: c2, c1
               Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3))
(10 rows)

select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last;
 c2 |  sum   
----+--------
  0 |  50500
  1 |  49600
  2 |  49700
    | 149800
(4 rows)

explain (verbose, costs off)
select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last;
                                    QUERY PLAN                                    
----------------------------------------------------------------------------------
 Sort
   Output: c2, c6, (sum(c1))
   Sort Key: ft1.c2, ft1.c6
   ->  HashAggregate
         Output: c2, c6, sum(c1)
         Hash Key: ft1.c2
         Hash Key: ft1.c6
         ->  Foreign Scan on public.ft1
               Output: c2, c6, c1
               Remote SQL: SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3))
(10 rows)

select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last;
 c2 | c6 |  sum  
----+----+-------
  0 |    | 50500
  1 |    | 49600
  2 |    | 49700
    | 0  | 50500
    | 1  | 49600
    | 2  | 49700
(6 rows)

explain (verbose, costs off)
select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Sort
   Output: c2, (sum(c1)), (GROUPING(c2))
   Sort Key: ft1.c2
   ->  HashAggregate
         Output: c2, sum(c1), GROUPING(c2)
         Group Key: ft1.c2
         ->  Foreign Scan on public.ft1
               Output: c2, c1
               Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3))
(9 rows)

select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last;
 c2 |  sum  | grouping 
----+-------+----------
  0 | 50500 |        0
  1 | 49600 |        0
  2 | 49700 |        0
(3 rows)

-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed
explain (verbose, costs off)
select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1;
                                              QUERY PLAN                                               
-------------------------------------------------------------------------------------------------------
 Unique
   Output: ((sum(c1) / 1000)), c2
   ->  Sort
         Output: ((sum(c1) / 1000)), c2
         Sort Key: ((sum(ft2.c1) / 1000))
         ->  Foreign Scan
               Output: ((sum(c1) / 1000)), c2
               Relations: Aggregate on (public.ft2)
               Remote SQL: SELECT (sum("C 1") / 1000), c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) GROUP BY 2
(9 rows)

select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1;
 s  
----
 49
 50
(2 rows)

-- WindowAgg
explain (verbose, costs off)
select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1;
                                                 QUERY PLAN                                                 
------------------------------------------------------------------------------------------------------------
 Sort
   Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2))
   Sort Key: ft2.c2
   ->  WindowAgg
         Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2))
         ->  Sort
               Output: c2, ((c2 % 2)), (sum(c2))
               Sort Key: ((ft2.c2 % 2))
               ->  Foreign Scan
                     Output: c2, ((c2 % 2)), (sum(c2))
                     Relations: Aggregate on (public.ft2)
                     Remote SQL: SELECT c2, (c2 % 2), sum(c2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1
(12 rows)

select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1;
 c2 | sum | count 
----+-----+-------
  0 |   0 |     5
  1 | 100 |     5
  2 | 200 |     5
  3 | 300 |     5
  4 | 400 |     5
  5 | 500 |     5
  6 | 600 |     5
  7 | 700 |     5
  8 | 800 |     5
  9 | 900 |     5
(10 rows)

explain (verbose, costs off)
select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1;
                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Sort
   Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2))
   Sort Key: ft1.c2
   ->  WindowAgg
         Output: c2, array_agg(c2) OVER (?), ((c2 % 2))
         ->  Sort
               Output: c2, ((c2 % 2))
               Sort Key: ((ft1.c2 % 2)), ft1.c2 DESC
               ->  Foreign Scan
                     Output: c2, ((c2 % 2))
                     Relations: Aggregate on (public.ft1)
                     Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1
(12 rows)

select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1;
 c2 |  array_agg  
----+-------------
  0 | {8,6,4,2,0}
  1 | {9,7,5,3,1}
  2 | {8,6,4,2}
  3 | {9,7,5,3}
  4 | {8,6,4}
  5 | {9,7,5}
  6 | {8,6}
  7 | {9,7}
  8 | {8}
  9 | {9}
(10 rows)

explain (verbose, costs off)
select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1;
                                            QUERY PLAN                                             
---------------------------------------------------------------------------------------------------
 Sort
   Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2))
   Sort Key: ft1.c2
   ->  WindowAgg
         Output: c2, array_agg(c2) OVER (?), ((c2 % 2))
         ->  Sort
               Output: c2, ((c2 % 2))
               Sort Key: ((ft1.c2 % 2)), ft1.c2
               ->  Foreign Scan
                     Output: c2, ((c2 % 2))
                     Relations: Aggregate on (public.ft1)
                     Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1
(12 rows)

select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1;
 c2 |  array_agg  
----+-------------
  0 | {0,2,4,6,8}
  1 | {1,3,5,7,9}
  2 | {2,4,6,8}
  3 | {3,5,7,9}
  4 | {4,6,8}
  5 | {5,7,9}
  6 | {6,8}
  7 | {7,9}
  8 | {8}
  9 | {9}
(10 rows)

-- ===================================================================
-- parameterized queries
-- ===================================================================
-- simple join
PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2);
                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.c3, t2.c3
   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
   Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = 2)) AND ((r1."C 1" = 1))))
(4 rows)

EXECUTE st1(1, 1);
  c3   |  c3   
-------+-------
 00001 | 00001
(1 row)

EXECUTE st1(101, 101);
  c3   |  c3   
-------+-------
 00101 | 00101
(1 row)

-- subquery using stable function (can't be sent to remote)
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20);
                                                QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
 Sort
   Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
   Sort Key: t1.c1
   ->  Nested Loop Semi Join
         Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
         Join Filter: (t1.c3 = t2.c3)
         ->  Foreign Scan on public.ft1 t1
               Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20))
         ->  Materialize
               Output: t2.c3
               ->  Foreign Scan on public.ft2 t2
                     Output: t2.c3
                     Filter: (date(t2.c4) = '01-17-1970'::date)
                     Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > 10))
(15 rows)

EXECUTE st2(10, 20);
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
(1 row)

EXECUTE st2(101, 121);
 c1  | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-----+----+-------+------------------------------+--------------------------+----+------------+-----
 116 |  6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
(1 row)

-- subquery using immutable function (can be sent to remote)
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20);
                                                      QUERY PLAN                                                       
-----------------------------------------------------------------------------------------------------------------------
 Sort
   Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
   Sort Key: t1.c1
   ->  Nested Loop Semi Join
         Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
         Join Filter: (t1.c3 = t2.c3)
         ->  Foreign Scan on public.ft1 t1
               Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20))
         ->  Materialize
               Output: t2.c3
               ->  Foreign Scan on public.ft2 t2
                     Output: t2.c3
                     Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date))
(14 rows)

EXECUTE st3(10, 20);
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo
(1 row)

EXECUTE st3(20, 30);
 c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 
----+----+----+----+----+----+----+----
(0 rows)

-- custom plan should be chosen initially
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(3 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(3 rows)

-- once we try it enough times, should switch to generic plan
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1);
                                              QUERY PLAN                                               
-------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer))
(3 rows)

-- value of $1 should not be sent to remote
PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Filter: (t1.c8 = 'foo'::user_enum)
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(4 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Filter: (t1.c8 = 'foo'::user_enum)
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(4 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Filter: (t1.c8 = 'foo'::user_enum)
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(4 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Filter: (t1.c8 = 'foo'::user_enum)
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(4 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Filter: (t1.c8 = 'foo'::user_enum)
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1))
(4 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1);
                                              QUERY PLAN                                               
-------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Filter: (t1.c8 = $1)
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer))
(4 rows)

EXECUTE st5('foo', 1);
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

-- altering FDW options requires replanning
PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2))
(3 rows)

PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo');
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
                                                                                           QUERY PLAN                                                                                            
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Insert on public.ft1
   Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
   ->  Result
         Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1       '::character(10), NULL::user_enum
(4 rows)

ALTER TABLE "S 1"."T 1" RENAME TO "T 0";
ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0');
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2))
(3 rows)

EXECUTE st6;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
  4 |  4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4  | 4          | foo
  5 |  5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo
  7 |  7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  8 |  8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  9 |  9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | 9          | foo
(9 rows)

EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7;
                                                                                           QUERY PLAN                                                                                            
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Insert on public.ft1
   Remote SQL: INSERT INTO "S 1"."T 0"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
   ->  Result
         Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1       '::character(10), NULL::user_enum
(4 rows)

ALTER TABLE "S 1"."T 0" RENAME TO "T 1";
ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1');
PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
                                       QUERY PLAN                                        
-----------------------------------------------------------------------------------------
 Foreign Scan
   Output: (count(c3))
   Relations: Aggregate on (public.ft1 t1)
   Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2))
(4 rows)

ALTER SERVER loopback OPTIONS (DROP extensions);
EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8;
                        QUERY PLAN                         
-----------------------------------------------------------
 Aggregate
   Output: count(c3)
   ->  Foreign Scan on public.ft1 t1
         Output: c3
         Filter: (t1.c1 === t1.c2)
         Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
(6 rows)

EXECUTE st8;
 count 
-------
     9
(1 row)

ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
-- cleanup
DEALLOCATE st1;
DEALLOCATE st2;
DEALLOCATE st3;
DEALLOCATE st4;
DEALLOCATE st5;
DEALLOCATE st6;
DEALLOCATE st7;
DEALLOCATE st8;
-- System columns, except ctid and oid, should not be sent to remote
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Limit
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   ->  Foreign Scan on public.ft1 t1
         Output: c1, c2, c3, c4, c5, c6, c7, c8
         Filter: (t1.tableoid = '1259'::oid)
         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
(6 rows)

SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
                                       QUERY PLAN                                        
-----------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: (tableoid)::regclass, c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" LIMIT 1::bigint
(3 rows)

SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
 tableoid | c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----------+----+----+-------+------------------------------+--------------------------+----+------------+-----
 ft1      |  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
                                              QUERY PLAN                                               
-------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((ctid = '(0,2)'::tid))
(3 rows)

SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
SELECT ctid, * FROM ft1 t1 LIMIT 1;
                                          QUERY PLAN                                           
-----------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1 t1
   Output: ctid, c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" LIMIT 1::bigint
(3 rows)

SELECT ctid, * FROM ft1 t1 LIMIT 1;
 ctid  | c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
-------+----+----+-------+------------------------------+--------------------------+----+------------+-----
 (0,1) |  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

-- ===================================================================
-- used in PL/pgSQL function
-- ===================================================================
CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$
DECLARE
	v_c1 int;
BEGIN
    SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1;
    PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1;
    RETURN v_c1;
END;
$$ LANGUAGE plpgsql;
SELECT f_test(100);
 f_test 
--------
    100
(1 row)

DROP FUNCTION f_test(int);
-- ===================================================================
-- conversion error
-- ===================================================================
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
SELECT * FROM ft1 WHERE c1 = 1;  -- ERROR
ERROR:  invalid input syntax for type integer: "foo"
CONTEXT:  column "c8" of foreign table "ft1"
SELECT  ft1.c1,  ft2.c2, ft1.c8 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR
ERROR:  invalid input syntax for type integer: "foo"
CONTEXT:  column "c8" of foreign table "ft1"
SELECT  ft1.c1,  ft2.c2, ft1 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR
ERROR:  invalid input syntax for type integer: "foo"
CONTEXT:  whole-row reference to foreign table "ft1"
SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR
ERROR:  invalid input syntax for type integer: "foo"
CONTEXT:  processing expression at position 2 in select list
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
-- ===================================================================
-- subtransaction
--  + local/remote error doesn't break cursor
-- ===================================================================
BEGIN;
DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1;
FETCH c;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

SAVEPOINT s;
ERROR OUT;          -- ERROR
ERROR:  syntax error at or near "ERROR"
LINE 1: ERROR OUT;
        ^
ROLLBACK TO s;
FETCH c;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  2 |  2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2  | 2          | foo
(1 row)

SAVEPOINT s;
SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0;  -- ERROR
ERROR:  division by zero
CONTEXT:  remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 / ("C 1" - 1)) > 0))
ROLLBACK TO s;
FETCH c;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  3 |  3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3  | 3          | foo
(1 row)

SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
 c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  
----+----+-------+------------------------------+--------------------------+----+------------+-----
  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1  | 1          | foo
(1 row)

COMMIT;
-- ===================================================================
-- test handling of collations
-- ===================================================================
create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique);
create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10))
  server loopback options (table_name 'loct3', use_remote_estimate 'true');
-- can be sent to remote
explain (verbose, costs off) select * from ft3 where f1 = 'foo';
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text))
(3 rows)

explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text))
(3 rows)

explain (verbose, costs off) select * from ft3 where f2 = 'foo';
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'::text))
(3 rows)

explain (verbose, costs off) select * from ft3 where f3 = 'foo';
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'::text))
(3 rows)

explain (verbose, costs off) select * from ft3 f, loct3 l
  where f.f3 = l.f3 and l.f1 = 'foo';
                                            QUERY PLAN                                            
--------------------------------------------------------------------------------------------------
 Nested Loop
   Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3
   ->  Index Scan using loct3_f1_key on public.loct3 l
         Output: l.f1, l.f2, l.f3
         Index Cond: (l.f1 = 'foo'::text)
   ->  Foreign Scan on public.ft3 f
         Output: f.f1, f.f2, f.f3
         Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE (($1::character varying(10) = f3))
(8 rows)

-- can't be sent to remote
explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo';
                    QUERY PLAN                     
---------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Filter: ((ft3.f1)::text = 'foo'::text)
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3
(4 rows)

explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C";
                    QUERY PLAN                     
---------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Filter: (ft3.f1 = 'foo'::text COLLATE "C")
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3
(4 rows)

explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo';
                    QUERY PLAN                     
---------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Filter: ((ft3.f2)::text = 'foo'::text)
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3
(4 rows)

explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C";
                    QUERY PLAN                     
---------------------------------------------------
 Foreign Scan on public.ft3
   Output: f1, f2, f3
   Filter: (ft3.f2 = 'foo'::text COLLATE "C")
   Remote SQL: SELECT f1, f2, f3 FROM public.loct3
(4 rows)

explain (verbose, costs off) select * from ft3 f, loct3 l
  where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo';
                         QUERY PLAN                          
-------------------------------------------------------------
 Hash Join
   Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3
   Inner Unique: true
   Hash Cond: ((f.f3)::text = (l.f3)::text)
   ->  Foreign Scan on public.ft3 f
         Output: f.f1, f.f2, f.f3
         Remote SQL: SELECT f1, f2, f3 FROM public.loct3
   ->  Hash
         Output: l.f1, l.f2, l.f3
         ->  Index Scan using loct3_f1_key on public.loct3 l
               Output: l.f1, l.f2, l.f3
               Index Cond: (l.f1 = 'foo'::text)
(12 rows)

-- ===================================================================
-- test writable foreign table stuff
-- ===================================================================
EXPLAIN (verbose, costs off)
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
                                                                                                                    QUERY PLAN                                                                                                                    
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Insert on public.ft2
   Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
   ->  Subquery Scan on "*SELECT*"
         Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2       '::character(10), NULL::user_enum
         ->  Foreign Scan on public.ft2 ft2_1
               Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3)
               Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint
(7 rows)

INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
INSERT INTO ft2 (c1,c2,c3)
  VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
  c1  | c2  | c3  | c4 | c5 | c6 |     c7     | c8 
------+-----+-----+----+----+----+------------+----
 1101 | 201 | aaa |    |    |    | ft2        | 
 1102 | 202 | bbb |    |    |    | ft2        | 
 1103 | 203 | ccc |    |    |    | ft2        | 
(3 rows)

INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;              -- can be pushed down
                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Update on public.ft2
   ->  Foreign Update on public.ft2
         Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 300), c3 = (c3 || '_update3'::text) WHERE ((("C 1" % 10) = 3))
(3 rows)

UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;  -- can be pushed down
                                                                            QUERY PLAN                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Update on public.ft2
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   ->  Foreign Update on public.ft2
         Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7'::text) WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
(4 rows)

UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
  c1  | c2  |         c3         |              c4              |            c5            | c6 |     c7     | c8  
------+-----+--------------------+------------------------------+--------------------------+----+------------+-----
    7 | 407 | 00007_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
   17 | 407 | 00017_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
   27 | 407 | 00027_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
   37 | 407 | 00037_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
   47 | 407 | 00047_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
   57 | 407 | 00057_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
   67 | 407 | 00067_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
   77 | 407 | 00077_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
   87 | 407 | 00087_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
   97 | 407 | 00097_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  107 | 407 | 00107_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  117 | 407 | 00117_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  127 | 407 | 00127_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  137 | 407 | 00137_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  147 | 407 | 00147_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  157 | 407 | 00157_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  167 | 407 | 00167_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  177 | 407 | 00177_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  187 | 407 | 00187_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  197 | 407 | 00197_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  207 | 407 | 00207_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  217 | 407 | 00217_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  227 | 407 | 00227_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  237 | 407 | 00237_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  247 | 407 | 00247_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  257 | 407 | 00257_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  267 | 407 | 00267_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  277 | 407 | 00277_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  287 | 407 | 00287_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  297 | 407 | 00297_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  307 | 407 | 00307_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  317 | 407 | 00317_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  327 | 407 | 00327_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  337 | 407 | 00337_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  347 | 407 | 00347_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  357 | 407 | 00357_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  367 | 407 | 00367_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  377 | 407 | 00377_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  387 | 407 | 00387_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  397 | 407 | 00397_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  407 | 407 | 00407_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  417 | 407 | 00417_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  427 | 407 | 00427_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  437 | 407 | 00437_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  447 | 407 | 00447_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  457 | 407 | 00457_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  467 | 407 | 00467_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  477 | 407 | 00477_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  487 | 407 | 00487_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  497 | 407 | 00497_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  507 | 407 | 00507_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  517 | 407 | 00517_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  527 | 407 | 00527_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  537 | 407 | 00537_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  547 | 407 | 00547_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  557 | 407 | 00557_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  567 | 407 | 00567_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  577 | 407 | 00577_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  587 | 407 | 00587_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  597 | 407 | 00597_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  607 | 407 | 00607_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  617 | 407 | 00617_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  627 | 407 | 00627_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  637 | 407 | 00637_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  647 | 407 | 00647_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  657 | 407 | 00657_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  667 | 407 | 00667_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  677 | 407 | 00677_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  687 | 407 | 00687_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  697 | 407 | 00697_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  707 | 407 | 00707_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  717 | 407 | 00717_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  727 | 407 | 00727_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  737 | 407 | 00737_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  747 | 407 | 00747_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  757 | 407 | 00757_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  767 | 407 | 00767_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  777 | 407 | 00777_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  787 | 407 | 00787_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  797 | 407 | 00797_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  807 | 407 | 00807_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  817 | 407 | 00817_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  827 | 407 | 00827_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  837 | 407 | 00837_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  847 | 407 | 00847_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  857 | 407 | 00857_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  867 | 407 | 00867_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  877 | 407 | 00877_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
  887 | 407 | 00887_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
  897 | 407 | 00897_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
  907 | 407 | 00907_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
  917 | 407 | 00917_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
  927 | 407 | 00927_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
  937 | 407 | 00937_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
  947 | 407 | 00947_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
  957 | 407 | 00957_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
  967 | 407 | 00967_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
  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 be pushed down
                                                                                                   QUERY PLAN                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Update on public.ft2
   ->  Foreign Update
         Remote SQL: UPDATE "S 1"."T 1" r1 SET c2 = (r1.c2 + 500), c3 = (r1.c3 || '_update9'::text), c7 = 'ft2       '::character(10) FROM "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9))
(3 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;
EXPLAIN (verbose, costs off)
  DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;                               -- can be pushed down
                                         QUERY PLAN                                         
--------------------------------------------------------------------------------------------
 Delete on public.ft2
   Output: c1, c4
   ->  Foreign Delete on public.ft2
         Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4
(4 rows)

DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
  c1  |              c4              
------+------------------------------
    5 | Tue Jan 06 00:00:00 1970 PST
   15 | Fri Jan 16 00:00:00 1970 PST
   25 | Mon Jan 26 00:00:00 1970 PST
   35 | Thu Feb 05 00:00:00 1970 PST
   45 | Sun Feb 15 00:00:00 1970 PST
   55 | Wed Feb 25 00:00:00 1970 PST
   65 | Sat Mar 07 00:00:00 1970 PST
   75 | Tue Mar 17 00:00:00 1970 PST
   85 | Fri Mar 27 00:00:00 1970 PST
   95 | Mon Apr 06 00:00:00 1970 PST
  105 | Tue Jan 06 00:00:00 1970 PST
  115 | Fri Jan 16 00:00:00 1970 PST
  125 | Mon Jan 26 00:00:00 1970 PST
  135 | Thu Feb 05 00:00:00 1970 PST
  145 | Sun Feb 15 00:00:00 1970 PST
  155 | Wed Feb 25 00:00:00 1970 PST
  165 | Sat Mar 07 00:00:00 1970 PST
  175 | Tue Mar 17 00:00:00 1970 PST
  185 | Fri Mar 27 00:00:00 1970 PST
  195 | Mon Apr 06 00:00:00 1970 PST
  205 | Tue Jan 06 00:00:00 1970 PST
  215 | Fri Jan 16 00:00:00 1970 PST
  225 | Mon Jan 26 00:00:00 1970 PST
  235 | Thu Feb 05 00:00:00 1970 PST
  245 | Sun Feb 15 00:00:00 1970 PST
  255 | Wed Feb 25 00:00:00 1970 PST
  265 | Sat Mar 07 00:00:00 1970 PST
  275 | Tue Mar 17 00:00:00 1970 PST
  285 | Fri Mar 27 00:00:00 1970 PST
  295 | Mon Apr 06 00:00:00 1970 PST
  305 | Tue Jan 06 00:00:00 1970 PST
  315 | Fri Jan 16 00:00:00 1970 PST
  325 | Mon Jan 26 00:00:00 1970 PST
  335 | Thu Feb 05 00:00:00 1970 PST
  345 | Sun Feb 15 00:00:00 1970 PST
  355 | Wed Feb 25 00:00:00 1970 PST
  365 | Sat Mar 07 00:00:00 1970 PST
  375 | Tue Mar 17 00:00:00 1970 PST
  385 | Fri Mar 27 00:00:00 1970 PST
  395 | Mon Apr 06 00:00:00 1970 PST
  405 | Tue Jan 06 00:00:00 1970 PST
  415 | Fri Jan 16 00:00:00 1970 PST
  425 | Mon Jan 26 00:00:00 1970 PST
  435 | Thu Feb 05 00:00:00 1970 PST
  445 | Sun Feb 15 00:00:00 1970 PST
  455 | Wed Feb 25 00:00:00 1970 PST
  465 | Sat Mar 07 00:00:00 1970 PST
  475 | Tue Mar 17 00:00:00 1970 PST
  485 | Fri Mar 27 00:00:00 1970 PST
  495 | Mon Apr 06 00:00:00 1970 PST
  505 | Tue Jan 06 00:00:00 1970 PST
  515 | Fri Jan 16 00:00:00 1970 PST
  525 | Mon Jan 26 00:00:00 1970 PST
  535 | Thu Feb 05 00:00:00 1970 PST
  545 | Sun Feb 15 00:00:00 1970 PST
  555 | Wed Feb 25 00:00:00 1970 PST
  565 | Sat Mar 07 00:00:00 1970 PST
  575 | Tue Mar 17 00:00:00 1970 PST
  585 | Fri Mar 27 00:00:00 1970 PST
  595 | Mon Apr 06 00:00:00 1970 PST
  605 | Tue Jan 06 00:00:00 1970 PST
  615 | Fri Jan 16 00:00:00 1970 PST
  625 | Mon Jan 26 00:00:00 1970 PST
  635 | Thu Feb 05 00:00:00 1970 PST
  645 | Sun Feb 15 00:00:00 1970 PST
  655 | Wed Feb 25 00:00:00 1970 PST
  665 | Sat Mar 07 00:00:00 1970 PST
  675 | Tue Mar 17 00:00:00 1970 PST
  685 | Fri Mar 27 00:00:00 1970 PST
  695 | Mon Apr 06 00:00:00 1970 PST
  705 | Tue Jan 06 00:00:00 1970 PST
  715 | Fri Jan 16 00:00:00 1970 PST
  725 | Mon Jan 26 00:00:00 1970 PST
  735 | Thu Feb 05 00:00:00 1970 PST
  745 | Sun Feb 15 00:00:00 1970 PST
  755 | Wed Feb 25 00:00:00 1970 PST
  765 | Sat Mar 07 00:00:00 1970 PST
  775 | Tue Mar 17 00:00:00 1970 PST
  785 | Fri Mar 27 00:00:00 1970 PST
  795 | Mon Apr 06 00:00:00 1970 PST
  805 | Tue Jan 06 00:00:00 1970 PST
  815 | Fri Jan 16 00:00:00 1970 PST
  825 | Mon Jan 26 00:00:00 1970 PST
  835 | Thu Feb 05 00:00:00 1970 PST
  845 | Sun Feb 15 00:00:00 1970 PST
  855 | Wed Feb 25 00:00:00 1970 PST
  865 | Sat Mar 07 00:00:00 1970 PST
  875 | Tue Mar 17 00:00:00 1970 PST
  885 | Fri Mar 27 00:00:00 1970 PST
  895 | Mon Apr 06 00:00:00 1970 PST
  905 | Tue Jan 06 00:00:00 1970 PST
  915 | Fri Jan 16 00:00:00 1970 PST
  925 | Mon Jan 26 00:00:00 1970 PST
  935 | Thu Feb 05 00:00:00 1970 PST
  945 | Sun Feb 15 00:00:00 1970 PST
  955 | Wed Feb 25 00:00:00 1970 PST
  965 | Sat Mar 07 00:00:00 1970 PST
  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 be pushed down
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Delete on public.ft2
   ->  Foreign Delete
         Remote SQL: DELETE FROM "S 1"."T 1" r1 USING "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 2))
(3 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;
  c1  | c2  |         c3         |              c4              
------+-----+--------------------+------------------------------
    1 |   1 | 00001              | Fri Jan 02 00:00:00 1970 PST
    3 | 303 | 00003_update3      | Sun Jan 04 00:00:00 1970 PST
    4 |   4 | 00004              | Mon Jan 05 00:00:00 1970 PST
    6 |   6 | 00006              | Wed Jan 07 00:00:00 1970 PST
    7 | 407 | 00007_update7      | Thu Jan 08 00:00:00 1970 PST
    8 |   8 | 00008              | Fri Jan 09 00:00:00 1970 PST
    9 | 509 | 00009_update9      | Sat Jan 10 00:00:00 1970 PST
   10 |   0 | 00010              | Sun Jan 11 00:00:00 1970 PST
   11 |   1 | 00011              | Mon Jan 12 00:00:00 1970 PST
   13 | 303 | 00013_update3      | Wed Jan 14 00:00:00 1970 PST
   14 |   4 | 00014              | Thu Jan 15 00:00:00 1970 PST
   16 |   6 | 00016              | Sat Jan 17 00:00:00 1970 PST
   17 | 407 | 00017_update7      | Sun Jan 18 00:00:00 1970 PST
   18 |   8 | 00018              | Mon Jan 19 00:00:00 1970 PST
   19 | 509 | 00019_update9      | Tue Jan 20 00:00:00 1970 PST
   20 |   0 | 00020              | Wed Jan 21 00:00:00 1970 PST
   21 |   1 | 00021              | Thu Jan 22 00:00:00 1970 PST
   23 | 303 | 00023_update3      | Sat Jan 24 00:00:00 1970 PST
   24 |   4 | 00024              | Sun Jan 25 00:00:00 1970 PST
   26 |   6 | 00026              | Tue Jan 27 00:00:00 1970 PST
   27 | 407 | 00027_update7      | Wed Jan 28 00:00:00 1970 PST
   28 |   8 | 00028              | Thu Jan 29 00:00:00 1970 PST
   29 | 509 | 00029_update9      | Fri Jan 30 00:00:00 1970 PST
   30 |   0 | 00030              | Sat Jan 31 00:00:00 1970 PST
   31 |   1 | 00031              | Sun Feb 01 00:00:00 1970 PST
   33 | 303 | 00033_update3      | Tue Feb 03 00:00:00 1970 PST
   34 |   4 | 00034              | Wed Feb 04 00:00:00 1970 PST
   36 |   6 | 00036              | Fri Feb 06 00:00:00 1970 PST
   37 | 407 | 00037_update7      | Sat Feb 07 00:00:00 1970 PST
   38 |   8 | 00038              | Sun Feb 08 00:00:00 1970 PST
   39 | 509 | 00039_update9      | Mon Feb 09 00:00:00 1970 PST
   40 |   0 | 00040              | Tue Feb 10 00:00:00 1970 PST
   41 |   1 | 00041              | Wed Feb 11 00:00:00 1970 PST
   43 | 303 | 00043_update3      | Fri Feb 13 00:00:00 1970 PST
   44 |   4 | 00044              | Sat Feb 14 00:00:00 1970 PST
   46 |   6 | 00046              | Mon Feb 16 00:00:00 1970 PST
   47 | 407 | 00047_update7      | Tue Feb 17 00:00:00 1970 PST
   48 |   8 | 00048              | Wed Feb 18 00:00:00 1970 PST
   49 | 509 | 00049_update9      | Thu Feb 19 00:00:00 1970 PST
   50 |   0 | 00050              | Fri Feb 20 00:00:00 1970 PST
   51 |   1 | 00051              | Sat Feb 21 00:00:00 1970 PST
   53 | 303 | 00053_update3      | Mon Feb 23 00:00:00 1970 PST
   54 |   4 | 00054              | Tue Feb 24 00:00:00 1970 PST
   56 |   6 | 00056              | Thu Feb 26 00:00:00 1970 PST
   57 | 407 | 00057_update7      | Fri Feb 27 00:00:00 1970 PST
   58 |   8 | 00058              | Sat Feb 28 00:00:00 1970 PST
   59 | 509 | 00059_update9      | Sun Mar 01 00:00:00 1970 PST
   60 |   0 | 00060              | Mon Mar 02 00:00:00 1970 PST
   61 |   1 | 00061              | Tue Mar 03 00:00:00 1970 PST
   63 | 303 | 00063_update3      | Thu Mar 05 00:00:00 1970 PST
   64 |   4 | 00064              | Fri Mar 06 00:00:00 1970 PST
   66 |   6 | 00066              | Sun Mar 08 00:00:00 1970 PST
   67 | 407 | 00067_update7      | Mon Mar 09 00:00:00 1970 PST
   68 |   8 | 00068              | Tue Mar 10 00:00:00 1970 PST
   69 | 509 | 00069_update9      | Wed Mar 11 00:00:00 1970 PST
   70 |   0 | 00070              | Thu Mar 12 00:00:00 1970 PST
   71 |   1 | 00071              | Fri Mar 13 00:00:00 1970 PST
   73 | 303 | 00073_update3      | Sun Mar 15 00:00:00 1970 PST
   74 |   4 | 00074              | Mon Mar 16 00:00:00 1970 PST
   76 |   6 | 00076              | Wed Mar 18 00:00:00 1970 PST
   77 | 407 | 00077_update7      | Thu Mar 19 00:00:00 1970 PST
   78 |   8 | 00078              | Fri Mar 20 00:00:00 1970 PST
   79 | 509 | 00079_update9      | Sat Mar 21 00:00:00 1970 PST
   80 |   0 | 00080              | Sun Mar 22 00:00:00 1970 PST
   81 |   1 | 00081              | Mon Mar 23 00:00:00 1970 PST
   83 | 303 | 00083_update3      | Wed Mar 25 00:00:00 1970 PST
   84 |   4 | 00084              | Thu Mar 26 00:00:00 1970 PST
   86 |   6 | 00086              | Sat Mar 28 00:00:00 1970 PST
   87 | 407 | 00087_update7      | Sun Mar 29 00:00:00 1970 PST
   88 |   8 | 00088              | Mon Mar 30 00:00:00 1970 PST
   89 | 509 | 00089_update9      | Tue Mar 31 00:00:00 1970 PST
   90 |   0 | 00090              | Wed Apr 01 00:00:00 1970 PST
   91 |   1 | 00091              | Thu Apr 02 00:00:00 1970 PST
   93 | 303 | 00093_update3      | Sat Apr 04 00:00:00 1970 PST
   94 |   4 | 00094              | Sun Apr 05 00:00:00 1970 PST
   96 |   6 | 00096              | Tue Apr 07 00:00:00 1970 PST
   97 | 407 | 00097_update7      | Wed Apr 08 00:00:00 1970 PST
   98 |   8 | 00098              | Thu Apr 09 00:00:00 1970 PST
   99 | 509 | 00099_update9      | Fri Apr 10 00:00:00 1970 PST
  100 |   0 | 00100              | Thu Jan 01 00:00:00 1970 PST
  101 |   1 | 00101              | Fri Jan 02 00:00:00 1970 PST
  103 | 303 | 00103_update3      | Sun Jan 04 00:00:00 1970 PST
  104 |   4 | 00104              | Mon Jan 05 00:00:00 1970 PST
  106 |   6 | 00106              | Wed Jan 07 00:00:00 1970 PST
  107 | 407 | 00107_update7      | Thu Jan 08 00:00:00 1970 PST
  108 |   8 | 00108              | Fri Jan 09 00:00:00 1970 PST
  109 | 509 | 00109_update9      | Sat Jan 10 00:00:00 1970 PST
  110 |   0 | 00110              | Sun Jan 11 00:00:00 1970 PST
  111 |   1 | 00111              | Mon Jan 12 00:00:00 1970 PST
  113 | 303 | 00113_update3      | Wed Jan 14 00:00:00 1970 PST
  114 |   4 | 00114              | Thu Jan 15 00:00:00 1970 PST
  116 |   6 | 00116              | Sat Jan 17 00:00:00 1970 PST
  117 | 407 | 00117_update7      | Sun Jan 18 00:00:00 1970 PST
  118 |   8 | 00118              | Mon Jan 19 00:00:00 1970 PST
  119 | 509 | 00119_update9      | Tue Jan 20 00:00:00 1970 PST
  120 |   0 | 00120              | Wed Jan 21 00:00:00 1970 PST
  121 |   1 | 00121              | Thu Jan 22 00:00:00 1970 PST
  123 | 303 | 00123_update3      | Sat Jan 24 00:00:00 1970 PST
  124 |   4 | 00124              | Sun Jan 25 00:00:00 1970 PST
  126 |   6 | 00126              | Tue Jan 27 00:00:00 1970 PST
  127 | 407 | 00127_update7      | Wed Jan 28 00:00:00 1970 PST
  128 |   8 | 00128              | Thu Jan 29 00:00:00 1970 PST
  129 | 509 | 00129_update9      | Fri Jan 30 00:00:00 1970 PST
  130 |   0 | 00130              | Sat Jan 31 00:00:00 1970 PST
  131 |   1 | 00131              | Sun Feb 01 00:00:00 1970 PST
  133 | 303 | 00133_update3      | Tue Feb 03 00:00:00 1970 PST
  134 |   4 | 00134              | Wed Feb 04 00:00:00 1970 PST
  136 |   6 | 00136              | Fri Feb 06 00:00:00 1970 PST
  137 | 407 | 00137_update7      | Sat Feb 07 00:00:00 1970 PST
  138 |   8 | 00138              | Sun Feb 08 00:00:00 1970 PST
  139 | 509 | 00139_update9      | Mon Feb 09 00:00:00 1970 PST
  140 |   0 | 00140              | Tue Feb 10 00:00:00 1970 PST
  141 |   1 | 00141              | Wed Feb 11 00:00:00 1970 PST
  143 | 303 | 00143_update3      | Fri Feb 13 00:00:00 1970 PST
  144 |   4 | 00144              | Sat Feb 14 00:00:00 1970 PST
  146 |   6 | 00146              | Mon Feb 16 00:00:00 1970 PST
  147 | 407 | 00147_update7      | Tue Feb 17 00:00:00 1970 PST
  148 |   8 | 00148              | Wed Feb 18 00:00:00 1970 PST
  149 | 509 | 00149_update9      | Thu Feb 19 00:00:00 1970 PST
  150 |   0 | 00150              | Fri Feb 20 00:00:00 1970 PST
  151 |   1 | 00151              | Sat Feb 21 00:00:00 1970 PST
  153 | 303 | 00153_update3      | Mon Feb 23 00:00:00 1970 PST
  154 |   4 | 00154              | Tue Feb 24 00:00:00 1970 PST
  156 |   6 | 00156              | Thu Feb 26 00:00:00 1970 PST
  157 | 407 | 00157_update7      | Fri Feb 27 00:00:00 1970 PST
  158 |   8 | 00158              | Sat Feb 28 00:00:00 1970 PST
  159 | 509 | 00159_update9      | Sun Mar 01 00:00:00 1970 PST
  160 |   0 | 00160              | Mon Mar 02 00:00:00 1970 PST
  161 |   1 | 00161              | Tue Mar 03 00:00:00 1970 PST
  163 | 303 | 00163_update3      | Thu Mar 05 00:00:00 1970 PST
  164 |   4 | 00164              | Fri Mar 06 00:00:00 1970 PST
  166 |   6 | 00166              | Sun Mar 08 00:00:00 1970 PST
  167 | 407 | 00167_update7      | Mon Mar 09 00:00:00 1970 PST
  168 |   8 | 00168              | Tue Mar 10 00:00:00 1970 PST
  169 | 509 | 00169_update9      | Wed Mar 11 00:00:00 1970 PST
  170 |   0 | 00170              | Thu Mar 12 00:00:00 1970 PST
  171 |   1 | 00171              | Fri Mar 13 00:00:00 1970 PST
  173 | 303 | 00173_update3      | Sun Mar 15 00:00:00 1970 PST
  174 |   4 | 00174              | Mon Mar 16 00:00:00 1970 PST
  176 |   6 | 00176              | Wed Mar 18 00:00:00 1970 PST
  177 | 407 | 00177_update7      | Thu Mar 19 00:00:00 1970 PST
  178 |   8 | 00178              | Fri Mar 20 00:00:00 1970 PST
  179 | 509 | 00179_update9      | Sat Mar 21 00:00:00 1970 PST
  180 |   0 | 00180              | Sun Mar 22 00:00:00 1970 PST
  181 |   1 | 00181              | Mon Mar 23 00:00:00 1970 PST
  183 | 303 | 00183_update3      | Wed Mar 25 00:00:00 1970 PST
  184 |   4 | 00184              | Thu Mar 26 00:00:00 1970 PST
  186 |   6 | 00186              | Sat Mar 28 00:00:00 1970 PST
  187 | 407 | 00187_update7      | Sun Mar 29 00:00:00 1970 PST
  188 |   8 | 00188              | Mon Mar 30 00:00:00 1970 PST
  189 | 509 | 00189_update9      | Tue Mar 31 00:00:00 1970 PST
  190 |   0 | 00190              | Wed Apr 01 00:00:00 1970 PST
  191 |   1 | 00191              | Thu Apr 02 00:00:00 1970 PST
  193 | 303 | 00193_update3      | Sat Apr 04 00:00:00 1970 PST
  194 |   4 | 00194              | Sun Apr 05 00:00:00 1970 PST
  196 |   6 | 00196              | Tue Apr 07 00:00:00 1970 PST
  197 | 407 | 00197_update7      | Wed Apr 08 00:00:00 1970 PST
  198 |   8 | 00198              | Thu Apr 09 00:00:00 1970 PST
  199 | 509 | 00199_update9      | Fri Apr 10 00:00:00 1970 PST
  200 |   0 | 00200              | Thu Jan 01 00:00:00 1970 PST
  201 |   1 | 00201              | Fri Jan 02 00:00:00 1970 PST
  203 | 303 | 00203_update3      | Sun Jan 04 00:00:00 1970 PST
  204 |   4 | 00204              | Mon Jan 05 00:00:00 1970 PST
  206 |   6 | 00206              | Wed Jan 07 00:00:00 1970 PST
  207 | 407 | 00207_update7      | Thu Jan 08 00:00:00 1970 PST
  208 |   8 | 00208              | Fri Jan 09 00:00:00 1970 PST
  209 | 509 | 00209_update9      | Sat Jan 10 00:00:00 1970 PST
  210 |   0 | 00210              | Sun Jan 11 00:00:00 1970 PST
  211 |   1 | 00211              | Mon Jan 12 00:00:00 1970 PST
  213 | 303 | 00213_update3      | Wed Jan 14 00:00:00 1970 PST
  214 |   4 | 00214              | Thu Jan 15 00:00:00 1970 PST
  216 |   6 | 00216              | Sat Jan 17 00:00:00 1970 PST
  217 | 407 | 00217_update7      | Sun Jan 18 00:00:00 1970 PST
  218 |   8 | 00218              | Mon Jan 19 00:00:00 1970 PST
  219 | 509 | 00219_update9      | Tue Jan 20 00:00:00 1970 PST
  220 |   0 | 00220              | Wed Jan 21 00:00:00 1970 PST
  221 |   1 | 00221              | Thu Jan 22 00:00:00 1970 PST
  223 | 303 | 00223_update3      | Sat Jan 24 00:00:00 1970 PST
  224 |   4 | 00224              | Sun Jan 25 00:00:00 1970 PST
  226 |   6 | 00226              | Tue Jan 27 00:00:00 1970 PST
  227 | 407 | 00227_update7      | Wed Jan 28 00:00:00 1970 PST
  228 |   8 | 00228              | Thu Jan 29 00:00:00 1970 PST
  229 | 509 | 00229_update9      | Fri Jan 30 00:00:00 1970 PST
  230 |   0 | 00230              | Sat Jan 31 00:00:00 1970 PST
  231 |   1 | 00231              | Sun Feb 01 00:00:00 1970 PST
  233 | 303 | 00233_update3      | Tue Feb 03 00:00:00 1970 PST
  234 |   4 | 00234              | Wed Feb 04 00:00:00 1970 PST
  236 |   6 | 00236              | Fri Feb 06 00:00:00 1970 PST
  237 | 407 | 00237_update7      | Sat Feb 07 00:00:00 1970 PST
  238 |   8 | 00238              | Sun Feb 08 00:00:00 1970 PST
  239 | 509 | 00239_update9      | Mon Feb 09 00:00:00 1970 PST
  240 |   0 | 00240              | Tue Feb 10 00:00:00 1970 PST
  241 |   1 | 00241              | Wed Feb 11 00:00:00 1970 PST
  243 | 303 | 00243_update3      | Fri Feb 13 00:00:00 1970 PST
  244 |   4 | 00244              | Sat Feb 14 00:00:00 1970 PST
  246 |   6 | 00246              | Mon Feb 16 00:00:00 1970 PST
  247 | 407 | 00247_update7      | Tue Feb 17 00:00:00 1970 PST
  248 |   8 | 00248              | Wed Feb 18 00:00:00 1970 PST
  249 | 509 | 00249_update9      | Thu Feb 19 00:00:00 1970 PST
  250 |   0 | 00250              | Fri Feb 20 00:00:00 1970 PST
  251 |   1 | 00251              | Sat Feb 21 00:00:00 1970 PST
  253 | 303 | 00253_update3      | Mon Feb 23 00:00:00 1970 PST
  254 |   4 | 00254              | Tue Feb 24 00:00:00 1970 PST
  256 |   6 | 00256              | Thu Feb 26 00:00:00 1970 PST
  257 | 407 | 00257_update7      | Fri Feb 27 00:00:00 1970 PST
  258 |   8 | 00258              | Sat Feb 28 00:00:00 1970 PST
  259 | 509 | 00259_update9      | Sun Mar 01 00:00:00 1970 PST
  260 |   0 | 00260              | Mon Mar 02 00:00:00 1970 PST
  261 |   1 | 00261              | Tue Mar 03 00:00:00 1970 PST
  263 | 303 | 00263_update3      | Thu Mar 05 00:00:00 1970 PST
  264 |   4 | 00264              | Fri Mar 06 00:00:00 1970 PST
  266 |   6 | 00266              | Sun Mar 08 00:00:00 1970 PST
  267 | 407 | 00267_update7      | Mon Mar 09 00:00:00 1970 PST
  268 |   8 | 00268              | Tue Mar 10 00:00:00 1970 PST
  269 | 509 | 00269_update9      | Wed Mar 11 00:00:00 1970 PST
  270 |   0 | 00270              | Thu Mar 12 00:00:00 1970 PST
  271 |   1 | 00271              | Fri Mar 13 00:00:00 1970 PST
  273 | 303 | 00273_update3      | Sun Mar 15 00:00:00 1970 PST
  274 |   4 | 00274              | Mon Mar 16 00:00:00 1970 PST
  276 |   6 | 00276              | Wed Mar 18 00:00:00 1970 PST
  277 | 407 | 00277_update7      | Thu Mar 19 00:00:00 1970 PST
  278 |   8 | 00278              | Fri Mar 20 00:00:00 1970 PST
  279 | 509 | 00279_update9      | Sat Mar 21 00:00:00 1970 PST
  280 |   0 | 00280              | Sun Mar 22 00:00:00 1970 PST
  281 |   1 | 00281              | Mon Mar 23 00:00:00 1970 PST
  283 | 303 | 00283_update3      | Wed Mar 25 00:00:00 1970 PST
  284 |   4 | 00284              | Thu Mar 26 00:00:00 1970 PST
  286 |   6 | 00286              | Sat Mar 28 00:00:00 1970 PST
  287 | 407 | 00287_update7      | Sun Mar 29 00:00:00 1970 PST
  288 |   8 | 00288              | Mon Mar 30 00:00:00 1970 PST
  289 | 509 | 00289_update9      | Tue Mar 31 00:00:00 1970 PST
  290 |   0 | 00290              | Wed Apr 01 00:00:00 1970 PST
  291 |   1 | 00291              | Thu Apr 02 00:00:00 1970 PST
  293 | 303 | 00293_update3      | Sat Apr 04 00:00:00 1970 PST
  294 |   4 | 00294              | Sun Apr 05 00:00:00 1970 PST
  296 |   6 | 00296              | Tue Apr 07 00:00:00 1970 PST
  297 | 407 | 00297_update7      | Wed Apr 08 00:00:00 1970 PST
  298 |   8 | 00298              | Thu Apr 09 00:00:00 1970 PST
  299 | 509 | 00299_update9      | Fri Apr 10 00:00:00 1970 PST
  300 |   0 | 00300              | Thu Jan 01 00:00:00 1970 PST
  301 |   1 | 00301              | Fri Jan 02 00:00:00 1970 PST
  303 | 303 | 00303_update3      | Sun Jan 04 00:00:00 1970 PST
  304 |   4 | 00304              | Mon Jan 05 00:00:00 1970 PST
  306 |   6 | 00306              | Wed Jan 07 00:00:00 1970 PST
  307 | 407 | 00307_update7      | Thu Jan 08 00:00:00 1970 PST
  308 |   8 | 00308              | Fri Jan 09 00:00:00 1970 PST
  309 | 509 | 00309_update9      | Sat Jan 10 00:00:00 1970 PST
  310 |   0 | 00310              | Sun Jan 11 00:00:00 1970 PST
  311 |   1 | 00311              | Mon Jan 12 00:00:00 1970 PST
  313 | 303 | 00313_update3      | Wed Jan 14 00:00:00 1970 PST
  314 |   4 | 00314              | Thu Jan 15 00:00:00 1970 PST
  316 |   6 | 00316              | Sat Jan 17 00:00:00 1970 PST
  317 | 407 | 00317_update7      | Sun Jan 18 00:00:00 1970 PST
  318 |   8 | 00318              | Mon Jan 19 00:00:00 1970 PST
  319 | 509 | 00319_update9      | Tue Jan 20 00:00:00 1970 PST
  320 |   0 | 00320              | Wed Jan 21 00:00:00 1970 PST
  321 |   1 | 00321              | Thu Jan 22 00:00:00 1970 PST
  323 | 303 | 00323_update3      | Sat Jan 24 00:00:00 1970 PST
  324 |   4 | 00324              | Sun Jan 25 00:00:00 1970 PST
  326 |   6 | 00326              | Tue Jan 27 00:00:00 1970 PST
  327 | 407 | 00327_update7      | Wed Jan 28 00:00:00 1970 PST
  328 |   8 | 00328              | Thu Jan 29 00:00:00 1970 PST
  329 | 509 | 00329_update9      | Fri Jan 30 00:00:00 1970 PST
  330 |   0 | 00330              | Sat Jan 31 00:00:00 1970 PST
  331 |   1 | 00331              | Sun Feb 01 00:00:00 1970 PST
  333 | 303 | 00333_update3      | Tue Feb 03 00:00:00 1970 PST
  334 |   4 | 00334              | Wed Feb 04 00:00:00 1970 PST
  336 |   6 | 00336              | Fri Feb 06 00:00:00 1970 PST
  337 | 407 | 00337_update7      | Sat Feb 07 00:00:00 1970 PST
  338 |   8 | 00338              | Sun Feb 08 00:00:00 1970 PST
  339 | 509 | 00339_update9      | Mon Feb 09 00:00:00 1970 PST
  340 |   0 | 00340              | Tue Feb 10 00:00:00 1970 PST
  341 |   1 | 00341              | Wed Feb 11 00:00:00 1970 PST
  343 | 303 | 00343_update3      | Fri Feb 13 00:00:00 1970 PST
  344 |   4 | 00344              | Sat Feb 14 00:00:00 1970 PST
  346 |   6 | 00346              | Mon Feb 16 00:00:00 1970 PST
  347 | 407 | 00347_update7      | Tue Feb 17 00:00:00 1970 PST
  348 |   8 | 00348              | Wed Feb 18 00:00:00 1970 PST
  349 | 509 | 00349_update9      | Thu Feb 19 00:00:00 1970 PST
  350 |   0 | 00350              | Fri Feb 20 00:00:00 1970 PST
  351 |   1 | 00351              | Sat Feb 21 00:00:00 1970 PST
  353 | 303 | 00353_update3      | Mon Feb 23 00:00:00 1970 PST
  354 |   4 | 00354              | Tue Feb 24 00:00:00 1970 PST
  356 |   6 | 00356              | Thu Feb 26 00:00:00 1970 PST
  357 | 407 | 00357_update7      | Fri Feb 27 00:00:00 1970 PST
  358 |   8 | 00358              | Sat Feb 28 00:00:00 1970 PST
  359 | 509 | 00359_update9      | Sun Mar 01 00:00:00 1970 PST
  360 |   0 | 00360              | Mon Mar 02 00:00:00 1970 PST
  361 |   1 | 00361              | Tue Mar 03 00:00:00 1970 PST
  363 | 303 | 00363_update3      | Thu Mar 05 00:00:00 1970 PST
  364 |   4 | 00364              | Fri Mar 06 00:00:00 1970 PST
  366 |   6 | 00366              | Sun Mar 08 00:00:00 1970 PST
  367 | 407 | 00367_update7      | Mon Mar 09 00:00:00 1970 PST
  368 |   8 | 00368              | Tue Mar 10 00:00:00 1970 PST
  369 | 509 | 00369_update9      | Wed Mar 11 00:00:00 1970 PST
  370 |   0 | 00370              | Thu Mar 12 00:00:00 1970 PST
  371 |   1 | 00371              | Fri Mar 13 00:00:00 1970 PST
  373 | 303 | 00373_update3      | Sun Mar 15 00:00:00 1970 PST
  374 |   4 | 00374              | Mon Mar 16 00:00:00 1970 PST
  376 |   6 | 00376              | Wed Mar 18 00:00:00 1970 PST
  377 | 407 | 00377_update7      | Thu Mar 19 00:00:00 1970 PST
  378 |   8 | 00378              | Fri Mar 20 00:00:00 1970 PST
  379 | 509 | 00379_update9      | Sat Mar 21 00:00:00 1970 PST
  380 |   0 | 00380              | Sun Mar 22 00:00:00 1970 PST
  381 |   1 | 00381              | Mon Mar 23 00:00:00 1970 PST
  383 | 303 | 00383_update3      | Wed Mar 25 00:00:00 1970 PST
  384 |   4 | 00384              | Thu Mar 26 00:00:00 1970 PST
  386 |   6 | 00386              | Sat Mar 28 00:00:00 1970 PST
  387 | 407 | 00387_update7      | Sun Mar 29 00:00:00 1970 PST
  388 |   8 | 00388              | Mon Mar 30 00:00:00 1970 PST
  389 | 509 | 00389_update9      | Tue Mar 31 00:00:00 1970 PST
  390 |   0 | 00390              | Wed Apr 01 00:00:00 1970 PST
  391 |   1 | 00391              | Thu Apr 02 00:00:00 1970 PST
  393 | 303 | 00393_update3      | Sat Apr 04 00:00:00 1970 PST
  394 |   4 | 00394              | Sun Apr 05 00:00:00 1970 PST
  396 |   6 | 00396              | Tue Apr 07 00:00:00 1970 PST
  397 | 407 | 00397_update7      | Wed Apr 08 00:00:00 1970 PST
  398 |   8 | 00398              | Thu Apr 09 00:00:00 1970 PST
  399 | 509 | 00399_update9      | Fri Apr 10 00:00:00 1970 PST
  400 |   0 | 00400              | Thu Jan 01 00:00:00 1970 PST
  401 |   1 | 00401              | Fri Jan 02 00:00:00 1970 PST
  403 | 303 | 00403_update3      | Sun Jan 04 00:00:00 1970 PST
  404 |   4 | 00404              | Mon Jan 05 00:00:00 1970 PST
  406 |   6 | 00406              | Wed Jan 07 00:00:00 1970 PST
  407 | 407 | 00407_update7      | Thu Jan 08 00:00:00 1970 PST
  408 |   8 | 00408              | Fri Jan 09 00:00:00 1970 PST
  409 | 509 | 00409_update9      | Sat Jan 10 00:00:00 1970 PST
  410 |   0 | 00410              | Sun Jan 11 00:00:00 1970 PST
  411 |   1 | 00411              | Mon Jan 12 00:00:00 1970 PST
  413 | 303 | 00413_update3      | Wed Jan 14 00:00:00 1970 PST
  414 |   4 | 00414              | Thu Jan 15 00:00:00 1970 PST
  416 |   6 | 00416              | Sat Jan 17 00:00:00 1970 PST
  417 | 407 | 00417_update7      | Sun Jan 18 00:00:00 1970 PST
  418 |   8 | 00418              | Mon Jan 19 00:00:00 1970 PST
  419 | 509 | 00419_update9      | Tue Jan 20 00:00:00 1970 PST
  420 |   0 | 00420              | Wed Jan 21 00:00:00 1970 PST
  421 |   1 | 00421              | Thu Jan 22 00:00:00 1970 PST
  423 | 303 | 00423_update3      | Sat Jan 24 00:00:00 1970 PST
  424 |   4 | 00424              | Sun Jan 25 00:00:00 1970 PST
  426 |   6 | 00426              | Tue Jan 27 00:00:00 1970 PST
  427 | 407 | 00427_update7      | Wed Jan 28 00:00:00 1970 PST
  428 |   8 | 00428              | Thu Jan 29 00:00:00 1970 PST
  429 | 509 | 00429_update9      | Fri Jan 30 00:00:00 1970 PST
  430 |   0 | 00430              | Sat Jan 31 00:00:00 1970 PST
  431 |   1 | 00431              | Sun Feb 01 00:00:00 1970 PST
  433 | 303 | 00433_update3      | Tue Feb 03 00:00:00 1970 PST
  434 |   4 | 00434              | Wed Feb 04 00:00:00 1970 PST
  436 |   6 | 00436              | Fri Feb 06 00:00:00 1970 PST
  437 | 407 | 00437_update7      | Sat Feb 07 00:00:00 1970 PST
  438 |   8 | 00438              | Sun Feb 08 00:00:00 1970 PST
  439 | 509 | 00439_update9      | Mon Feb 09 00:00:00 1970 PST
  440 |   0 | 00440              | Tue Feb 10 00:00:00 1970 PST
  441 |   1 | 00441              | Wed Feb 11 00:00:00 1970 PST
  443 | 303 | 00443_update3      | Fri Feb 13 00:00:00 1970 PST
  444 |   4 | 00444              | Sat Feb 14 00:00:00 1970 PST
  446 |   6 | 00446              | Mon Feb 16 00:00:00 1970 PST
  447 | 407 | 00447_update7      | Tue Feb 17 00:00:00 1970 PST
  448 |   8 | 00448              | Wed Feb 18 00:00:00 1970 PST
  449 | 509 | 00449_update9      | Thu Feb 19 00:00:00 1970 PST
  450 |   0 | 00450              | Fri Feb 20 00:00:00 1970 PST
  451 |   1 | 00451              | Sat Feb 21 00:00:00 1970 PST
  453 | 303 | 00453_update3      | Mon Feb 23 00:00:00 1970 PST
  454 |   4 | 00454              | Tue Feb 24 00:00:00 1970 PST
  456 |   6 | 00456              | Thu Feb 26 00:00:00 1970 PST
  457 | 407 | 00457_update7      | Fri Feb 27 00:00:00 1970 PST
  458 |   8 | 00458              | Sat Feb 28 00:00:00 1970 PST
  459 | 509 | 00459_update9      | Sun Mar 01 00:00:00 1970 PST
  460 |   0 | 00460              | Mon Mar 02 00:00:00 1970 PST
  461 |   1 | 00461              | Tue Mar 03 00:00:00 1970 PST
  463 | 303 | 00463_update3      | Thu Mar 05 00:00:00 1970 PST
  464 |   4 | 00464              | Fri Mar 06 00:00:00 1970 PST
  466 |   6 | 00466              | Sun Mar 08 00:00:00 1970 PST
  467 | 407 | 00467_update7      | Mon Mar 09 00:00:00 1970 PST
  468 |   8 | 00468              | Tue Mar 10 00:00:00 1970 PST
  469 | 509 | 00469_update9      | Wed Mar 11 00:00:00 1970 PST
  470 |   0 | 00470              | Thu Mar 12 00:00:00 1970 PST
  471 |   1 | 00471              | Fri Mar 13 00:00:00 1970 PST
  473 | 303 | 00473_update3      | Sun Mar 15 00:00:00 1970 PST
  474 |   4 | 00474              | Mon Mar 16 00:00:00 1970 PST
  476 |   6 | 00476              | Wed Mar 18 00:00:00 1970 PST
  477 | 407 | 00477_update7      | Thu Mar 19 00:00:00 1970 PST
  478 |   8 | 00478              | Fri Mar 20 00:00:00 1970 PST
  479 | 509 | 00479_update9      | Sat Mar 21 00:00:00 1970 PST
  480 |   0 | 00480              | Sun Mar 22 00:00:00 1970 PST
  481 |   1 | 00481              | Mon Mar 23 00:00:00 1970 PST
  483 | 303 | 00483_update3      | Wed Mar 25 00:00:00 1970 PST
  484 |   4 | 00484              | Thu Mar 26 00:00:00 1970 PST
  486 |   6 | 00486              | Sat Mar 28 00:00:00 1970 PST
  487 | 407 | 00487_update7      | Sun Mar 29 00:00:00 1970 PST
  488 |   8 | 00488              | Mon Mar 30 00:00:00 1970 PST
  489 | 509 | 00489_update9      | Tue Mar 31 00:00:00 1970 PST
  490 |   0 | 00490              | Wed Apr 01 00:00:00 1970 PST
  491 |   1 | 00491              | Thu Apr 02 00:00:00 1970 PST
  493 | 303 | 00493_update3      | Sat Apr 04 00:00:00 1970 PST
  494 |   4 | 00494              | Sun Apr 05 00:00:00 1970 PST
  496 |   6 | 00496              | Tue Apr 07 00:00:00 1970 PST
  497 | 407 | 00497_update7      | Wed Apr 08 00:00:00 1970 PST
  498 |   8 | 00498              | Thu Apr 09 00:00:00 1970 PST
  499 | 509 | 00499_update9      | Fri Apr 10 00:00:00 1970 PST
  500 |   0 | 00500              | Thu Jan 01 00:00:00 1970 PST
  501 |   1 | 00501              | Fri Jan 02 00:00:00 1970 PST
  503 | 303 | 00503_update3      | Sun Jan 04 00:00:00 1970 PST
  504 |   4 | 00504              | Mon Jan 05 00:00:00 1970 PST
  506 |   6 | 00506              | Wed Jan 07 00:00:00 1970 PST
  507 | 407 | 00507_update7      | Thu Jan 08 00:00:00 1970 PST
  508 |   8 | 00508              | Fri Jan 09 00:00:00 1970 PST
  509 | 509 | 00509_update9      | Sat Jan 10 00:00:00 1970 PST
  510 |   0 | 00510              | Sun Jan 11 00:00:00 1970 PST
  511 |   1 | 00511              | Mon Jan 12 00:00:00 1970 PST
  513 | 303 | 00513_update3      | Wed Jan 14 00:00:00 1970 PST
  514 |   4 | 00514              | Thu Jan 15 00:00:00 1970 PST
  516 |   6 | 00516              | Sat Jan 17 00:00:00 1970 PST
  517 | 407 | 00517_update7      | Sun Jan 18 00:00:00 1970 PST
  518 |   8 | 00518              | Mon Jan 19 00:00:00 1970 PST
  519 | 509 | 00519_update9      | Tue Jan 20 00:00:00 1970 PST
  520 |   0 | 00520              | Wed Jan 21 00:00:00 1970 PST
  521 |   1 | 00521              | Thu Jan 22 00:00:00 1970 PST
  523 | 303 | 00523_update3      | Sat Jan 24 00:00:00 1970 PST
  524 |   4 | 00524              | Sun Jan 25 00:00:00 1970 PST
  526 |   6 | 00526              | Tue Jan 27 00:00:00 1970 PST
  527 | 407 | 00527_update7      | Wed Jan 28 00:00:00 1970 PST
  528 |   8 | 00528              | Thu Jan 29 00:00:00 1970 PST
  529 | 509 | 00529_update9      | Fri Jan 30 00:00:00 1970 PST
  530 |   0 | 00530              | Sat Jan 31 00:00:00 1970 PST
  531 |   1 | 00531              | Sun Feb 01 00:00:00 1970 PST
  533 | 303 | 00533_update3      | Tue Feb 03 00:00:00 1970 PST
  534 |   4 | 00534              | Wed Feb 04 00:00:00 1970 PST
  536 |   6 | 00536              | Fri Feb 06 00:00:00 1970 PST
  537 | 407 | 00537_update7      | Sat Feb 07 00:00:00 1970 PST
  538 |   8 | 00538              | Sun Feb 08 00:00:00 1970 PST
  539 | 509 | 00539_update9      | Mon Feb 09 00:00:00 1970 PST
  540 |   0 | 00540              | Tue Feb 10 00:00:00 1970 PST
  541 |   1 | 00541              | Wed Feb 11 00:00:00 1970 PST
  543 | 303 | 00543_update3      | Fri Feb 13 00:00:00 1970 PST
  544 |   4 | 00544              | Sat Feb 14 00:00:00 1970 PST
  546 |   6 | 00546              | Mon Feb 16 00:00:00 1970 PST
  547 | 407 | 00547_update7      | Tue Feb 17 00:00:00 1970 PST
  548 |   8 | 00548              | Wed Feb 18 00:00:00 1970 PST
  549 | 509 | 00549_update9      | Thu Feb 19 00:00:00 1970 PST
  550 |   0 | 00550              | Fri Feb 20 00:00:00 1970 PST
  551 |   1 | 00551              | Sat Feb 21 00:00:00 1970 PST
  553 | 303 | 00553_update3      | Mon Feb 23 00:00:00 1970 PST
  554 |   4 | 00554              | Tue Feb 24 00:00:00 1970 PST
  556 |   6 | 00556              | Thu Feb 26 00:00:00 1970 PST
  557 | 407 | 00557_update7      | Fri Feb 27 00:00:00 1970 PST
  558 |   8 | 00558              | Sat Feb 28 00:00:00 1970 PST
  559 | 509 | 00559_update9      | Sun Mar 01 00:00:00 1970 PST
  560 |   0 | 00560              | Mon Mar 02 00:00:00 1970 PST
  561 |   1 | 00561              | Tue Mar 03 00:00:00 1970 PST
  563 | 303 | 00563_update3      | Thu Mar 05 00:00:00 1970 PST
  564 |   4 | 00564              | Fri Mar 06 00:00:00 1970 PST
  566 |   6 | 00566              | Sun Mar 08 00:00:00 1970 PST
  567 | 407 | 00567_update7      | Mon Mar 09 00:00:00 1970 PST
  568 |   8 | 00568              | Tue Mar 10 00:00:00 1970 PST
  569 | 509 | 00569_update9      | Wed Mar 11 00:00:00 1970 PST
  570 |   0 | 00570              | Thu Mar 12 00:00:00 1970 PST
  571 |   1 | 00571              | Fri Mar 13 00:00:00 1970 PST
  573 | 303 | 00573_update3      | Sun Mar 15 00:00:00 1970 PST
  574 |   4 | 00574              | Mon Mar 16 00:00:00 1970 PST
  576 |   6 | 00576              | Wed Mar 18 00:00:00 1970 PST
  577 | 407 | 00577_update7      | Thu Mar 19 00:00:00 1970 PST
  578 |   8 | 00578              | Fri Mar 20 00:00:00 1970 PST
  579 | 509 | 00579_update9      | Sat Mar 21 00:00:00 1970 PST
  580 |   0 | 00580              | Sun Mar 22 00:00:00 1970 PST
  581 |   1 | 00581              | Mon Mar 23 00:00:00 1970 PST
  583 | 303 | 00583_update3      | Wed Mar 25 00:00:00 1970 PST
  584 |   4 | 00584              | Thu Mar 26 00:00:00 1970 PST
  586 |   6 | 00586              | Sat Mar 28 00:00:00 1970 PST
  587 | 407 | 00587_update7      | Sun Mar 29 00:00:00 1970 PST
  588 |   8 | 00588              | Mon Mar 30 00:00:00 1970 PST
  589 | 509 | 00589_update9      | Tue Mar 31 00:00:00 1970 PST
  590 |   0 | 00590              | Wed Apr 01 00:00:00 1970 PST
  591 |   1 | 00591              | Thu Apr 02 00:00:00 1970 PST
  593 | 303 | 00593_update3      | Sat Apr 04 00:00:00 1970 PST
  594 |   4 | 00594              | Sun Apr 05 00:00:00 1970 PST
  596 |   6 | 00596              | Tue Apr 07 00:00:00 1970 PST
  597 | 407 | 00597_update7      | Wed Apr 08 00:00:00 1970 PST
  598 |   8 | 00598              | Thu Apr 09 00:00:00 1970 PST
  599 | 509 | 00599_update9      | Fri Apr 10 00:00:00 1970 PST
  600 |   0 | 00600              | Thu Jan 01 00:00:00 1970 PST
  601 |   1 | 00601              | Fri Jan 02 00:00:00 1970 PST
  603 | 303 | 00603_update3      | Sun Jan 04 00:00:00 1970 PST
  604 |   4 | 00604              | Mon Jan 05 00:00:00 1970 PST
  606 |   6 | 00606              | Wed Jan 07 00:00:00 1970 PST
  607 | 407 | 00607_update7      | Thu Jan 08 00:00:00 1970 PST
  608 |   8 | 00608              | Fri Jan 09 00:00:00 1970 PST
  609 | 509 | 00609_update9      | Sat Jan 10 00:00:00 1970 PST
  610 |   0 | 00610              | Sun Jan 11 00:00:00 1970 PST
  611 |   1 | 00611              | Mon Jan 12 00:00:00 1970 PST
  613 | 303 | 00613_update3      | Wed Jan 14 00:00:00 1970 PST
  614 |   4 | 00614              | Thu Jan 15 00:00:00 1970 PST
  616 |   6 | 00616              | Sat Jan 17 00:00:00 1970 PST
  617 | 407 | 00617_update7      | Sun Jan 18 00:00:00 1970 PST
  618 |   8 | 00618              | Mon Jan 19 00:00:00 1970 PST
  619 | 509 | 00619_update9      | Tue Jan 20 00:00:00 1970 PST
  620 |   0 | 00620              | Wed Jan 21 00:00:00 1970 PST
  621 |   1 | 00621              | Thu Jan 22 00:00:00 1970 PST
  623 | 303 | 00623_update3      | Sat Jan 24 00:00:00 1970 PST
  624 |   4 | 00624              | Sun Jan 25 00:00:00 1970 PST
  626 |   6 | 00626              | Tue Jan 27 00:00:00 1970 PST
  627 | 407 | 00627_update7      | Wed Jan 28 00:00:00 1970 PST
  628 |   8 | 00628              | Thu Jan 29 00:00:00 1970 PST
  629 | 509 | 00629_update9      | Fri Jan 30 00:00:00 1970 PST
  630 |   0 | 00630              | Sat Jan 31 00:00:00 1970 PST
  631 |   1 | 00631              | Sun Feb 01 00:00:00 1970 PST
  633 | 303 | 00633_update3      | Tue Feb 03 00:00:00 1970 PST
  634 |   4 | 00634              | Wed Feb 04 00:00:00 1970 PST
  636 |   6 | 00636              | Fri Feb 06 00:00:00 1970 PST
  637 | 407 | 00637_update7      | Sat Feb 07 00:00:00 1970 PST
  638 |   8 | 00638              | Sun Feb 08 00:00:00 1970 PST
  639 | 509 | 00639_update9      | Mon Feb 09 00:00:00 1970 PST
  640 |   0 | 00640              | Tue Feb 10 00:00:00 1970 PST
  641 |   1 | 00641              | Wed Feb 11 00:00:00 1970 PST
  643 | 303 | 00643_update3      | Fri Feb 13 00:00:00 1970 PST
  644 |   4 | 00644              | Sat Feb 14 00:00:00 1970 PST
  646 |   6 | 00646              | Mon Feb 16 00:00:00 1970 PST
  647 | 407 | 00647_update7      | Tue Feb 17 00:00:00 1970 PST
  648 |   8 | 00648              | Wed Feb 18 00:00:00 1970 PST
  649 | 509 | 00649_update9      | Thu Feb 19 00:00:00 1970 PST
  650 |   0 | 00650              | Fri Feb 20 00:00:00 1970 PST
  651 |   1 | 00651              | Sat Feb 21 00:00:00 1970 PST
  653 | 303 | 00653_update3      | Mon Feb 23 00:00:00 1970 PST
  654 |   4 | 00654              | Tue Feb 24 00:00:00 1970 PST
  656 |   6 | 00656              | Thu Feb 26 00:00:00 1970 PST
  657 | 407 | 00657_update7      | Fri Feb 27 00:00:00 1970 PST
  658 |   8 | 00658              | Sat Feb 28 00:00:00 1970 PST
  659 | 509 | 00659_update9      | Sun Mar 01 00:00:00 1970 PST
  660 |   0 | 00660              | Mon Mar 02 00:00:00 1970 PST
  661 |   1 | 00661              | Tue Mar 03 00:00:00 1970 PST
  663 | 303 | 00663_update3      | Thu Mar 05 00:00:00 1970 PST
  664 |   4 | 00664              | Fri Mar 06 00:00:00 1970 PST
  666 |   6 | 00666              | Sun Mar 08 00:00:00 1970 PST
  667 | 407 | 00667_update7      | Mon Mar 09 00:00:00 1970 PST
  668 |   8 | 00668              | Tue Mar 10 00:00:00 1970 PST
  669 | 509 | 00669_update9      | Wed Mar 11 00:00:00 1970 PST
  670 |   0 | 00670              | Thu Mar 12 00:00:00 1970 PST
  671 |   1 | 00671              | Fri Mar 13 00:00:00 1970 PST
  673 | 303 | 00673_update3      | Sun Mar 15 00:00:00 1970 PST
  674 |   4 | 00674              | Mon Mar 16 00:00:00 1970 PST
  676 |   6 | 00676              | Wed Mar 18 00:00:00 1970 PST
  677 | 407 | 00677_update7      | Thu Mar 19 00:00:00 1970 PST
  678 |   8 | 00678              | Fri Mar 20 00:00:00 1970 PST
  679 | 509 | 00679_update9      | Sat Mar 21 00:00:00 1970 PST
  680 |   0 | 00680              | Sun Mar 22 00:00:00 1970 PST
  681 |   1 | 00681              | Mon Mar 23 00:00:00 1970 PST
  683 | 303 | 00683_update3      | Wed Mar 25 00:00:00 1970 PST
  684 |   4 | 00684              | Thu Mar 26 00:00:00 1970 PST
  686 |   6 | 00686              | Sat Mar 28 00:00:00 1970 PST
  687 | 407 | 00687_update7      | Sun Mar 29 00:00:00 1970 PST
  688 |   8 | 00688              | Mon Mar 30 00:00:00 1970 PST
  689 | 509 | 00689_update9      | Tue Mar 31 00:00:00 1970 PST
  690 |   0 | 00690              | Wed Apr 01 00:00:00 1970 PST
  691 |   1 | 00691              | Thu Apr 02 00:00:00 1970 PST
  693 | 303 | 00693_update3      | Sat Apr 04 00:00:00 1970 PST
  694 |   4 | 00694              | Sun Apr 05 00:00:00 1970 PST
  696 |   6 | 00696              | Tue Apr 07 00:00:00 1970 PST
  697 | 407 | 00697_update7      | Wed Apr 08 00:00:00 1970 PST
  698 |   8 | 00698              | Thu Apr 09 00:00:00 1970 PST
  699 | 509 | 00699_update9      | Fri Apr 10 00:00:00 1970 PST
  700 |   0 | 00700              | Thu Jan 01 00:00:00 1970 PST
  701 |   1 | 00701              | Fri Jan 02 00:00:00 1970 PST
  703 | 303 | 00703_update3      | Sun Jan 04 00:00:00 1970 PST
  704 |   4 | 00704              | Mon Jan 05 00:00:00 1970 PST
  706 |   6 | 00706              | Wed Jan 07 00:00:00 1970 PST
  707 | 407 | 00707_update7      | Thu Jan 08 00:00:00 1970 PST
  708 |   8 | 00708              | Fri Jan 09 00:00:00 1970 PST
  709 | 509 | 00709_update9      | Sat Jan 10 00:00:00 1970 PST
  710 |   0 | 00710              | Sun Jan 11 00:00:00 1970 PST
  711 |   1 | 00711              | Mon Jan 12 00:00:00 1970 PST
  713 | 303 | 00713_update3      | Wed Jan 14 00:00:00 1970 PST
  714 |   4 | 00714              | Thu Jan 15 00:00:00 1970 PST
  716 |   6 | 00716              | Sat Jan 17 00:00:00 1970 PST
  717 | 407 | 00717_update7      | Sun Jan 18 00:00:00 1970 PST
  718 |   8 | 00718              | Mon Jan 19 00:00:00 1970 PST
  719 | 509 | 00719_update9      | Tue Jan 20 00:00:00 1970 PST
  720 |   0 | 00720              | Wed Jan 21 00:00:00 1970 PST
  721 |   1 | 00721              | Thu Jan 22 00:00:00 1970 PST
  723 | 303 | 00723_update3      | Sat Jan 24 00:00:00 1970 PST
  724 |   4 | 00724              | Sun Jan 25 00:00:00 1970 PST
  726 |   6 | 00726              | Tue Jan 27 00:00:00 1970 PST
  727 | 407 | 00727_update7      | Wed Jan 28 00:00:00 1970 PST
  728 |   8 | 00728              | Thu Jan 29 00:00:00 1970 PST
  729 | 509 | 00729_update9      | Fri Jan 30 00:00:00 1970 PST
  730 |   0 | 00730              | Sat Jan 31 00:00:00 1970 PST
  731 |   1 | 00731              | Sun Feb 01 00:00:00 1970 PST
  733 | 303 | 00733_update3      | Tue Feb 03 00:00:00 1970 PST
  734 |   4 | 00734              | Wed Feb 04 00:00:00 1970 PST
  736 |   6 | 00736              | Fri Feb 06 00:00:00 1970 PST
  737 | 407 | 00737_update7      | Sat Feb 07 00:00:00 1970 PST
  738 |   8 | 00738              | Sun Feb 08 00:00:00 1970 PST
  739 | 509 | 00739_update9      | Mon Feb 09 00:00:00 1970 PST
  740 |   0 | 00740              | Tue Feb 10 00:00:00 1970 PST
  741 |   1 | 00741              | Wed Feb 11 00:00:00 1970 PST
  743 | 303 | 00743_update3      | Fri Feb 13 00:00:00 1970 PST
  744 |   4 | 00744              | Sat Feb 14 00:00:00 1970 PST
  746 |   6 | 00746              | Mon Feb 16 00:00:00 1970 PST
  747 | 407 | 00747_update7      | Tue Feb 17 00:00:00 1970 PST
  748 |   8 | 00748              | Wed Feb 18 00:00:00 1970 PST
  749 | 509 | 00749_update9      | Thu Feb 19 00:00:00 1970 PST
  750 |   0 | 00750              | Fri Feb 20 00:00:00 1970 PST
  751 |   1 | 00751              | Sat Feb 21 00:00:00 1970 PST
  753 | 303 | 00753_update3      | Mon Feb 23 00:00:00 1970 PST
  754 |   4 | 00754              | Tue Feb 24 00:00:00 1970 PST
  756 |   6 | 00756              | Thu Feb 26 00:00:00 1970 PST
  757 | 407 | 00757_update7      | Fri Feb 27 00:00:00 1970 PST
  758 |   8 | 00758              | Sat Feb 28 00:00:00 1970 PST
  759 | 509 | 00759_update9      | Sun Mar 01 00:00:00 1970 PST
  760 |   0 | 00760              | Mon Mar 02 00:00:00 1970 PST
  761 |   1 | 00761              | Tue Mar 03 00:00:00 1970 PST
  763 | 303 | 00763_update3      | Thu Mar 05 00:00:00 1970 PST
  764 |   4 | 00764              | Fri Mar 06 00:00:00 1970 PST
  766 |   6 | 00766              | Sun Mar 08 00:00:00 1970 PST
  767 | 407 | 00767_update7      | Mon Mar 09 00:00:00 1970 PST
  768 |   8 | 00768              | Tue Mar 10 00:00:00 1970 PST
  769 | 509 | 00769_update9      | Wed Mar 11 00:00:00 1970 PST
  770 |   0 | 00770              | Thu Mar 12 00:00:00 1970 PST
  771 |   1 | 00771              | Fri Mar 13 00:00:00 1970 PST
  773 | 303 | 00773_update3      | Sun Mar 15 00:00:00 1970 PST
  774 |   4 | 00774              | Mon Mar 16 00:00:00 1970 PST
  776 |   6 | 00776              | Wed Mar 18 00:00:00 1970 PST
  777 | 407 | 00777_update7      | Thu Mar 19 00:00:00 1970 PST
  778 |   8 | 00778              | Fri Mar 20 00:00:00 1970 PST
  779 | 509 | 00779_update9      | Sat Mar 21 00:00:00 1970 PST
  780 |   0 | 00780              | Sun Mar 22 00:00:00 1970 PST
  781 |   1 | 00781              | Mon Mar 23 00:00:00 1970 PST
  783 | 303 | 00783_update3      | Wed Mar 25 00:00:00 1970 PST
  784 |   4 | 00784              | Thu Mar 26 00:00:00 1970 PST
  786 |   6 | 00786              | Sat Mar 28 00:00:00 1970 PST
  787 | 407 | 00787_update7      | Sun Mar 29 00:00:00 1970 PST
  788 |   8 | 00788              | Mon Mar 30 00:00:00 1970 PST
  789 | 509 | 00789_update9      | Tue Mar 31 00:00:00 1970 PST
  790 |   0 | 00790              | Wed Apr 01 00:00:00 1970 PST
  791 |   1 | 00791              | Thu Apr 02 00:00:00 1970 PST
  793 | 303 | 00793_update3      | Sat Apr 04 00:00:00 1970 PST
  794 |   4 | 00794              | Sun Apr 05 00:00:00 1970 PST
  796 |   6 | 00796              | Tue Apr 07 00:00:00 1970 PST
  797 | 407 | 00797_update7      | Wed Apr 08 00:00:00 1970 PST
  798 |   8 | 00798              | Thu Apr 09 00:00:00 1970 PST
  799 | 509 | 00799_update9      | Fri Apr 10 00:00:00 1970 PST
  800 |   0 | 00800              | Thu Jan 01 00:00:00 1970 PST
  801 |   1 | 00801              | Fri Jan 02 00:00:00 1970 PST
  803 | 303 | 00803_update3      | Sun Jan 04 00:00:00 1970 PST
  804 |   4 | 00804              | Mon Jan 05 00:00:00 1970 PST
  806 |   6 | 00806              | Wed Jan 07 00:00:00 1970 PST
  807 | 407 | 00807_update7      | Thu Jan 08 00:00:00 1970 PST
  808 |   8 | 00808              | Fri Jan 09 00:00:00 1970 PST
  809 | 509 | 00809_update9      | Sat Jan 10 00:00:00 1970 PST
  810 |   0 | 00810              | Sun Jan 11 00:00:00 1970 PST
  811 |   1 | 00811              | Mon Jan 12 00:00:00 1970 PST
  813 | 303 | 00813_update3      | Wed Jan 14 00:00:00 1970 PST
  814 |   4 | 00814              | Thu Jan 15 00:00:00 1970 PST
  816 |   6 | 00816              | Sat Jan 17 00:00:00 1970 PST
  817 | 407 | 00817_update7      | Sun Jan 18 00:00:00 1970 PST
  818 |   8 | 00818              | Mon Jan 19 00:00:00 1970 PST
  819 | 509 | 00819_update9      | Tue Jan 20 00:00:00 1970 PST
  820 |   0 | 00820              | Wed Jan 21 00:00:00 1970 PST
  821 |   1 | 00821              | Thu Jan 22 00:00:00 1970 PST
  823 | 303 | 00823_update3      | Sat Jan 24 00:00:00 1970 PST
  824 |   4 | 00824              | Sun Jan 25 00:00:00 1970 PST
  826 |   6 | 00826              | Tue Jan 27 00:00:00 1970 PST
  827 | 407 | 00827_update7      | Wed Jan 28 00:00:00 1970 PST
  828 |   8 | 00828              | Thu Jan 29 00:00:00 1970 PST
  829 | 509 | 00829_update9      | Fri Jan 30 00:00:00 1970 PST
  830 |   0 | 00830              | Sat Jan 31 00:00:00 1970 PST
  831 |   1 | 00831              | Sun Feb 01 00:00:00 1970 PST
  833 | 303 | 00833_update3      | Tue Feb 03 00:00:00 1970 PST
  834 |   4 | 00834              | Wed Feb 04 00:00:00 1970 PST
  836 |   6 | 00836              | Fri Feb 06 00:00:00 1970 PST
  837 | 407 | 00837_update7      | Sat Feb 07 00:00:00 1970 PST
  838 |   8 | 00838              | Sun Feb 08 00:00:00 1970 PST
  839 | 509 | 00839_update9      | Mon Feb 09 00:00:00 1970 PST
  840 |   0 | 00840              | Tue Feb 10 00:00:00 1970 PST
  841 |   1 | 00841              | Wed Feb 11 00:00:00 1970 PST
  843 | 303 | 00843_update3      | Fri Feb 13 00:00:00 1970 PST
  844 |   4 | 00844              | Sat Feb 14 00:00:00 1970 PST
  846 |   6 | 00846              | Mon Feb 16 00:00:00 1970 PST
  847 | 407 | 00847_update7      | Tue Feb 17 00:00:00 1970 PST
  848 |   8 | 00848              | Wed Feb 18 00:00:00 1970 PST
  849 | 509 | 00849_update9      | Thu Feb 19 00:00:00 1970 PST
  850 |   0 | 00850              | Fri Feb 20 00:00:00 1970 PST
  851 |   1 | 00851              | Sat Feb 21 00:00:00 1970 PST
  853 | 303 | 00853_update3      | Mon Feb 23 00:00:00 1970 PST
  854 |   4 | 00854              | Tue Feb 24 00:00:00 1970 PST
  856 |   6 | 00856              | Thu Feb 26 00:00:00 1970 PST
  857 | 407 | 00857_update7      | Fri Feb 27 00:00:00 1970 PST
  858 |   8 | 00858              | Sat Feb 28 00:00:00 1970 PST
  859 | 509 | 00859_update9      | Sun Mar 01 00:00:00 1970 PST
  860 |   0 | 00860              | Mon Mar 02 00:00:00 1970 PST
  861 |   1 | 00861              | Tue Mar 03 00:00:00 1970 PST
  863 | 303 | 00863_update3      | Thu Mar 05 00:00:00 1970 PST
  864 |   4 | 00864              | Fri Mar 06 00:00:00 1970 PST
  866 |   6 | 00866              | Sun Mar 08 00:00:00 1970 PST
  867 | 407 | 00867_update7      | Mon Mar 09 00:00:00 1970 PST
  868 |   8 | 00868              | Tue Mar 10 00:00:00 1970 PST
  869 | 509 | 00869_update9      | Wed Mar 11 00:00:00 1970 PST
  870 |   0 | 00870              | Thu Mar 12 00:00:00 1970 PST
  871 |   1 | 00871              | Fri Mar 13 00:00:00 1970 PST
  873 | 303 | 00873_update3      | Sun Mar 15 00:00:00 1970 PST
  874 |   4 | 00874              | Mon Mar 16 00:00:00 1970 PST
  876 |   6 | 00876              | Wed Mar 18 00:00:00 1970 PST
  877 | 407 | 00877_update7      | Thu Mar 19 00:00:00 1970 PST
  878 |   8 | 00878              | Fri Mar 20 00:00:00 1970 PST
  879 | 509 | 00879_update9      | Sat Mar 21 00:00:00 1970 PST
  880 |   0 | 00880              | Sun Mar 22 00:00:00 1970 PST
  881 |   1 | 00881              | Mon Mar 23 00:00:00 1970 PST
  883 | 303 | 00883_update3      | Wed Mar 25 00:00:00 1970 PST
  884 |   4 | 00884              | Thu Mar 26 00:00:00 1970 PST
  886 |   6 | 00886              | Sat Mar 28 00:00:00 1970 PST
  887 | 407 | 00887_update7      | Sun Mar 29 00:00:00 1970 PST
  888 |   8 | 00888              | Mon Mar 30 00:00:00 1970 PST
  889 | 509 | 00889_update9      | Tue Mar 31 00:00:00 1970 PST
  890 |   0 | 00890              | Wed Apr 01 00:00:00 1970 PST
  891 |   1 | 00891              | Thu Apr 02 00:00:00 1970 PST
  893 | 303 | 00893_update3      | Sat Apr 04 00:00:00 1970 PST
  894 |   4 | 00894              | Sun Apr 05 00:00:00 1970 PST
  896 |   6 | 00896              | Tue Apr 07 00:00:00 1970 PST
  897 | 407 | 00897_update7      | Wed Apr 08 00:00:00 1970 PST
  898 |   8 | 00898              | Thu Apr 09 00:00:00 1970 PST
  899 | 509 | 00899_update9      | Fri Apr 10 00:00:00 1970 PST
  900 |   0 | 00900              | Thu Jan 01 00:00:00 1970 PST
  901 |   1 | 00901              | Fri Jan 02 00:00:00 1970 PST
  903 | 303 | 00903_update3      | Sun Jan 04 00:00:00 1970 PST
  904 |   4 | 00904              | Mon Jan 05 00:00:00 1970 PST
  906 |   6 | 00906              | Wed Jan 07 00:00:00 1970 PST
  907 | 407 | 00907_update7      | Thu Jan 08 00:00:00 1970 PST
  908 |   8 | 00908              | Fri Jan 09 00:00:00 1970 PST
  909 | 509 | 00909_update9      | Sat Jan 10 00:00:00 1970 PST
  910 |   0 | 00910              | Sun Jan 11 00:00:00 1970 PST
  911 |   1 | 00911              | Mon Jan 12 00:00:00 1970 PST
  913 | 303 | 00913_update3      | Wed Jan 14 00:00:00 1970 PST
  914 |   4 | 00914              | Thu Jan 15 00:00:00 1970 PST
  916 |   6 | 00916              | Sat Jan 17 00:00:00 1970 PST
  917 | 407 | 00917_update7      | Sun Jan 18 00:00:00 1970 PST
  918 |   8 | 00918              | Mon Jan 19 00:00:00 1970 PST
  919 | 509 | 00919_update9      | Tue Jan 20 00:00:00 1970 PST
  920 |   0 | 00920              | Wed Jan 21 00:00:00 1970 PST
  921 |   1 | 00921              | Thu Jan 22 00:00:00 1970 PST
  923 | 303 | 00923_update3      | Sat Jan 24 00:00:00 1970 PST
  924 |   4 | 00924              | Sun Jan 25 00:00:00 1970 PST
  926 |   6 | 00926              | Tue Jan 27 00:00:00 1970 PST
  927 | 407 | 00927_update7      | Wed Jan 28 00:00:00 1970 PST
  928 |   8 | 00928              | Thu Jan 29 00:00:00 1970 PST
  929 | 509 | 00929_update9      | Fri Jan 30 00:00:00 1970 PST
  930 |   0 | 00930              | Sat Jan 31 00:00:00 1970 PST
  931 |   1 | 00931              | Sun Feb 01 00:00:00 1970 PST
  933 | 303 | 00933_update3      | Tue Feb 03 00:00:00 1970 PST
  934 |   4 | 00934              | Wed Feb 04 00:00:00 1970 PST
  936 |   6 | 00936              | Fri Feb 06 00:00:00 1970 PST
  937 | 407 | 00937_update7      | Sat Feb 07 00:00:00 1970 PST
  938 |   8 | 00938              | Sun Feb 08 00:00:00 1970 PST
  939 | 509 | 00939_update9      | Mon Feb 09 00:00:00 1970 PST
  940 |   0 | 00940              | Tue Feb 10 00:00:00 1970 PST
  941 |   1 | 00941              | Wed Feb 11 00:00:00 1970 PST
  943 | 303 | 00943_update3      | Fri Feb 13 00:00:00 1970 PST
  944 |   4 | 00944              | Sat Feb 14 00:00:00 1970 PST
  946 |   6 | 00946              | Mon Feb 16 00:00:00 1970 PST
  947 | 407 | 00947_update7      | Tue Feb 17 00:00:00 1970 PST
  948 |   8 | 00948              | Wed Feb 18 00:00:00 1970 PST
  949 | 509 | 00949_update9      | Thu Feb 19 00:00:00 1970 PST
  950 |   0 | 00950              | Fri Feb 20 00:00:00 1970 PST
  951 |   1 | 00951              | Sat Feb 21 00:00:00 1970 PST
  953 | 303 | 00953_update3      | Mon Feb 23 00:00:00 1970 PST
  954 |   4 | 00954              | Tue Feb 24 00:00:00 1970 PST
  956 |   6 | 00956              | Thu Feb 26 00:00:00 1970 PST
  957 | 407 | 00957_update7      | Fri Feb 27 00:00:00 1970 PST
  958 |   8 | 00958              | Sat Feb 28 00:00:00 1970 PST
  959 | 509 | 00959_update9      | Sun Mar 01 00:00:00 1970 PST
  960 |   0 | 00960              | Mon Mar 02 00:00:00 1970 PST
  961 |   1 | 00961              | Tue Mar 03 00:00:00 1970 PST
  963 | 303 | 00963_update3      | Thu Mar 05 00:00:00 1970 PST
  964 |   4 | 00964              | Fri Mar 06 00:00:00 1970 PST
  966 |   6 | 00966              | Sun Mar 08 00:00:00 1970 PST
  967 | 407 | 00967_update7      | Mon Mar 09 00:00:00 1970 PST
  968 |   8 | 00968              | Tue Mar 10 00:00:00 1970 PST
  969 | 509 | 00969_update9      | Wed Mar 11 00:00:00 1970 PST
  970 |   0 | 00970              | Thu Mar 12 00:00:00 1970 PST
  971 |   1 | 00971              | Fri Mar 13 00:00:00 1970 PST
  973 | 303 | 00973_update3      | Sun Mar 15 00:00:00 1970 PST
  974 |   4 | 00974              | Mon Mar 16 00:00:00 1970 PST
  976 |   6 | 00976              | Wed Mar 18 00:00:00 1970 PST
  977 | 407 | 00977_update7      | Thu Mar 19 00:00:00 1970 PST
  978 |   8 | 00978              | Fri Mar 20 00:00:00 1970 PST
  979 | 509 | 00979_update9      | Sat Mar 21 00:00:00 1970 PST
  980 |   0 | 00980              | Sun Mar 22 00:00:00 1970 PST
  981 |   1 | 00981              | Mon Mar 23 00:00:00 1970 PST
  983 | 303 | 00983_update3      | Wed Mar 25 00:00:00 1970 PST
  984 |   4 | 00984              | Thu Mar 26 00:00:00 1970 PST
  986 |   6 | 00986              | Sat Mar 28 00:00:00 1970 PST
  987 | 407 | 00987_update7      | Sun Mar 29 00:00:00 1970 PST
  988 |   8 | 00988              | Mon Mar 30 00:00:00 1970 PST
  989 | 509 | 00989_update9      | Tue Mar 31 00:00:00 1970 PST
  990 |   0 | 00990              | Wed Apr 01 00:00:00 1970 PST
  991 |   1 | 00991              | Thu Apr 02 00:00:00 1970 PST
  993 | 303 | 00993_update3      | Sat Apr 04 00:00:00 1970 PST
  994 |   4 | 00994              | Sun Apr 05 00:00:00 1970 PST
  996 |   6 | 00996              | Tue Apr 07 00:00:00 1970 PST
  997 | 407 | 00997_update7      | Wed Apr 08 00:00:00 1970 PST
  998 |   8 | 00998              | Thu Apr 09 00:00:00 1970 PST
  999 | 509 | 00999_update9      | Fri Apr 10 00:00:00 1970 PST
 1000 |   0 | 01000              | Thu Jan 01 00:00:00 1970 PST
 1001 | 101 | 0000100001         | 
 1003 | 403 | 0000300003_update3 | 
 1004 | 104 | 0000400004         | 
 1006 | 106 | 0000600006         | 
 1007 | 507 | 0000700007_update7 | 
 1008 | 108 | 0000800008         | 
 1009 | 609 | 0000900009_update9 | 
 1010 | 100 | 0001000010         | 
 1011 | 101 | 0001100011         | 
 1013 | 403 | 0001300013_update3 | 
 1014 | 104 | 0001400014         | 
 1016 | 106 | 0001600016         | 
 1017 | 507 | 0001700017_update7 | 
 1018 | 108 | 0001800018         | 
 1019 | 609 | 0001900019_update9 | 
 1020 | 100 | 0002000020         | 
 1101 | 201 | aaa                | 
 1103 | 503 | ccc_update3        | 
 1104 | 204 | ddd                | 
(819 rows)

EXPLAIN (verbose, costs off)
INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass;
                                                                                           QUERY PLAN                                                                                            
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Insert on public.ft2
   Output: (ft2.tableoid)::regclass
   Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
   ->  Result
         Output: 1200, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2       '::character(10), NULL::user_enum
(5 rows)

INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass;
 tableoid 
----------
 ft2
(1 row)

EXPLAIN (verbose, costs off)
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass;             -- can be pushed down
                                     QUERY PLAN                                     
------------------------------------------------------------------------------------
 Update on public.ft2
   Output: (tableoid)::regclass
   ->  Foreign Update on public.ft2
         Remote SQL: UPDATE "S 1"."T 1" SET c3 = 'bar'::text WHERE (("C 1" = 1200))
(4 rows)

UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass;
 tableoid 
----------
 ft2
(1 row)

EXPLAIN (verbose, costs off)
DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;                       -- can be pushed down
                             QUERY PLAN                             
--------------------------------------------------------------------
 Delete on public.ft2
   Output: (tableoid)::regclass
   ->  Foreign Delete on public.ft2
         Remote SQL: DELETE FROM "S 1"."T 1" WHERE (("C 1" = 1200))
(4 rows)

DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
 tableoid 
----------
 ft2
(1 row)

-- Test UPDATE/DELETE with RETURNING on a three-table join
INSERT INTO ft2 (c1,c2,c3)
  SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c3 = 'foo'
  FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
  WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
  RETURNING ft2, ft2.*, ft4, ft4.*;       -- can be pushed down
                                                                                                                                                                          QUERY PLAN                                                                                                                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Update on public.ft2
   Output: ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3
   ->  Foreign Update
         Remote SQL: UPDATE "S 1"."T 1" r1 SET c3 = 'foo'::text FROM ("S 1"."T 3" r2 INNER JOIN "S 1"."T 4" r3 ON (TRUE)) WHERE ((r2.c1 = r3.c1)) AND ((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)) RETURNING r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1, r2.c2, r2.c3
(4 rows)

UPDATE ft2 SET c3 = 'foo'
  FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
  WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1
  RETURNING ft2, ft2.*, ft4, ft4.*;
              ft2               |  c1  | c2 | c3  | c4 | c5 | c6 |     c7     | c8 |      ft4       | c1 | c2 |   c3   
--------------------------------+------+----+-----+----+----+----+------------+----+----------------+----+----+--------
 (1206,6,foo,,,,"ft2       ",)  | 1206 |  6 | foo |    |    |    | ft2        |    | (6,7,AAA006)   |  6 |  7 | AAA006
 (1212,12,foo,,,,"ft2       ",) | 1212 | 12 | foo |    |    |    | ft2        |    | (12,13,AAA012) | 12 | 13 | AAA012
 (1218,18,foo,,,,"ft2       ",) | 1218 | 18 | foo |    |    |    | ft2        |    | (18,19,AAA018) | 18 | 19 | AAA018
 (1224,24,foo,,,,"ft2       ",) | 1224 | 24 | foo |    |    |    | ft2        |    | (24,25,AAA024) | 24 | 25 | AAA024
 (1230,30,foo,,,,"ft2       ",) | 1230 | 30 | foo |    |    |    | ft2        |    | (30,31,AAA030) | 30 | 31 | AAA030
 (1236,36,foo,,,,"ft2       ",) | 1236 | 36 | foo |    |    |    | ft2        |    | (36,37,AAA036) | 36 | 37 | AAA036
 (1242,42,foo,,,,"ft2       ",) | 1242 | 42 | foo |    |    |    | ft2        |    | (42,43,AAA042) | 42 | 43 | AAA042
 (1248,48,foo,,,,"ft2       ",) | 1248 | 48 | foo |    |    |    | ft2        |    | (48,49,AAA048) | 48 | 49 | AAA048
 (1254,54,foo,,,,"ft2       ",) | 1254 | 54 | foo |    |    |    | ft2        |    | (54,55,AAA054) | 54 | 55 | AAA054
 (1260,60,foo,,,,"ft2       ",) | 1260 | 60 | foo |    |    |    | ft2        |    | (60,61,AAA060) | 60 | 61 | AAA060
 (1266,66,foo,,,,"ft2       ",) | 1266 | 66 | foo |    |    |    | ft2        |    | (66,67,AAA066) | 66 | 67 | AAA066
 (1272,72,foo,,,,"ft2       ",) | 1272 | 72 | foo |    |    |    | ft2        |    | (72,73,AAA072) | 72 | 73 | AAA072
 (1278,78,foo,,,,"ft2       ",) | 1278 | 78 | foo |    |    |    | ft2        |    | (78,79,AAA078) | 78 | 79 | AAA078
 (1284,84,foo,,,,"ft2       ",) | 1284 | 84 | foo |    |    |    | ft2        |    | (84,85,AAA084) | 84 | 85 | AAA084
 (1290,90,foo,,,,"ft2       ",) | 1290 | 90 | foo |    |    |    | ft2        |    | (90,91,AAA090) | 90 | 91 | AAA090
 (1296,96,foo,,,,"ft2       ",) | 1296 | 96 | foo |    |    |    | ft2        |    | (96,97,AAA096) | 96 | 97 | AAA096
(16 rows)

EXPLAIN (verbose, costs off)
DELETE FROM ft2
  USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1)
  WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1
  RETURNING 100;                          -- can be pushed down
                                                                                            QUERY PLAN                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Delete on public.ft2
   Output: 100
   ->  Foreign Delete
         Remote SQL: DELETE FROM "S 1"."T 1" r1 USING ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) WHERE ((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)) AND (((r1."C 1" % 10) = 0))
(4 rows)

DELETE FROM ft2
  USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1)
  WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1
  RETURNING 100;
 ?column? 
----------
      100
      100
      100
      100
      100
      100
      100
      100
      100
      100
(10 rows)

DELETE FROM ft2 WHERE ft2.c1 > 1200;
-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing
-- user-defined operators/functions
ALTER SERVER loopback OPTIONS (DROP extensions);
INSERT INTO ft2 (c1,c2,c3)
  SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;            -- can't be pushed down
                                                QUERY PLAN                                                
----------------------------------------------------------------------------------------------------------
 Update on public.ft2
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
   ->  Foreign Scan on public.ft2
         Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
         Filter: (postgres_fdw_abs(ft2.c1) > 2000)
         Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
(7 rows)

UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
  c1  | c2 | c3  | c4 | c5 | c6 |     c7     | c8 
------+----+-----+----+----+----+------------+----
 2001 |  1 | bar |    |    |    | ft2        | 
 2002 |  2 | bar |    |    |    | ft2        | 
 2003 |  3 | bar |    |    |    | ft2        | 
 2004 |  4 | bar |    |    |    | ft2        | 
 2005 |  5 | bar |    |    |    | ft2        | 
 2006 |  6 | bar |    |    |    | ft2        | 
 2007 |  7 | bar |    |    |    | ft2        | 
 2008 |  8 | bar |    |    |    | ft2        | 
 2009 |  9 | bar |    |    |    | ft2        | 
 2010 |  0 | bar |    |    |    | ft2        | 
(10 rows)

EXPLAIN (verbose, costs off)
UPDATE ft2 SET c3 = 'baz'
  FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
  WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1
  RETURNING ft2.*, ft4.*, ft5.*;                                                    -- can't be pushed down
                                                                                                                                          QUERY PLAN                                                                                                                                          
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Update on public.ft2
   Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
   Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
   ->  Nested Loop
         Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
         Join Filter: (ft2.c2 === ft4.c1)
         ->  Foreign Scan on public.ft2
               Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid
               Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
         ->  Foreign Scan
               Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
               Relations: (public.ft4) INNER JOIN (public.ft5)
               Remote SQL: SELECT CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r3.c1, r3.c2, r3.c3 FROM ("S 1"."T 3" r2 INNER JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1))))
               ->  Hash Join
                     Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
                     Hash Cond: (ft4.c1 = ft5.c1)
                     ->  Foreign Scan on public.ft4
                           Output: ft4.*, ft4.c1, ft4.c2, ft4.c3
                           Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
                     ->  Hash
                           Output: ft5.*, ft5.c1, ft5.c2, ft5.c3
                           ->  Foreign Scan on public.ft5
                                 Output: ft5.*, ft5.c1, ft5.c2, ft5.c3
                                 Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4"
(24 rows)

UPDATE ft2 SET c3 = 'baz'
  FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1)
  WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1
  RETURNING ft2.*, ft4.*, ft5.*;
  c1  | c2 | c3  | c4 | c5 | c6 |     c7     | c8 | c1 | c2 |   c3   | c1 | c2 |   c3   
------+----+-----+----+----+----+------------+----+----+----+--------+----+----+--------
 2006 |  6 | baz |    |    |    | ft2        |    |  6 |  7 | AAA006 |  6 |  7 | AAA006
(1 row)

EXPLAIN (verbose, costs off)
DELETE FROM ft2
  USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1)
  WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1
  RETURNING ft2.c1, ft2.c2, ft2.c3;       -- can't be pushed down
                                                                                                                                                                     QUERY PLAN                                                                                                                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Delete on public.ft2
   Output: ft2.c1, ft2.c2, ft2.c3
   Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c2, c3
   ->  Foreign Scan
         Output: ft2.ctid, ft4.*, ft5.*
         Filter: (ft4.c1 === ft5.c1)
         Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
         Remote SQL: SELECT r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r3.c1 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 2000)))) INNER JOIN "S 1"."T 4" r3 ON (TRUE)) FOR UPDATE OF r1
         ->  Nested Loop
               Output: ft2.ctid, ft4.*, ft5.*, ft4.c1, ft5.c1
               ->  Nested Loop
                     Output: ft2.ctid, ft4.*, ft4.c1
                     Join Filter: (ft2.c2 = ft4.c1)
                     ->  Foreign Scan on public.ft2
                           Output: ft2.ctid, ft2.c2
                           Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
                     ->  Foreign Scan on public.ft4
                           Output: ft4.*, ft4.c1
                           Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
               ->  Foreign Scan on public.ft5
                     Output: ft5.*, ft5.c1
                     Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4"
(22 rows)

DELETE FROM ft2
  USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1)
  WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1
  RETURNING ft2.c1, ft2.c2, ft2.c3;
  c1  | c2 | c3  
------+----+-----
 2006 |  6 | baz
(1 row)

DELETE FROM ft2 WHERE ft2.c1 > 2000;
ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
-- Test that trigger on remote table works as expected
CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
BEGIN
    NEW.c3 = NEW.c3 || '_trig_update';
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
    ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *;
  c1  | c2  |       c3        | c4 | c5 | c6 |     c7     | c8 
------+-----+-----------------+----+----+----+------------+----
 1208 | 818 | fff_trig_update |    |    |    | ft2        | 
(1 row)

INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
  c1  | c2  |       c3        | c4 | c5 |  c6  |     c7     | c8 
------+-----+-----------------+----+----+------+------------+----
 1218 | 818 | ggg_trig_update |    |    | (--; | ft2        | 
(1 row)

UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
  c1  | c2  |           c3           |              c4              |            c5            | c6 |     c7     | c8  
------+-----+------------------------+------------------------------+--------------------------+----+------------+-----
    8 | 608 | 00008_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
   18 | 608 | 00018_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
   28 | 608 | 00028_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
   38 | 608 | 00038_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
   48 | 608 | 00048_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
   58 | 608 | 00058_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
   68 | 608 | 00068_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
   78 | 608 | 00078_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
   88 | 608 | 00088_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
   98 | 608 | 00098_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  108 | 608 | 00108_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  118 | 608 | 00118_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  128 | 608 | 00128_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  138 | 608 | 00138_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  148 | 608 | 00148_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  158 | 608 | 00158_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  168 | 608 | 00168_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  178 | 608 | 00178_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  188 | 608 | 00188_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  198 | 608 | 00198_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  208 | 608 | 00208_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  218 | 608 | 00218_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  228 | 608 | 00228_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  238 | 608 | 00238_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  248 | 608 | 00248_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  258 | 608 | 00258_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  268 | 608 | 00268_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  278 | 608 | 00278_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  288 | 608 | 00288_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  298 | 608 | 00298_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  308 | 608 | 00308_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  318 | 608 | 00318_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  328 | 608 | 00328_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  338 | 608 | 00338_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  348 | 608 | 00348_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  358 | 608 | 00358_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  368 | 608 | 00368_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  378 | 608 | 00378_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  388 | 608 | 00388_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  398 | 608 | 00398_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  408 | 608 | 00408_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  418 | 608 | 00418_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  428 | 608 | 00428_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  438 | 608 | 00438_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  448 | 608 | 00448_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  458 | 608 | 00458_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  468 | 608 | 00468_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  478 | 608 | 00478_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  488 | 608 | 00488_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  498 | 608 | 00498_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  508 | 608 | 00508_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  518 | 608 | 00518_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  528 | 608 | 00528_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  538 | 608 | 00538_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  548 | 608 | 00548_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  558 | 608 | 00558_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  568 | 608 | 00568_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  578 | 608 | 00578_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  588 | 608 | 00588_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  598 | 608 | 00598_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  608 | 608 | 00608_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  618 | 608 | 00618_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  628 | 608 | 00628_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  638 | 608 | 00638_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  648 | 608 | 00648_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  658 | 608 | 00658_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  668 | 608 | 00668_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  678 | 608 | 00678_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  688 | 608 | 00688_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  698 | 608 | 00698_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  708 | 608 | 00708_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  718 | 608 | 00718_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  728 | 608 | 00728_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  738 | 608 | 00738_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  748 | 608 | 00748_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  758 | 608 | 00758_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  768 | 608 | 00768_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  778 | 608 | 00778_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  788 | 608 | 00788_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  798 | 608 | 00798_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  808 | 608 | 00808_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  818 | 608 | 00818_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  828 | 608 | 00828_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  838 | 608 | 00838_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  848 | 608 | 00848_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  858 | 608 | 00858_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  868 | 608 | 00868_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  878 | 608 | 00878_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  888 | 608 | 00888_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  898 | 608 | 00898_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
  908 | 608 | 00908_trig_update      | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8  | 8          | foo
  918 | 608 | 00918_trig_update      | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8  | 8          | foo
  928 | 608 | 00928_trig_update      | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8  | 8          | foo
  938 | 608 | 00938_trig_update      | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8  | 8          | foo
  948 | 608 | 00948_trig_update      | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8  | 8          | foo
  958 | 608 | 00958_trig_update      | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8  | 8          | foo
  968 | 608 | 00968_trig_update      | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8  | 8          | foo
  978 | 608 | 00978_trig_update      | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8  | 8          | foo
  988 | 608 | 00988_trig_update      | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8  | 8          | foo
  998 | 608 | 00998_trig_update      | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8  | 8          | foo
 1008 | 708 | 0000800008_trig_update |                              |                          |    | ft2        | 
 1018 | 708 | 0001800018_trig_update |                              |                          |    | ft2        | 
(102 rows)

-- Test errors thrown on remote side during update
ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
INSERT INTO ft1(c1, c2) VALUES(11, 12);  -- duplicate key
ERROR:  duplicate key value violates unique constraint "t1_pkey"
DETAIL:  Key ("C 1")=(11) already exists.
CONTEXT:  remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works
INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported
ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification
INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported
ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification
INSERT INTO ft1(c1, c2) VALUES(1111, -2);  -- c2positive
ERROR:  new row for relation "T 1" violates check constraint "c2positive"
DETAIL:  Failing row contains (1111, -2, null, null, null, null, ft1       , null).
CONTEXT:  remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1;  -- c2positive
ERROR:  new row for relation "T 1" violates check constraint "c2positive"
DETAIL:  Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1         , foo).
CONTEXT:  remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1))
-- Test savepoint/rollback behavior
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   0 |   100
   1 |   100
   4 |   100
   6 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   0 |   100
   1 |   100
   4 |   100
   6 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

begin;
update ft2 set c2 = 42 where c2 = 0;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   4 |   100
   6 |   100
  42 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

savepoint s1;
update ft2 set c2 = 44 where c2 = 4;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

release savepoint s1;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

savepoint s2;
update ft2 set c2 = 46 where c2 = 6;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
  42 |   100
  44 |   100
  46 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

rollback to savepoint s2;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

release savepoint s2;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

savepoint s3;
update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side
ERROR:  new row for relation "T 1" violates check constraint "c2positive"
DETAIL:  Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 08:00:00+00, 1970-01-11 00:00:00, 0, 0         , foo).
CONTEXT:  remote SQL command: UPDATE "S 1"."T 1" SET c2 = (-2) WHERE ((c2 = 42)) AND (("C 1" = 10))
rollback to savepoint s3;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

release savepoint s3;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

-- none of the above is committed yet remotely
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   0 |   100
   1 |   100
   4 |   100
   6 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

commit;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
 c2  | count 
-----+-------
   1 |   100
   6 |   100
  42 |   100
  44 |   100
 100 |     2
 101 |     2
 104 |     2
 106 |     2
 201 |     1
 204 |     1
 303 |   100
 403 |     2
 407 |   100
(13 rows)

VACUUM ANALYZE "S 1"."T 1";
-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs
-- FIRST behavior here.
-- ORDER BY DESC NULLS LAST options
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10;
                                                                          QUERY PLAN                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 795::bigint
(3 rows)

SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795  LIMIT 10;
  c1  | c2  |         c3         |              c4              |            c5            |  c6  |     c7     | c8  
------+-----+--------------------+------------------------------+--------------------------+------+------------+-----
  960 |  42 | 00960_trig_update  | Mon Mar 02 00:00:00 1970 PST | Mon Mar 02 00:00:00 1970 | 0    | 0          | foo
  970 |  42 | 00970_trig_update  | Thu Mar 12 00:00:00 1970 PST | Thu Mar 12 00:00:00 1970 | 0    | 0          | foo
  980 |  42 | 00980_trig_update  | Sun Mar 22 00:00:00 1970 PST | Sun Mar 22 00:00:00 1970 | 0    | 0          | foo
  990 |  42 | 00990_trig_update  | Wed Apr 01 00:00:00 1970 PST | Wed Apr 01 00:00:00 1970 | 0    | 0          | foo
 1000 |  42 | 01000_trig_update  | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0    | 0          | foo
 1218 | 818 | ggg_trig_update    |                              |                          | (--; | ft2        | 
 1001 | 101 | 0000100001         |                              |                          |      | ft2        | 
 1003 | 403 | 0000300003_update3 |                              |                          |      | ft2        | 
 1004 | 104 | 0000400004         |                              |                          |      | ft2        | 
 1006 | 106 | 0000600006         |                              |                          |      | ft2        | 
(10 rows)

-- ORDER BY DESC NULLS FIRST options
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
                                                                          QUERY PLAN                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint
(3 rows)

SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
  c1  | c2  |       c3        |              c4              |            c5            | c6 |     c7     | c8  
------+-----+-----------------+------------------------------+--------------------------+----+------------+-----
 1020 | 100 | 0002000020      |                              |                          |    | ft2        | 
 1101 | 201 | aaa             |                              |                          |    | ft2        | 
 1103 | 503 | ccc_update3     |                              |                          |    | ft2        | 
 1104 | 204 | ddd             |                              |                          |    | ft2        | 
 1208 | 818 | fff_trig_update |                              |                          |    | ft2        | 
    9 | 509 | 00009_update9   | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9  | ft2        | foo
   19 | 509 | 00019_update9   | Tue Jan 20 00:00:00 1970 PST | Tue Jan 20 00:00:00 1970 | 9  | ft2        | foo
   29 | 509 | 00029_update9   | Fri Jan 30 00:00:00 1970 PST | Fri Jan 30 00:00:00 1970 | 9  | ft2        | foo
   39 | 509 | 00039_update9   | Mon Feb 09 00:00:00 1970 PST | Mon Feb 09 00:00:00 1970 | 9  | ft2        | foo
   49 | 509 | 00049_update9   | Thu Feb 19 00:00:00 1970 PST | Thu Feb 19 00:00:00 1970 | 9  | ft2        | foo
(10 rows)

-- ORDER BY ASC NULLS FIRST options
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
                                                                          QUERY PLAN                                                                          
--------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan on public.ft1
   Output: c1, c2, c3, c4, c5, c6, c7, c8
   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 ASC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint
(3 rows)

SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
  c1  | c2  |        c3         |              c4              |            c5            |  c6  |     c7     | c8  
------+-----+-------------------+------------------------------+--------------------------+------+------------+-----
 1020 | 100 | 0002000020        |                              |                          |      | ft2        | 
 1101 | 201 | aaa               |                              |                          |      | ft2        | 
 1103 | 503 | ccc_update3       |                              |                          |      | ft2        | 
 1104 | 204 | ddd               |                              |                          |      | ft2        | 
 1208 | 818 | fff_trig_update   |                              |                          |      | ft2        | 
 1218 | 818 | ggg_trig_update   |                              |                          | (--; | ft2        | 
   10 |  42 | 00010_trig_update | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0    | 0          | foo
   20 |  42 | 00020_trig_update | Wed Jan 21 00:00:00 1970 PST | Wed Jan 21 00:00:00 1970 | 0    | 0          | foo
   30 |  42 | 00030_trig_update | Sat Jan 31 00:00:00 1970 PST | Sat Jan 31 00:00:00 1970 | 0    | 0          | foo
   40 |  42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0    | 0          | foo
(10 rows)

-- ===================================================================
-- test check constraints
-- ===================================================================
-- Consistent check constraints provide consistent results
ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0;
                           QUERY PLAN                            
-----------------------------------------------------------------
 Foreign Scan
   Output: (count(*))
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 < 0))
(4 rows)

SELECT count(*) FROM ft1 WHERE c2 < 0;
 count 
-------
     0
(1 row)

SET constraint_exclusion = 'on';
EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0;
           QUERY PLAN           
--------------------------------
 Aggregate
   Output: count(*)
   ->  Result
         One-Time Filter: false
(4 rows)

SELECT count(*) FROM ft1 WHERE c2 < 0;
 count 
-------
     0
(1 row)

RESET constraint_exclusion;
-- check constraint is enforced on the remote side, not locally
INSERT INTO ft1(c1, c2) VALUES(1111, -2);  -- c2positive
ERROR:  new row for relation "T 1" violates check constraint "c2positive"
DETAIL:  Failing row contains (1111, -2, null, null, null, null, ft1       , null).
CONTEXT:  remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1;  -- c2positive
ERROR:  new row for relation "T 1" violates check constraint "c2positive"
DETAIL:  Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1         , foo).
CONTEXT:  remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1))
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
-- But inconsistent check constraints provide inconsistent results
ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0;
                            QUERY PLAN                            
------------------------------------------------------------------
 Foreign Scan
   Output: (count(*))
   Relations: Aggregate on (public.ft1)
   Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0))
(4 rows)

SELECT count(*) FROM ft1 WHERE c2 >= 0;
 count 
-------
   821
(1 row)

SET constraint_exclusion = 'on';
EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0;
           QUERY PLAN           
--------------------------------
 Aggregate
   Output: count(*)
   ->  Result
         One-Time Filter: false
(4 rows)

SELECT count(*) FROM ft1 WHERE c2 >= 0;
 count 
-------
     0
(1 row)

RESET constraint_exclusion;
-- local check constraint is not actually enforced
INSERT INTO ft1(c1, c2) VALUES(1111, 2);
UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1;
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative;
-- ===================================================================
-- test WITH CHECK OPTION constraints
-- ===================================================================
CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql;
CREATE TABLE base_tbl (a int, b int);
ALTER TABLE base_tbl SET (autovacuum_enabled = 'false');
CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc();
CREATE FOREIGN TABLE foreign_tbl (a int, b int)
  SERVER loopback OPTIONS (table_name 'base_tbl');
CREATE VIEW rw_view AS SELECT * FROM foreign_tbl
  WHERE a < b WITH CHECK OPTION;
\d+ rw_view
                           View "public.rw_view"
 Column |  Type   | Collation | Nullable | Default | Storage | Description 
--------+---------+-----------+----------+---------+---------+-------------
 a      | integer |           |          |         | plain   | 
 b      | integer |           |          |         | plain   | 
View definition:
 SELECT foreign_tbl.a,
    foreign_tbl.b
   FROM foreign_tbl
  WHERE foreign_tbl.a < foreign_tbl.b;
Options: check_option=cascaded

EXPLAIN (VERBOSE, COSTS OFF)
INSERT INTO rw_view VALUES (0, 5);
                                   QUERY PLAN                                   
--------------------------------------------------------------------------------
 Insert on public.foreign_tbl
   Remote SQL: INSERT INTO public.base_tbl(a, b) VALUES ($1, $2) RETURNING a, b
   ->  Result
         Output: 0, 5
(4 rows)

INSERT INTO rw_view VALUES (0, 5); -- should fail
ERROR:  new row violates check option for view "rw_view"
DETAIL:  Failing row contains (10, 5).
EXPLAIN (VERBOSE, COSTS OFF)
INSERT INTO rw_view VALUES (0, 15);
                                   QUERY PLAN                                   
--------------------------------------------------------------------------------
 Insert on public.foreign_tbl
   Remote SQL: INSERT INTO public.base_tbl(a, b) VALUES ($1, $2) RETURNING a, b
   ->  Result
         Output: 0, 15
(4 rows)

INSERT INTO rw_view VALUES (0, 15); -- ok
SELECT * FROM foreign_tbl;
 a  | b  
----+----
 10 | 15
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Update on public.foreign_tbl
   Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
   ->  Foreign Scan on public.foreign_tbl
         Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
         Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)

UPDATE rw_view SET b = b + 5; -- should fail
ERROR:  new row violates check option for view "rw_view"
DETAIL:  Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Update on public.foreign_tbl
   Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
   ->  Foreign Scan on public.foreign_tbl
         Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
         Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)

UPDATE rw_view SET b = b + 15; -- ok
SELECT * FROM foreign_tbl;
 a  | b  
----+----
 20 | 30
(1 row)

DROP FOREIGN TABLE foreign_tbl CASCADE;
NOTICE:  drop cascades to view rw_view
DROP TRIGGER row_before_insupd_trigger ON base_tbl;
DROP TABLE base_tbl;
-- test WCO for partitions
CREATE TABLE child_tbl (a int, b int);
ALTER TABLE child_tbl SET (autovacuum_enabled = 'false');
CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON child_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc();
CREATE FOREIGN TABLE foreign_tbl (a int, b int)
  SERVER loopback OPTIONS (table_name 'child_tbl');
CREATE TABLE parent_tbl (a int, b int) PARTITION BY RANGE(a);
ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100);
CREATE VIEW rw_view AS SELECT * FROM parent_tbl
  WHERE a < b WITH CHECK OPTION;
\d+ rw_view
                           View "public.rw_view"
 Column |  Type   | Collation | Nullable | Default | Storage | Description 
--------+---------+-----------+----------+---------+---------+-------------
 a      | integer |           |          |         | plain   | 
 b      | integer |           |          |         | plain   | 
View definition:
 SELECT parent_tbl.a,
    parent_tbl.b
   FROM parent_tbl
  WHERE parent_tbl.a < parent_tbl.b;
Options: check_option=cascaded

EXPLAIN (VERBOSE, COSTS OFF)
INSERT INTO rw_view VALUES (0, 5);
         QUERY PLAN          
-----------------------------
 Insert on public.parent_tbl
   ->  Result
         Output: 0, 5
(3 rows)

INSERT INTO rw_view VALUES (0, 5); -- should fail
ERROR:  new row violates check option for view "rw_view"
DETAIL:  Failing row contains (10, 5).
EXPLAIN (VERBOSE, COSTS OFF)
INSERT INTO rw_view VALUES (0, 15);
         QUERY PLAN          
-----------------------------
 Insert on public.parent_tbl
   ->  Result
         Output: 0, 15
(3 rows)

INSERT INTO rw_view VALUES (0, 15); -- ok
SELECT * FROM foreign_tbl;
 a  | b  
----+----
 10 | 15
(1 row)

EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
 Update on public.parent_tbl
   Foreign Update on public.foreign_tbl
     Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
   ->  Foreign Scan on public.foreign_tbl
         Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
         Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)

UPDATE rw_view SET b = b + 5; -- should fail
ERROR:  new row violates check option for view "rw_view"
DETAIL:  Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
 Update on public.parent_tbl
   Foreign Update on public.foreign_tbl
     Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
   ->  Foreign Scan on public.foreign_tbl
         Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
         Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)

UPDATE rw_view SET b = b + 15; -- ok
SELECT * FROM foreign_tbl;
 a  | b  
----+----
 20 | 30
(1 row)

DROP FOREIGN TABLE foreign_tbl CASCADE;
DROP TRIGGER row_before_insupd_trigger ON child_tbl;
DROP TABLE parent_tbl CASCADE;
NOTICE:  drop cascades to view rw_view
DROP FUNCTION row_before_insupd_trigfunc;
-- ===================================================================
-- test serial columns (ie, sequence-based defaults)
-- ===================================================================
create table loc1 (f1 serial, f2 text);
alter table loc1 set (autovacuum_enabled = 'false');
create foreign table rem1 (f1 serial, f2 text)
  server loopback options(table_name 'loc1');
select pg_catalog.setval('rem1_f1_seq', 10, false);
 setval 
--------
     10
(1 row)

insert into loc1(f2) values('hi');
insert into rem1(f2) values('hi remote');
insert into loc1(f2) values('bye');
insert into rem1(f2) values('bye remote');
select * from loc1;
 f1 |     f2     
----+------------
  1 | hi
 10 | hi remote
  2 | bye
 11 | bye remote
(4 rows)

select * from rem1;
 f1 |     f2     
----+------------
  1 | hi
 10 | hi remote
  2 | bye
 11 | bye remote
(4 rows)

-- ===================================================================
-- test generated columns
-- ===================================================================
create table gloc1 (a int, b int);
alter table gloc1 set (autovacuum_enabled = 'false');
create foreign table grem1 (
  a int,
  b int generated always as (a * 2) stored)
  server loopback options(table_name 'gloc1');
insert into grem1 (a) values (1), (2);
update grem1 set a = 22 where a = 2;
select * from gloc1;
 a  | b  
----+----
  1 |  2
 22 | 44
(2 rows)

select * from grem1;
 a  | b  
----+----
  1 |  2
 22 | 44
(2 rows)

-- ===================================================================
-- test local triggers
-- ===================================================================
-- Trigger functions "borrowed" from triggers regress test.
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
	RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %',
		TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
	RETURN NULL;
END;$$;
CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1
	FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1
	FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
CREATE OR REPLACE FUNCTION trigger_data()  RETURNS trigger
LANGUAGE plpgsql AS $$

declare
	oldnew text[];
	relid text;
    argstr text;
begin

	relid := TG_relid::regclass;
	argstr := '';
	for i in 0 .. TG_nargs - 1 loop
		if i > 0 then
			argstr := argstr || ', ';
		end if;
		argstr := argstr || TG_argv[i];
	end loop;

    RAISE NOTICE '%(%) % % % ON %',
		tg_name, argstr, TG_when, TG_level, TG_OP, relid;
    oldnew := '{}'::text[];
	if TG_OP != 'INSERT' then
		oldnew := array_append(oldnew, format('OLD: %s', OLD));
	end if;

	if TG_OP != 'DELETE' then
		oldnew := array_append(oldnew, format('NEW: %s', NEW));
	end if;

    RAISE NOTICE '%', array_to_string(oldnew, ',');

	if TG_OP = 'DELETE' then
		return OLD;
	else
		return NEW;
	end if;
end;
$$;
-- Test basic functionality
CREATE TRIGGER trig_row_before
BEFORE INSERT OR UPDATE OR DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER trig_row_after
AFTER INSERT OR UPDATE OR DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
delete from rem1;
NOTICE:  trigger_func(<NULL>) called: action = DELETE, when = BEFORE, level = STATEMENT
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
NOTICE:  OLD: (1,hi)
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
NOTICE:  OLD: (10,"hi remote")
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
NOTICE:  OLD: (2,bye)
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1
NOTICE:  OLD: (11,"bye remote")
NOTICE:  trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
NOTICE:  OLD: (1,hi)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
NOTICE:  OLD: (10,"hi remote")
NOTICE:  trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
NOTICE:  OLD: (2,bye)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1
NOTICE:  OLD: (11,"bye remote")
NOTICE:  trigger_func(<NULL>) called: action = DELETE, when = AFTER, level = STATEMENT
insert into rem1 values(1,'insert');
NOTICE:  trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1
NOTICE:  NEW: (1,insert)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
NOTICE:  NEW: (1,insert)
NOTICE:  trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
update rem1 set f2  = 'update' where f1 = 1;
NOTICE:  trigger_func(<NULL>) called: action = UPDATE, when = BEFORE, level = STATEMENT
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1
NOTICE:  OLD: (1,insert),NEW: (1,update)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1
NOTICE:  OLD: (1,insert),NEW: (1,update)
NOTICE:  trigger_func(<NULL>) called: action = UPDATE, when = AFTER, level = STATEMENT
update rem1 set f2 = f2 || f2;
NOTICE:  trigger_func(<NULL>) called: action = UPDATE, when = BEFORE, level = STATEMENT
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1
NOTICE:  OLD: (1,update),NEW: (1,updateupdate)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1
NOTICE:  OLD: (1,update),NEW: (1,updateupdate)
NOTICE:  trigger_func(<NULL>) called: action = UPDATE, when = AFTER, level = STATEMENT
-- cleanup
DROP TRIGGER trig_row_before ON rem1;
DROP TRIGGER trig_row_after ON rem1;
DROP TRIGGER trig_stmt_before ON rem1;
DROP TRIGGER trig_stmt_after ON rem1;
DELETE from rem1;
-- Test WHEN conditions
CREATE TRIGGER trig_row_before_insupd
BEFORE INSERT OR UPDATE ON rem1
FOR EACH ROW
WHEN (NEW.f2 like '%update%')
EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER trig_row_after_insupd
AFTER INSERT OR UPDATE ON rem1
FOR EACH ROW
WHEN (NEW.f2 like '%update%')
EXECUTE PROCEDURE trigger_data(23,'skidoo');
-- Insert or update not matching: nothing happens
INSERT INTO rem1 values(1, 'insert');
UPDATE rem1 set f2 = 'test';
-- Insert or update matching: triggers are fired
INSERT INTO rem1 values(2, 'update');
NOTICE:  trig_row_before_insupd(23, skidoo) BEFORE ROW INSERT ON rem1
NOTICE:  NEW: (2,update)
NOTICE:  trig_row_after_insupd(23, skidoo) AFTER ROW INSERT ON rem1
NOTICE:  NEW: (2,update)
UPDATE rem1 set f2 = 'update update' where f1 = '2';
NOTICE:  trig_row_before_insupd(23, skidoo) BEFORE ROW UPDATE ON rem1
NOTICE:  OLD: (2,update),NEW: (2,"update update")
NOTICE:  trig_row_after_insupd(23, skidoo) AFTER ROW UPDATE ON rem1
NOTICE:  OLD: (2,update),NEW: (2,"update update")
CREATE TRIGGER trig_row_before_delete
BEFORE DELETE ON rem1
FOR EACH ROW
WHEN (OLD.f2 like '%update%')
EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER trig_row_after_delete
AFTER DELETE ON rem1
FOR EACH ROW
WHEN (OLD.f2 like '%update%')
EXECUTE PROCEDURE trigger_data(23,'skidoo');
-- Trigger is fired for f1=2, not for f1=1
DELETE FROM rem1;
NOTICE:  trig_row_before_delete(23, skidoo) BEFORE ROW DELETE ON rem1
NOTICE:  OLD: (2,"update update")
NOTICE:  trig_row_after_delete(23, skidoo) AFTER ROW DELETE ON rem1
NOTICE:  OLD: (2,"update update")
-- cleanup
DROP TRIGGER trig_row_before_insupd ON rem1;
DROP TRIGGER trig_row_after_insupd ON rem1;
DROP TRIGGER trig_row_before_delete ON rem1;
DROP TRIGGER trig_row_after_delete ON rem1;
-- Test various RETURN statements in BEFORE triggers.
CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$
  BEGIN
    NEW.f2 := NEW.f2 || ' triggered !';
    RETURN NEW;
  END
$$ language plpgsql;
CREATE TRIGGER trig_row_before_insupd
BEFORE INSERT OR UPDATE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
-- The new values should have 'triggered' appended
INSERT INTO rem1 values(1, 'insert');
SELECT * from loc1;
 f1 |         f2         
----+--------------------
  1 | insert triggered !
(1 row)

INSERT INTO rem1 values(2, 'insert') RETURNING f2;
         f2         
--------------------
 insert triggered !
(1 row)

SELECT * from loc1;
 f1 |         f2         
----+--------------------
  1 | insert triggered !
  2 | insert triggered !
(2 rows)

UPDATE rem1 set f2 = '';
SELECT * from loc1;
 f1 |      f2      
----+--------------
  1 |  triggered !
  2 |  triggered !
(2 rows)

UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
         f2         
--------------------
 skidoo triggered !
 skidoo triggered !
(2 rows)

SELECT * from loc1;
 f1 |         f2         
----+--------------------
  1 | skidoo triggered !
  2 | skidoo triggered !
(2 rows)

EXPLAIN (verbose, costs off)
UPDATE rem1 set f1 = 10;          -- all columns should be transmitted
                              QUERY PLAN                               
-----------------------------------------------------------------------
 Update on public.rem1
   Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
   ->  Foreign Scan on public.rem1
         Output: 10, f2, ctid, rem1.*
         Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)

UPDATE rem1 set f1 = 10;
SELECT * from loc1;
 f1 |               f2               
----+--------------------------------
 10 | skidoo triggered ! triggered !
 10 | skidoo triggered ! triggered !
(2 rows)

DELETE FROM rem1;
-- Add a second trigger, to check that the changes are propagated correctly
-- from trigger to trigger
CREATE TRIGGER trig_row_before_insupd2
BEFORE INSERT OR UPDATE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
INSERT INTO rem1 values(1, 'insert');
SELECT * from loc1;
 f1 |               f2               
----+--------------------------------
  1 | insert triggered ! triggered !
(1 row)

INSERT INTO rem1 values(2, 'insert') RETURNING f2;
               f2               
--------------------------------
 insert triggered ! triggered !
(1 row)

SELECT * from loc1;
 f1 |               f2               
----+--------------------------------
  1 | insert triggered ! triggered !
  2 | insert triggered ! triggered !
(2 rows)

UPDATE rem1 set f2 = '';
SELECT * from loc1;
 f1 |            f2            
----+--------------------------
  1 |  triggered ! triggered !
  2 |  triggered ! triggered !
(2 rows)

UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
               f2               
--------------------------------
 skidoo triggered ! triggered !
 skidoo triggered ! triggered !
(2 rows)

SELECT * from loc1;
 f1 |               f2               
----+--------------------------------
  1 | skidoo triggered ! triggered !
  2 | skidoo triggered ! triggered !
(2 rows)

DROP TRIGGER trig_row_before_insupd ON rem1;
DROP TRIGGER trig_row_before_insupd2 ON rem1;
DELETE from rem1;
INSERT INTO rem1 VALUES (1, 'test');
-- Test with a trigger returning NULL
CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$
  BEGIN
    RETURN NULL;
  END
$$ language plpgsql;
CREATE TRIGGER trig_null
BEFORE INSERT OR UPDATE OR DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trig_null();
-- Nothing should have changed.
INSERT INTO rem1 VALUES (2, 'test2');
SELECT * from loc1;
 f1 |  f2  
----+------
  1 | test
(1 row)

UPDATE rem1 SET f2 = 'test2';
SELECT * from loc1;
 f1 |  f2  
----+------
  1 | test
(1 row)

DELETE from rem1;
SELECT * from loc1;
 f1 |  f2  
----+------
  1 | test
(1 row)

DROP TRIGGER trig_null ON rem1;
DELETE from rem1;
-- Test a combination of local and remote triggers
CREATE TRIGGER trig_row_before
BEFORE INSERT OR UPDATE OR DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER trig_row_after
AFTER INSERT OR UPDATE OR DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1
FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
INSERT INTO rem1(f2) VALUES ('test');
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1
NOTICE:  NEW: (12,test)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
NOTICE:  NEW: (12,"test triggered !")
UPDATE rem1 SET f2 = 'testo';
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1
NOTICE:  OLD: (12,"test triggered !"),NEW: (12,testo)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1
NOTICE:  OLD: (12,"test triggered !"),NEW: (12,"testo triggered !")
-- Test returning a system attribute
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1
NOTICE:  NEW: (13,test)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1
NOTICE:  NEW: (13,"test triggered !")
  ctid  
--------
 (0,29)
(1 row)

-- cleanup
DROP TRIGGER trig_row_before ON rem1;
DROP TRIGGER trig_row_after ON rem1;
DROP TRIGGER trig_local_before ON loc1;
-- Test direct foreign table modification functionality
-- Test with statement-level triggers
CREATE TRIGGER trig_stmt_before
	BEFORE DELETE OR INSERT OR UPDATE ON rem1
	FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can be pushed down
                        QUERY PLAN                        
----------------------------------------------------------
 Update on public.rem1
   ->  Foreign Update on public.rem1
         Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can be pushed down
                 QUERY PLAN                  
---------------------------------------------
 Delete on public.rem1
   ->  Foreign Delete on public.rem1
         Remote SQL: DELETE FROM public.loc1
(3 rows)

DROP TRIGGER trig_stmt_before ON rem1;
CREATE TRIGGER trig_stmt_after
	AFTER DELETE OR INSERT OR UPDATE ON rem1
	FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can be pushed down
                        QUERY PLAN                        
----------------------------------------------------------
 Update on public.rem1
   ->  Foreign Update on public.rem1
         Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can be pushed down
                 QUERY PLAN                  
---------------------------------------------
 Delete on public.rem1
   ->  Foreign Delete on public.rem1
         Remote SQL: DELETE FROM public.loc1
(3 rows)

DROP TRIGGER trig_stmt_after ON rem1;
-- Test with row-level ON INSERT triggers
CREATE TRIGGER trig_row_before_insert
BEFORE INSERT ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can be pushed down
                        QUERY PLAN                        
----------------------------------------------------------
 Update on public.rem1
   ->  Foreign Update on public.rem1
         Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can be pushed down
                 QUERY PLAN                  
---------------------------------------------
 Delete on public.rem1
   ->  Foreign Delete on public.rem1
         Remote SQL: DELETE FROM public.loc1
(3 rows)

DROP TRIGGER trig_row_before_insert ON rem1;
CREATE TRIGGER trig_row_after_insert
AFTER INSERT ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can be pushed down
                        QUERY PLAN                        
----------------------------------------------------------
 Update on public.rem1
   ->  Foreign Update on public.rem1
         Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can be pushed down
                 QUERY PLAN                  
---------------------------------------------
 Delete on public.rem1
   ->  Foreign Delete on public.rem1
         Remote SQL: DELETE FROM public.loc1
(3 rows)

DROP TRIGGER trig_row_after_insert ON rem1;
-- Test with row-level ON UPDATE triggers
CREATE TRIGGER trig_row_before_update
BEFORE UPDATE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can't be pushed down
                              QUERY PLAN                               
-----------------------------------------------------------------------
 Update on public.rem1
   Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1
   ->  Foreign Scan on public.rem1
         Output: f1, ''::text, ctid, rem1.*
         Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can be pushed down
                 QUERY PLAN                  
---------------------------------------------
 Delete on public.rem1
   ->  Foreign Delete on public.rem1
         Remote SQL: DELETE FROM public.loc1
(3 rows)

DROP TRIGGER trig_row_before_update ON rem1;
CREATE TRIGGER trig_row_after_update
AFTER UPDATE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can't be pushed down
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Update on public.rem1
   Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2
   ->  Foreign Scan on public.rem1
         Output: f1, ''::text, ctid, rem1.*
         Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can be pushed down
                 QUERY PLAN                  
---------------------------------------------
 Delete on public.rem1
   ->  Foreign Delete on public.rem1
         Remote SQL: DELETE FROM public.loc1
(3 rows)

DROP TRIGGER trig_row_after_update ON rem1;
-- Test with row-level ON DELETE triggers
CREATE TRIGGER trig_row_before_delete
BEFORE DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can be pushed down
                        QUERY PLAN                        
----------------------------------------------------------
 Update on public.rem1
   ->  Foreign Update on public.rem1
         Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can't be pushed down
                             QUERY PLAN                              
---------------------------------------------------------------------
 Delete on public.rem1
   Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1
   ->  Foreign Scan on public.rem1
         Output: ctid, rem1.*
         Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)

DROP TRIGGER trig_row_before_delete ON rem1;
CREATE TRIGGER trig_row_after_delete
AFTER DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = '';          -- can be pushed down
                        QUERY PLAN                        
----------------------------------------------------------
 Update on public.rem1
   ->  Foreign Update on public.rem1
         Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)

EXPLAIN (verbose, costs off)
DELETE FROM rem1;                 -- can't be pushed down
                               QUERY PLAN                               
------------------------------------------------------------------------
 Delete on public.rem1
   Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 RETURNING f1, f2
   ->  Foreign Scan on public.rem1
         Output: ctid, rem1.*
         Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)

DROP TRIGGER trig_row_after_delete ON rem1;
-- ===================================================================
-- test inheritance features
-- ===================================================================
CREATE TABLE a (aa TEXT);
CREATE TABLE loct (aa TEXT, bb TEXT);
ALTER TABLE a SET (autovacuum_enabled = 'false');
ALTER TABLE loct SET (autovacuum_enabled = 'false');
CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
  SERVER loopback OPTIONS (table_name 'loct');
INSERT INTO a(aa) VALUES('aaa');
INSERT INTO a(aa) VALUES('aaaa');
INSERT INTO a(aa) VALUES('aaaaa');
INSERT INTO b(aa) VALUES('bbb');
INSERT INTO b(aa) VALUES('bbbb');
INSERT INTO b(aa) VALUES('bbbbb');
SELECT tableoid::regclass, * FROM a;
 tableoid |  aa   
----------+-------
 a        | aaa
 a        | aaaa
 a        | aaaaa
 b        | bbb
 b        | bbbb
 b        | bbbbb
(6 rows)

SELECT tableoid::regclass, * FROM b;
 tableoid |  aa   | bb 
----------+-------+----
 b        | bbb   | 
 b        | bbbb  | 
 b        | bbbbb | 
(3 rows)

SELECT tableoid::regclass, * FROM ONLY a;
 tableoid |  aa   
----------+-------
 a        | aaa
 a        | aaaa
 a        | aaaaa
(3 rows)

UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%';
SELECT tableoid::regclass, * FROM a;
 tableoid |   aa   
----------+--------
 a        | aaa
 a        | zzzzzz
 a        | zzzzzz
 b        | bbb
 b        | bbbb
 b        | bbbbb
(6 rows)

SELECT tableoid::regclass, * FROM b;
 tableoid |  aa   | bb 
----------+-------+----
 b        | bbb   | 
 b        | bbbb  | 
 b        | bbbbb | 
(3 rows)

SELECT tableoid::regclass, * FROM ONLY a;
 tableoid |   aa   
----------+--------
 a        | aaa
 a        | zzzzzz
 a        | zzzzzz
(3 rows)

UPDATE b SET aa = 'new';
SELECT tableoid::regclass, * FROM a;
 tableoid |   aa   
----------+--------
 a        | aaa
 a        | zzzzzz
 a        | zzzzzz
 b        | new
 b        | new
 b        | new
(6 rows)

SELECT tableoid::regclass, * FROM b;
 tableoid | aa  | bb 
----------+-----+----
 b        | new | 
 b        | new | 
 b        | new | 
(3 rows)

SELECT tableoid::regclass, * FROM ONLY a;
 tableoid |   aa   
----------+--------
 a        | aaa
 a        | zzzzzz
 a        | zzzzzz
(3 rows)

UPDATE a SET aa = 'newtoo';
SELECT tableoid::regclass, * FROM a;
 tableoid |   aa   
----------+--------
 a        | newtoo
 a        | newtoo
 a        | newtoo
 b        | newtoo
 b        | newtoo
 b        | newtoo
(6 rows)

SELECT tableoid::regclass, * FROM b;
 tableoid |   aa   | bb 
----------+--------+----
 b        | newtoo | 
 b        | newtoo | 
 b        | newtoo | 
(3 rows)

SELECT tableoid::regclass, * FROM ONLY a;
 tableoid |   aa   
----------+--------
 a        | newtoo
 a        | newtoo
 a        | newtoo
(3 rows)

DELETE FROM a;
SELECT tableoid::regclass, * FROM a;
 tableoid | aa 
----------+----
(0 rows)

SELECT tableoid::regclass, * FROM b;
 tableoid | aa | bb 
----------+----+----
(0 rows)

SELECT tableoid::regclass, * FROM ONLY a;
 tableoid | aa 
----------+----
(0 rows)

DROP TABLE a CASCADE;
NOTICE:  drop cascades to foreign table b
DROP TABLE loct;
-- Check SELECT FOR UPDATE/SHARE with an inherited source table
create table loct1 (f1 int, f2 int, f3 int);
create table loct2 (f1 int, f2 int, f3 int);
alter table loct1 set (autovacuum_enabled = 'false');
alter table loct2 set (autovacuum_enabled = 'false');
create table foo (f1 int, f2 int);
create foreign table foo2 (f3 int) inherits (foo)
  server loopback options (table_name 'loct1');
create table bar (f1 int, f2 int);
create foreign table bar2 (f3 int) inherits (bar)
  server loopback options (table_name 'loct2');
alter table foo set (autovacuum_enabled = 'false');
alter table bar set (autovacuum_enabled = 'false');
insert into foo values(1,1);
insert into foo values(3,3);
insert into foo2 values(2,2,2);
insert into foo2 values(4,4,4);
insert into bar values(1,11);
insert into bar values(2,22);
insert into bar values(6,66);
insert into bar2 values(3,33,33);
insert into bar2 values(4,44,44);
insert into bar2 values(7,77,77);
explain (verbose, costs off)
select * from bar where f1 in (select f1 from foo) for update;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 LockRows
   Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid
   ->  Hash Join
         Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid
         Inner Unique: true
         Hash Cond: (bar.f1 = foo.f1)
         ->  Append
               ->  Seq Scan on public.bar
                     Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid
               ->  Foreign Scan on public.bar2
                     Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid
                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
         ->  Hash
               Output: foo.ctid, foo.f1, foo.*, foo.tableoid
               ->  HashAggregate
                     Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                     Group Key: foo.f1
                     ->  Append
                           ->  Seq Scan on public.foo
                                 Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                           ->  Foreign Scan on public.foo2
                                 Output: foo2.ctid, foo2.f1, foo2.*, foo2.tableoid
                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(23 rows)

select * from bar where f1 in (select f1 from foo) for update;
 f1 | f2 
----+----
  1 | 11
  2 | 22
  3 | 33
  4 | 44
(4 rows)

explain (verbose, costs off)
select * from bar where f1 in (select f1 from foo) for share;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 LockRows
   Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid
   ->  Hash Join
         Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid
         Inner Unique: true
         Hash Cond: (bar.f1 = foo.f1)
         ->  Append
               ->  Seq Scan on public.bar
                     Output: bar.f1, bar.f2, bar.ctid, bar.*, bar.tableoid
               ->  Foreign Scan on public.bar2
                     Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid
                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE
         ->  Hash
               Output: foo.ctid, foo.f1, foo.*, foo.tableoid
               ->  HashAggregate
                     Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                     Group Key: foo.f1
                     ->  Append
                           ->  Seq Scan on public.foo
                                 Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                           ->  Foreign Scan on public.foo2
                                 Output: foo2.ctid, foo2.f1, foo2.*, foo2.tableoid
                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(23 rows)

select * from bar where f1 in (select f1 from foo) for share;
 f1 | f2 
----+----
  1 | 11
  2 | 22
  3 | 33
  4 | 44
(4 rows)

-- Check UPDATE with inherited target and an inherited source table
explain (verbose, costs off)
update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Update on public.bar
   Update on public.bar
   Foreign Update on public.bar2
     Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
   ->  Hash Join
         Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
         Inner Unique: true
         Hash Cond: (bar.f1 = foo.f1)
         ->  Seq Scan on public.bar
               Output: bar.f1, bar.f2, bar.ctid
         ->  Hash
               Output: foo.ctid, foo.f1, foo.*, foo.tableoid
               ->  HashAggregate
                     Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                     Group Key: foo.f1
                     ->  Append
                           ->  Seq Scan on public.foo
                                 Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                           ->  Foreign Scan on public.foo2
                                 Output: foo2.ctid, foo2.f1, foo2.*, foo2.tableoid
                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
   ->  Hash Join
         Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid
         Inner Unique: true
         Hash Cond: (bar2.f1 = foo.f1)
         ->  Foreign Scan on public.bar2
               Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
               Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
         ->  Hash
               Output: foo.ctid, foo.f1, foo.*, foo.tableoid
               ->  HashAggregate
                     Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                     Group Key: foo.f1
                     ->  Append
                           ->  Seq Scan on public.foo
                                 Output: foo.ctid, foo.f1, foo.*, foo.tableoid
                           ->  Foreign Scan on public.foo2
                                 Output: foo2.ctid, foo2.f1, foo2.*, foo2.tableoid
                                 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(39 rows)

update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
select tableoid::regclass, * from bar order by 1,2;
 tableoid | f1 | f2  
----------+----+-----
 bar      |  1 | 111
 bar      |  2 | 122
 bar      |  6 |  66
 bar2     |  3 | 133
 bar2     |  4 | 144
 bar2     |  7 |  77
(6 rows)

-- Check UPDATE with inherited target and an appendrel subquery
explain (verbose, costs off)
update bar set f2 = f2 + 100
from
  ( select f1 from foo union all select f1+3 from foo ) ss
where bar.f1 = ss.f1;
                                      QUERY PLAN                                      
--------------------------------------------------------------------------------------
 Update on public.bar
   Update on public.bar
   Foreign Update on public.bar2
     Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
   ->  Hash Join
         Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
         Hash Cond: (foo.f1 = bar.f1)
         ->  Append
               ->  Seq Scan on public.foo
                     Output: ROW(foo.f1), foo.f1
               ->  Foreign Scan on public.foo2
                     Output: ROW(foo2.f1), foo2.f1
                     Remote SQL: SELECT f1 FROM public.loct1
               ->  Seq Scan on public.foo foo_1
                     Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
               ->  Foreign Scan on public.foo2 foo2_1
                     Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
                     Remote SQL: SELECT f1 FROM public.loct1
         ->  Hash
               Output: bar.f1, bar.f2, bar.ctid
               ->  Seq Scan on public.bar
                     Output: bar.f1, bar.f2, bar.ctid
   ->  Merge Join
         Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
         Merge Cond: (bar2.f1 = foo.f1)
         ->  Sort
               Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
               Sort Key: bar2.f1
               ->  Foreign Scan on public.bar2
                     Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
                     Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
         ->  Sort
               Output: (ROW(foo.f1)), foo.f1
               Sort Key: foo.f1
               ->  Append
                     ->  Seq Scan on public.foo
                           Output: ROW(foo.f1), foo.f1
                     ->  Foreign Scan on public.foo2
                           Output: ROW(foo2.f1), foo2.f1
                           Remote SQL: SELECT f1 FROM public.loct1
                     ->  Seq Scan on public.foo foo_1
                           Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3)
                     ->  Foreign Scan on public.foo2 foo2_1
                           Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3)
                           Remote SQL: SELECT f1 FROM public.loct1
(45 rows)

update bar set f2 = f2 + 100
from
  ( select f1 from foo union all select f1+3 from foo ) ss
where bar.f1 = ss.f1;
select tableoid::regclass, * from bar order by 1,2;
 tableoid | f1 | f2  
----------+----+-----
 bar      |  1 | 211
 bar      |  2 | 222
 bar      |  6 | 166
 bar2     |  3 | 233
 bar2     |  4 | 244
 bar2     |  7 | 177
(6 rows)

-- Test forcing the remote server to produce sorted data for a merge join,
-- but the foreign table is an inheritance child.
truncate table loct1;
truncate table only foo;
\set num_rows_foo 2000
insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2);
insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2);
SET enable_hashjoin to false;
SET enable_nestloop to false;
alter foreign table foo2 options (use_remote_estimate 'true');
create index i_loct1_f1 on loct1(f1);
create index i_foo_f1 on foo(f1);
analyze foo;
analyze loct1;
-- inner join; expressions in the clauses appear in the equivalence class list
explain (verbose, costs off)
	select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
                                            QUERY PLAN                                            
--------------------------------------------------------------------------------------------------
 Limit
   Output: foo.f1, loct1.f1, foo.f2
   ->  Sort
         Output: foo.f1, loct1.f1, foo.f2
         Sort Key: foo.f2
         ->  Merge Join
               Output: foo.f1, loct1.f1, foo.f2
               Merge Cond: (foo.f1 = loct1.f1)
               ->  Merge Append
                     Sort Key: foo.f1
                     ->  Index Scan using i_foo_f1 on public.foo
                           Output: foo.f1, foo.f2
                     ->  Foreign Scan on public.foo2
                           Output: foo2.f1, foo2.f2
                           Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST
               ->  Index Only Scan using i_loct1_f1 on public.loct1
                     Output: loct1.f1
(17 rows)

select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
 f1 | f1 
----+----
 20 | 20
 22 | 22
 24 | 24
 26 | 26
 28 | 28
 30 | 30
 32 | 32
 34 | 34
 36 | 36
 38 | 38
(10 rows)

-- outer join; expressions in the clauses do not appear in equivalence class
-- list but no output change as compared to the previous query
explain (verbose, costs off)
	select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
                                            QUERY PLAN                                            
--------------------------------------------------------------------------------------------------
 Limit
   Output: foo.f1, loct1.f1, foo.f2
   ->  Sort
         Output: foo.f1, loct1.f1, foo.f2
         Sort Key: foo.f2
         ->  Merge Left Join
               Output: foo.f1, loct1.f1, foo.f2
               Merge Cond: (foo.f1 = loct1.f1)
               ->  Merge Append
                     Sort Key: foo.f1
                     ->  Index Scan using i_foo_f1 on public.foo
                           Output: foo.f1, foo.f2
                     ->  Foreign Scan on public.foo2
                           Output: foo2.f1, foo2.f2
                           Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST
               ->  Index Only Scan using i_loct1_f1 on public.loct1
                     Output: loct1.f1
(17 rows)

select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10;
 f1 | f1 
----+----
 10 | 10
 11 |   
 12 | 12
 13 |   
 14 | 14
 15 |   
 16 | 16
 17 |   
 18 | 18
 19 |   
(10 rows)

RESET enable_hashjoin;
RESET enable_nestloop;
-- Test that WHERE CURRENT OF is not supported
begin;
declare c cursor for select * from bar where f1 = 7;
fetch from c;
 f1 | f2  
----+-----
  7 | 177
(1 row)

update bar set f2 = null where current of c;
ERROR:  WHERE CURRENT OF is not supported for this table type
rollback;
explain (verbose, costs off)
delete from foo where f1 < 5 returning *;
                                   QUERY PLAN                                   
--------------------------------------------------------------------------------
 Delete on public.foo
   Output: foo.f1, foo.f2
   Delete on public.foo
   Foreign Delete on public.foo2
   ->  Index Scan using i_foo_f1 on public.foo
         Output: foo.ctid
         Index Cond: (foo.f1 < 5)
   ->  Foreign Delete on public.foo2
         Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
(9 rows)

delete from foo where f1 < 5 returning *;
 f1 | f2 
----+----
  1 |  1
  3 |  3
  0 |  0
  2 |  2
  4 |  4
(5 rows)

explain (verbose, costs off)
update bar set f2 = f2 + 100 returning *;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Update on public.bar
   Output: bar.f1, bar.f2
   Update on public.bar
   Foreign Update on public.bar2
   ->  Seq Scan on public.bar
         Output: bar.f1, (bar.f2 + 100), bar.ctid
   ->  Foreign Update on public.bar2
         Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
(8 rows)

update bar set f2 = f2 + 100 returning *;
 f1 | f2  
----+-----
  1 | 311
  2 | 322
  6 | 266
  3 | 333
  4 | 344
  7 | 277
(6 rows)

-- Test that UPDATE/DELETE with inherited target works with row-level triggers
CREATE TRIGGER trig_row_before
BEFORE UPDATE OR DELETE ON bar2
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER trig_row_after
AFTER UPDATE OR DELETE ON bar2
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
explain (verbose, costs off)
update bar set f2 = f2 + 100;
                                               QUERY PLAN                                               
--------------------------------------------------------------------------------------------------------
 Update on public.bar
   Update on public.bar
   Foreign Update on public.bar2
     Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3
   ->  Seq Scan on public.bar
         Output: bar.f1, (bar.f2 + 100), bar.ctid
   ->  Foreign Scan on public.bar2
         Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, bar2.*
         Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
(9 rows)

update bar set f2 = f2 + 100;
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2
NOTICE:  OLD: (3,333,33),NEW: (3,433,33)
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2
NOTICE:  OLD: (4,344,44),NEW: (4,444,44)
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2
NOTICE:  OLD: (7,277,77),NEW: (7,377,77)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
NOTICE:  OLD: (3,333,33),NEW: (3,433,33)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
NOTICE:  OLD: (4,344,44),NEW: (4,444,44)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
NOTICE:  OLD: (7,277,77),NEW: (7,377,77)
explain (verbose, costs off)
delete from bar where f2 < 400;
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Delete on public.bar
   Delete on public.bar
   Foreign Delete on public.bar2
     Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3
   ->  Seq Scan on public.bar
         Output: bar.ctid
         Filter: (bar.f2 < 400)
   ->  Foreign Scan on public.bar2
         Output: bar2.ctid, bar2.*
         Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
(10 rows)

delete from bar where f2 < 400;
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW DELETE ON bar2
NOTICE:  OLD: (7,377,77)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW DELETE ON bar2
NOTICE:  OLD: (7,377,77)
-- cleanup
drop table foo cascade;
NOTICE:  drop cascades to foreign table foo2
drop table bar cascade;
NOTICE:  drop cascades to foreign table bar2
drop table loct1;
drop table loct2;
-- Test pushing down UPDATE/DELETE joins to the remote server
create table parent (a int, b text);
create table loct1 (a int, b text);
create table loct2 (a int, b text);
create foreign table remt1 (a int, b text)
  server loopback options (table_name 'loct1');
create foreign table remt2 (a int, b text)
  server loopback options (table_name 'loct2');
alter foreign table remt1 inherit parent;
insert into remt1 values (1, 'foo');
insert into remt1 values (2, 'bar');
insert into remt2 values (1, 'foo');
insert into remt2 values (2, 'bar');
analyze remt1;
analyze remt2;
explain (verbose, costs off)
update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
                                                                  QUERY PLAN                                                                   
-----------------------------------------------------------------------------------------------------------------------------------------------
 Update on public.parent
   Output: parent.a, parent.b, remt2.a, remt2.b
   Update on public.parent
   Foreign Update on public.remt1
   ->  Nested Loop
         Output: parent.a, (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
         Join Filter: (parent.a = remt2.a)
         ->  Seq Scan on public.parent
               Output: parent.a, parent.b, parent.ctid
         ->  Foreign Scan on public.remt2
               Output: remt2.b, remt2.*, remt2.a
               Remote SQL: SELECT a, b FROM public.loct2
   ->  Foreign Update
         Remote SQL: UPDATE public.loct1 r4 SET b = (r4.b || r2.b) FROM public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b, r2.a, r2.b
(14 rows)

update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
 a |   b    | a |  b  
---+--------+---+-----
 1 | foofoo | 1 | foo
 2 | barbar | 2 | bar
(2 rows)

explain (verbose, costs off)
delete from parent using remt2 where parent.a = remt2.a returning parent;
                                                    QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Delete on public.parent
   Output: parent.*
   Delete on public.parent
   Foreign Delete on public.remt1
   ->  Nested Loop
         Output: parent.ctid, remt2.*
         Join Filter: (parent.a = remt2.a)
         ->  Seq Scan on public.parent
               Output: parent.ctid, parent.a
         ->  Foreign Scan on public.remt2
               Output: remt2.*, remt2.a
               Remote SQL: SELECT a, b FROM public.loct2
   ->  Foreign Delete
         Remote SQL: DELETE FROM public.loct1 r4 USING public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b
(14 rows)

delete from parent using remt2 where parent.a = remt2.a returning parent;
   parent   
------------
 (1,foofoo)
 (2,barbar)
(2 rows)

-- cleanup
drop foreign table remt1;
drop foreign table remt2;
drop table loct1;
drop table loct2;
drop table parent;
-- ===================================================================
-- test tuple routing for foreign-table partitions
-- ===================================================================
-- Test insert tuple routing
create table itrtest (a int, b text) partition by list (a);
create table loct1 (a int check (a in (1)), b text);
create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
create table loct2 (a int check (a in (2)), b text);
create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
alter table itrtest attach partition remp1 for values in (1);
alter table itrtest attach partition remp2 for values in (2);
insert into itrtest values (1, 'foo');
insert into itrtest values (1, 'bar') returning *;
 a |  b  
---+-----
 1 | bar
(1 row)

insert into itrtest values (2, 'baz');
insert into itrtest values (2, 'qux') returning *;
 a |  b  
---+-----
 2 | qux
(1 row)

insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
 a |   b   
---+-------
 1 | test1
 2 | test2
(2 rows)

select tableoid::regclass, * FROM itrtest;
 tableoid | a |   b   
----------+---+-------
 remp1    | 1 | foo
 remp1    | 1 | bar
 remp1    | 1 | test1
 remp2    | 2 | baz
 remp2    | 2 | qux
 remp2    | 2 | test2
(6 rows)

select tableoid::regclass, * FROM remp1;
 tableoid | a |   b   
----------+---+-------
 remp1    | 1 | foo
 remp1    | 1 | bar
 remp1    | 1 | test1
(3 rows)

select tableoid::regclass, * FROM remp2;
 tableoid |   b   | a 
----------+-------+---
 remp2    | baz   | 2
 remp2    | qux   | 2
 remp2    | test2 | 2
(3 rows)

delete from itrtest;
create unique index loct1_idx on loct1 (a);
-- DO NOTHING without an inference specification is supported
insert into itrtest values (1, 'foo') on conflict do nothing returning *;
 a |  b  
---+-----
 1 | foo
(1 row)

insert into itrtest values (1, 'foo') on conflict do nothing returning *;
 a | b 
---+---
(0 rows)

-- But other cases are not supported
insert into itrtest values (1, 'bar') on conflict (a) do nothing;
ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification
insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification
select tableoid::regclass, * FROM itrtest;
 tableoid | a |  b  
----------+---+-----
 remp1    | 1 | foo
(1 row)

delete from itrtest;
drop index loct1_idx;
-- Test that remote triggers work with insert tuple routing
create function br_insert_trigfunc() returns trigger as $$
begin
	new.b := new.b || ' triggered !';
	return new;
end
$$ language plpgsql;
create trigger loct1_br_insert_trigger before insert on loct1
	for each row execute procedure br_insert_trigfunc();
create trigger loct2_br_insert_trigger before insert on loct2
	for each row execute procedure br_insert_trigfunc();
-- The new values are concatenated with ' triggered !'
insert into itrtest values (1, 'foo') returning *;
 a |        b        
---+-----------------
 1 | foo triggered !
(1 row)

insert into itrtest values (2, 'qux') returning *;
 a |        b        
---+-----------------
 2 | qux triggered !
(1 row)

insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
 a |         b         
---+-------------------
 1 | test1 triggered !
 2 | test2 triggered !
(2 rows)

with result as (insert into itrtest values (1, 'test1'), (2, 'test2') returning *) select * from result;
 a |         b         
---+-------------------
 1 | test1 triggered !
 2 | test2 triggered !
(2 rows)

drop trigger loct1_br_insert_trigger on loct1;
drop trigger loct2_br_insert_trigger on loct2;
drop table itrtest;
drop table loct1;
drop table loct2;
-- Test update tuple routing
create table utrtest (a int, b text) partition by list (a);
create table loct (a int check (a in (1)), b text);
create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
create table locp (a int check (a in (2)), b text);
alter table utrtest attach partition remp for values in (1);
alter table utrtest attach partition locp for values in (2);
insert into utrtest values (1, 'foo');
insert into utrtest values (2, 'qux');
select tableoid::regclass, * FROM utrtest;
 tableoid | a |  b  
----------+---+-----
 remp     | 1 | foo
 locp     | 2 | qux
(2 rows)

select tableoid::regclass, * FROM remp;
 tableoid | a |  b  
----------+---+-----
 remp     | 1 | foo
(1 row)

select tableoid::regclass, * FROM locp;
 tableoid | a |  b  
----------+---+-----
 locp     | 2 | qux
(1 row)

-- It's not allowed to move a row from a partition that is foreign to another
update utrtest set a = 2 where b = 'foo' returning *;
ERROR:  new row for relation "loct" violates check constraint "loct_a_check"
DETAIL:  Failing row contains (2, foo).
CONTEXT:  remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
-- But the reverse is allowed
update utrtest set a = 1 where b = 'qux' returning *;
 a |  b  
---+-----
 1 | qux
(1 row)

select tableoid::regclass, * FROM utrtest;
 tableoid | a |  b  
----------+---+-----
 remp     | 1 | foo
 remp     | 1 | qux
(2 rows)

select tableoid::regclass, * FROM remp;
 tableoid | a |  b  
----------+---+-----
 remp     | 1 | foo
 remp     | 1 | qux
(2 rows)

select tableoid::regclass, * FROM locp;
 tableoid | a | b 
----------+---+---
(0 rows)

-- The executor should not let unexercised FDWs shut down
update utrtest set a = 1 where b = 'foo';
-- Test that remote triggers work with update tuple routing
create trigger loct_br_insert_trigger before insert on loct
	for each row execute procedure br_insert_trigfunc();
delete from utrtest;
insert into utrtest values (2, 'qux');
-- Check case where the foreign partition is a subplan target rel
explain (verbose, costs off)
update utrtest set a = 1 where a = 1 or a = 2 returning *;
                                          QUERY PLAN                                          
----------------------------------------------------------------------------------------------
 Update on public.utrtest
   Output: remp.a, remp.b
   Foreign Update on public.remp
   Update on public.locp
   ->  Foreign Update on public.remp
         Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b
   ->  Seq Scan on public.locp
         Output: 1, locp.b, locp.ctid
         Filter: ((locp.a = 1) OR (locp.a = 2))
(9 rows)

-- The new values are concatenated with ' triggered !'
update utrtest set a = 1 where a = 1 or a = 2 returning *;
 a |        b        
---+-----------------
 1 | qux triggered !
(1 row)

delete from utrtest;
insert into utrtest values (2, 'qux');
-- Check case where the foreign partition isn't a subplan target rel
explain (verbose, costs off)
update utrtest set a = 1 where a = 2 returning *;
              QUERY PLAN              
--------------------------------------
 Update on public.utrtest
   Output: locp.a, locp.b
   Update on public.locp
   ->  Seq Scan on public.locp
         Output: 1, locp.b, locp.ctid
         Filter: (locp.a = 2)
(6 rows)

-- The new values are concatenated with ' triggered !'
update utrtest set a = 1 where a = 2 returning *;
 a |        b        
---+-----------------
 1 | qux triggered !
(1 row)

drop trigger loct_br_insert_trigger on loct;
-- We can move rows to a foreign partition that has been updated already,
-- but can't move rows to a foreign partition that hasn't been updated yet
delete from utrtest;
insert into utrtest values (1, 'foo');
insert into utrtest values (2, 'qux');
-- Test the former case:
-- with a direct modification plan
explain (verbose, costs off)
update utrtest set a = 1 returning *;
                           QUERY PLAN                            
-----------------------------------------------------------------
 Update on public.utrtest
   Output: remp.a, remp.b
   Foreign Update on public.remp
   Update on public.locp
   ->  Foreign Update on public.remp
         Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b
   ->  Seq Scan on public.locp
         Output: 1, locp.b, locp.ctid
(8 rows)

update utrtest set a = 1 returning *;
 a |  b  
---+-----
 1 | foo
 1 | qux
(2 rows)

delete from utrtest;
insert into utrtest values (1, 'foo');
insert into utrtest values (2, 'qux');
-- with a non-direct modification plan
explain (verbose, costs off)
update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Update on public.utrtest
   Output: remp.a, remp.b, "*VALUES*".column1
   Foreign Update on public.remp
     Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
   Update on public.locp
   ->  Hash Join
         Output: 1, remp.b, remp.ctid, "*VALUES*".*, "*VALUES*".column1
         Hash Cond: (remp.a = "*VALUES*".column1)
         ->  Foreign Scan on public.remp
               Output: remp.b, remp.ctid, remp.a
               Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
         ->  Hash
               Output: "*VALUES*".*, "*VALUES*".column1
               ->  Values Scan on "*VALUES*"
                     Output: "*VALUES*".*, "*VALUES*".column1
   ->  Hash Join
         Output: 1, locp.b, locp.ctid, "*VALUES*".*, "*VALUES*".column1
         Hash Cond: (locp.a = "*VALUES*".column1)
         ->  Seq Scan on public.locp
               Output: locp.b, locp.ctid, locp.a
         ->  Hash
               Output: "*VALUES*".*, "*VALUES*".column1
               ->  Values Scan on "*VALUES*"
                     Output: "*VALUES*".*, "*VALUES*".column1
(24 rows)

update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
 a |  b  | x 
---+-----+---
 1 | foo | 1
 1 | qux | 2
(2 rows)

-- Change the definition of utrtest so that the foreign partition get updated
-- after the local partition
delete from utrtest;
alter table utrtest detach partition remp;
drop foreign table remp;
alter table loct drop constraint loct_a_check;
alter table loct add check (a in (3));
create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct');
alter table utrtest attach partition remp for values in (3);
insert into utrtest values (2, 'qux');
insert into utrtest values (3, 'xyzzy');
-- Test the latter case:
-- with a direct modification plan
explain (verbose, costs off)
update utrtest set a = 3 returning *;
                           QUERY PLAN                            
-----------------------------------------------------------------
 Update on public.utrtest
   Output: locp.a, locp.b
   Update on public.locp
   Foreign Update on public.remp
   ->  Seq Scan on public.locp
         Output: 3, locp.b, locp.ctid
   ->  Foreign Update on public.remp
         Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b
(8 rows)

update utrtest set a = 3 returning *; -- ERROR
ERROR:  cannot route tuples into foreign table to be updated "remp"
-- with a non-direct modification plan
explain (verbose, costs off)
update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
                                  QUERY PLAN                                  
------------------------------------------------------------------------------
 Update on public.utrtest
   Output: locp.a, locp.b, "*VALUES*".column1
   Update on public.locp
   Foreign Update on public.remp
     Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
   ->  Hash Join
         Output: 3, locp.b, locp.ctid, "*VALUES*".*, "*VALUES*".column1
         Hash Cond: (locp.a = "*VALUES*".column1)
         ->  Seq Scan on public.locp
               Output: locp.b, locp.ctid, locp.a
         ->  Hash
               Output: "*VALUES*".*, "*VALUES*".column1
               ->  Values Scan on "*VALUES*"
                     Output: "*VALUES*".*, "*VALUES*".column1
   ->  Hash Join
         Output: 3, remp.b, remp.ctid, "*VALUES*".*, "*VALUES*".column1
         Hash Cond: (remp.a = "*VALUES*".column1)
         ->  Foreign Scan on public.remp
               Output: remp.b, remp.ctid, remp.a
               Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
         ->  Hash
               Output: "*VALUES*".*, "*VALUES*".column1
               ->  Values Scan on "*VALUES*"
                     Output: "*VALUES*".*, "*VALUES*".column1
(24 rows)

update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR
ERROR:  cannot route tuples into foreign table to be updated "remp"
drop table utrtest;
drop table loct;
-- Test copy tuple routing
create table ctrtest (a int, b text) partition by list (a);
create table loct1 (a int check (a in (1)), b text);
create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
create table loct2 (a int check (a in (2)), b text);
create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
alter table ctrtest attach partition remp1 for values in (1);
alter table ctrtest attach partition remp2 for values in (2);
copy ctrtest from stdin;
select tableoid::regclass, * FROM ctrtest;
 tableoid | a |  b  
----------+---+-----
 remp1    | 1 | foo
 remp2    | 2 | qux
(2 rows)

select tableoid::regclass, * FROM remp1;
 tableoid | a |  b  
----------+---+-----
 remp1    | 1 | foo
(1 row)

select tableoid::regclass, * FROM remp2;
 tableoid |  b  | a 
----------+-----+---
 remp2    | qux | 2
(1 row)

-- Copying into foreign partitions directly should work as well
copy remp1 from stdin;
select tableoid::regclass, * FROM remp1;
 tableoid | a |  b  
----------+---+-----
 remp1    | 1 | foo
 remp1    | 1 | bar
(2 rows)

drop table ctrtest;
drop table loct1;
drop table loct2;
-- ===================================================================
-- test COPY FROM
-- ===================================================================
create table loc2 (f1 int, f2 text);
alter table loc2 set (autovacuum_enabled = 'false');
create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
-- Test basic functionality
copy rem2 from stdin;
select * from rem2;
 f1 | f2  
----+-----
  1 | foo
  2 | bar
(2 rows)

delete from rem2;
-- Test check constraints
alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
-- check constraint is enforced on the remote side, not locally
copy rem2 from stdin;
copy rem2 from stdin; -- ERROR
ERROR:  new row for relation "loc2" violates check constraint "loc2_f1positive"
DETAIL:  Failing row contains (-1, xyzzy).
CONTEXT:  remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2)
COPY rem2, line 1: "-1	xyzzy"
select * from rem2;
 f1 | f2  
----+-----
  1 | foo
  2 | bar
(2 rows)

alter foreign table rem2 drop constraint rem2_f1positive;
alter table loc2 drop constraint loc2_f1positive;
delete from rem2;
-- Test local triggers
create trigger trig_stmt_before before insert on rem2
	for each statement execute procedure trigger_func();
create trigger trig_stmt_after after insert on rem2
	for each statement execute procedure trigger_func();
create trigger trig_row_before before insert on rem2
	for each row execute procedure trigger_data(23,'skidoo');
create trigger trig_row_after after insert on rem2
	for each row execute procedure trigger_data(23,'skidoo');
copy rem2 from stdin;
NOTICE:  trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
NOTICE:  NEW: (1,foo)
NOTICE:  trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
NOTICE:  NEW: (2,bar)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
NOTICE:  NEW: (1,foo)
NOTICE:  trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
NOTICE:  NEW: (2,bar)
NOTICE:  trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
select * from rem2;
 f1 | f2  
----+-----
  1 | foo
  2 | bar
(2 rows)

drop trigger trig_row_before on rem2;
drop trigger trig_row_after on rem2;
drop trigger trig_stmt_before on rem2;
drop trigger trig_stmt_after on rem2;
delete from rem2;
create trigger trig_row_before_insert before insert on rem2
	for each row execute procedure trig_row_before_insupdate();
-- The new values are concatenated with ' triggered !'
copy rem2 from stdin;
select * from rem2;
 f1 |       f2        
----+-----------------
  1 | foo triggered !
  2 | bar triggered !
(2 rows)

drop trigger trig_row_before_insert on rem2;
delete from rem2;
create trigger trig_null before insert on rem2
	for each row execute procedure trig_null();
-- Nothing happens
copy rem2 from stdin;
select * from rem2;
 f1 | f2 
----+----
(0 rows)

drop trigger trig_null on rem2;
delete from rem2;
-- Test remote triggers
create trigger trig_row_before_insert before insert on loc2
	for each row execute procedure trig_row_before_insupdate();
-- The new values are concatenated with ' triggered !'
copy rem2 from stdin;
select * from rem2;
 f1 |       f2        
----+-----------------
  1 | foo triggered !
  2 | bar triggered !
(2 rows)

drop trigger trig_row_before_insert on loc2;
delete from rem2;
create trigger trig_null before insert on loc2
	for each row execute procedure trig_null();
-- Nothing happens
copy rem2 from stdin;
select * from rem2;
 f1 | f2 
----+----
(0 rows)

drop trigger trig_null on loc2;
delete from rem2;
-- Test a combination of local and remote triggers
create trigger rem2_trig_row_before before insert on rem2
	for each row execute procedure trigger_data(23,'skidoo');
create trigger rem2_trig_row_after after insert on rem2
	for each row execute procedure trigger_data(23,'skidoo');
create trigger loc2_trig_row_before_insert before insert on loc2
	for each row execute procedure trig_row_before_insupdate();
copy rem2 from stdin;
NOTICE:  rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
NOTICE:  NEW: (1,foo)
NOTICE:  rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
NOTICE:  NEW: (2,bar)
NOTICE:  rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
NOTICE:  NEW: (1,"foo triggered !")
NOTICE:  rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
NOTICE:  NEW: (2,"bar triggered !")
select * from rem2;
 f1 |       f2        
----+-----------------
  1 | foo triggered !
  2 | bar triggered !
(2 rows)

drop trigger rem2_trig_row_before on rem2;
drop trigger rem2_trig_row_after on rem2;
drop trigger loc2_trig_row_before_insert on loc2;
delete from rem2;
-- test COPY FROM with foreign table created in the same transaction
create table loc3 (f1 int, f2 text);
begin;
create foreign table rem3 (f1 int, f2 text)
	server loopback options(table_name 'loc3');
copy rem3 from stdin;
commit;
select * from rem3;
 f1 | f2  
----+-----
  1 | foo
  2 | bar
(2 rows)

drop foreign table rem3;
drop table loc3;
-- ===================================================================
-- test IMPORT FOREIGN SCHEMA
-- ===================================================================
CREATE SCHEMA import_source;
CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL);
CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX");
CREATE TYPE typ1 AS (m1 int, m2 varchar);
CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1);
CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42));
CREATE TABLE import_source."x 5" (c1 float8);
ALTER TABLE import_source."x 5" DROP COLUMN c1;
CREATE TABLE import_source.t4 (c1 int) PARTITION BY RANGE (c1);
CREATE TABLE import_source.t4_part PARTITION OF import_source.t4
  FOR VALUES FROM (1) TO (100);
CREATE SCHEMA import_dest1;
IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1;
\det+ import_dest1.*
                                     List of foreign tables
    Schema    | Table |  Server  |                   FDW options                   | Description 
--------------+-------+----------+-------------------------------------------------+-------------
 import_dest1 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
 import_dest1 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
 import_dest1 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
 import_dest1 | t4    | loopback | (schema_name 'import_source', table_name 't4')  | 
 import_dest1 | x 4   | loopback | (schema_name 'import_source', table_name 'x 4') | 
 import_dest1 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
(6 rows)

\d import_dest1.*
                         Foreign table "import_dest1.t1"
 Column |       Type        | Collation | Nullable | Default |    FDW options     
--------+-------------------+-----------+----------+---------+--------------------
 c1     | integer           |           |          |         | (column_name 'c1')
 c2     | character varying |           | not null |         | (column_name 'c2')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't1')

                         Foreign table "import_dest1.t2"
 Column |       Type        | Collation | Nullable | Default |    FDW options     
--------+-------------------+-----------+----------+---------+--------------------
 c1     | integer           |           |          |         | (column_name 'c1')
 c2     | character varying |           |          |         | (column_name 'c2')
 c3     | text              | POSIX     |          |         | (column_name 'c3')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't2')

                             Foreign table "import_dest1.t3"
 Column |           Type           | Collation | Nullable | Default |    FDW options     
--------+--------------------------+-----------+----------+---------+--------------------
 c1     | timestamp with time zone |           |          |         | (column_name 'c1')
 c2     | typ1                     |           |          |         | (column_name 'c2')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't3')

                    Foreign table "import_dest1.t4"
 Column |  Type   | Collation | Nullable | Default |    FDW options     
--------+---------+-----------+----------+---------+--------------------
 c1     | integer |           |          |         | (column_name 'c1')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't4')

                           Foreign table "import_dest1.x 4"
 Column |         Type          | Collation | Nullable | Default |     FDW options     
--------+-----------------------+-----------+----------+---------+---------------------
 c1     | double precision      |           |          |         | (column_name 'c1')
 C 2    | text                  |           |          |         | (column_name 'C 2')
 c3     | character varying(42) |           |          |         | (column_name 'c3')
Server: loopback
FDW options: (schema_name 'import_source', table_name 'x 4')

               Foreign table "import_dest1.x 5"
 Column | Type | Collation | Nullable | Default | FDW options 
--------+------+-----------+----------+---------+-------------
Server: loopback
FDW options: (schema_name 'import_source', table_name 'x 5')

-- Options
CREATE SCHEMA import_dest2;
IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2
  OPTIONS (import_default 'true');
\det+ import_dest2.*
                                     List of foreign tables
    Schema    | Table |  Server  |                   FDW options                   | Description 
--------------+-------+----------+-------------------------------------------------+-------------
 import_dest2 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
 import_dest2 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
 import_dest2 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
 import_dest2 | t4    | loopback | (schema_name 'import_source', table_name 't4')  | 
 import_dest2 | x 4   | loopback | (schema_name 'import_source', table_name 'x 4') | 
 import_dest2 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
(6 rows)

\d import_dest2.*
                         Foreign table "import_dest2.t1"
 Column |       Type        | Collation | Nullable | Default |    FDW options     
--------+-------------------+-----------+----------+---------+--------------------
 c1     | integer           |           |          |         | (column_name 'c1')
 c2     | character varying |           | not null |         | (column_name 'c2')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't1')

                         Foreign table "import_dest2.t2"
 Column |       Type        | Collation | Nullable | Default |    FDW options     
--------+-------------------+-----------+----------+---------+--------------------
 c1     | integer           |           |          | 42      | (column_name 'c1')
 c2     | character varying |           |          |         | (column_name 'c2')
 c3     | text              | POSIX     |          |         | (column_name 'c3')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't2')

                             Foreign table "import_dest2.t3"
 Column |           Type           | Collation | Nullable | Default |    FDW options     
--------+--------------------------+-----------+----------+---------+--------------------
 c1     | timestamp with time zone |           |          | now()   | (column_name 'c1')
 c2     | typ1                     |           |          |         | (column_name 'c2')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't3')

                    Foreign table "import_dest2.t4"
 Column |  Type   | Collation | Nullable | Default |    FDW options     
--------+---------+-----------+----------+---------+--------------------
 c1     | integer |           |          |         | (column_name 'c1')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't4')

                           Foreign table "import_dest2.x 4"
 Column |         Type          | Collation | Nullable | Default |     FDW options     
--------+-----------------------+-----------+----------+---------+---------------------
 c1     | double precision      |           |          |         | (column_name 'c1')
 C 2    | text                  |           |          |         | (column_name 'C 2')
 c3     | character varying(42) |           |          |         | (column_name 'c3')
Server: loopback
FDW options: (schema_name 'import_source', table_name 'x 4')

               Foreign table "import_dest2.x 5"
 Column | Type | Collation | Nullable | Default | FDW options 
--------+------+-----------+----------+---------+-------------
Server: loopback
FDW options: (schema_name 'import_source', table_name 'x 5')

CREATE SCHEMA import_dest3;
IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3
  OPTIONS (import_collate 'false', import_not_null 'false');
\det+ import_dest3.*
                                     List of foreign tables
    Schema    | Table |  Server  |                   FDW options                   | Description 
--------------+-------+----------+-------------------------------------------------+-------------
 import_dest3 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
 import_dest3 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
 import_dest3 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
 import_dest3 | t4    | loopback | (schema_name 'import_source', table_name 't4')  | 
 import_dest3 | x 4   | loopback | (schema_name 'import_source', table_name 'x 4') | 
 import_dest3 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
(6 rows)

\d import_dest3.*
                         Foreign table "import_dest3.t1"
 Column |       Type        | Collation | Nullable | Default |    FDW options     
--------+-------------------+-----------+----------+---------+--------------------
 c1     | integer           |           |          |         | (column_name 'c1')
 c2     | character varying |           |          |         | (column_name 'c2')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't1')

                         Foreign table "import_dest3.t2"
 Column |       Type        | Collation | Nullable | Default |    FDW options     
--------+-------------------+-----------+----------+---------+--------------------
 c1     | integer           |           |          |         | (column_name 'c1')
 c2     | character varying |           |          |         | (column_name 'c2')
 c3     | text              |           |          |         | (column_name 'c3')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't2')

                             Foreign table "import_dest3.t3"
 Column |           Type           | Collation | Nullable | Default |    FDW options     
--------+--------------------------+-----------+----------+---------+--------------------
 c1     | timestamp with time zone |           |          |         | (column_name 'c1')
 c2     | typ1                     |           |          |         | (column_name 'c2')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't3')

                    Foreign table "import_dest3.t4"
 Column |  Type   | Collation | Nullable | Default |    FDW options     
--------+---------+-----------+----------+---------+--------------------
 c1     | integer |           |          |         | (column_name 'c1')
Server: loopback
FDW options: (schema_name 'import_source', table_name 't4')

                           Foreign table "import_dest3.x 4"
 Column |         Type          | Collation | Nullable | Default |     FDW options     
--------+-----------------------+-----------+----------+---------+---------------------
 c1     | double precision      |           |          |         | (column_name 'c1')
 C 2    | text                  |           |          |         | (column_name 'C 2')
 c3     | character varying(42) |           |          |         | (column_name 'c3')
Server: loopback
FDW options: (schema_name 'import_source', table_name 'x 4')

               Foreign table "import_dest3.x 5"
 Column | Type | Collation | Nullable | Default | FDW options 
--------+------+-----------+----------+---------+-------------
Server: loopback
FDW options: (schema_name 'import_source', table_name 'x 5')

-- Check LIMIT TO and EXCEPT
CREATE SCHEMA import_dest4;
IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch)
  FROM SERVER loopback INTO import_dest4;
\det+ import_dest4.*
                                     List of foreign tables
    Schema    | Table |  Server  |                  FDW options                   | Description 
--------------+-------+----------+------------------------------------------------+-------------
 import_dest4 | t1    | loopback | (schema_name 'import_source', table_name 't1') | 
(1 row)

IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch)
  FROM SERVER loopback INTO import_dest4;
\det+ import_dest4.*
                                     List of foreign tables
    Schema    | Table |  Server  |                   FDW options                   | Description 
--------------+-------+----------+-------------------------------------------------+-------------
 import_dest4 | t1    | loopback | (schema_name 'import_source', table_name 't1')  | 
 import_dest4 | t2    | loopback | (schema_name 'import_source', table_name 't2')  | 
 import_dest4 | t3    | loopback | (schema_name 'import_source', table_name 't3')  | 
 import_dest4 | t4    | loopback | (schema_name 'import_source', table_name 't4')  | 
 import_dest4 | x 5   | loopback | (schema_name 'import_source', table_name 'x 5') | 
(5 rows)

-- Assorted error cases
IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4;
ERROR:  relation "t1" already exists
CONTEXT:  importing foreign table "t1"
IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4;
ERROR:  schema "nonesuch" is not present on foreign server "loopback"
IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere;
ERROR:  schema "notthere" does not exist
IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere;
ERROR:  server "nowhere" does not exist
-- Check case of a type present only on the remote server.
-- We can fake this by dropping the type locally in our transaction.
CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue');
CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors");
CREATE SCHEMA import_dest5;
BEGIN;
DROP TYPE "Colors" CASCADE;
NOTICE:  drop cascades to column Col of table import_source.t5
IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5)
  FROM SERVER loopback INTO import_dest5;  -- ERROR
ERROR:  type "public.Colors" does not exist
LINE 4:   "Col" public."Colors" OPTIONS (column_name 'Col')
                ^
QUERY:  CREATE FOREIGN TABLE t5 (
  c1 integer OPTIONS (column_name 'c1'),
  c2 text OPTIONS (column_name 'c2') COLLATE pg_catalog."C",
  "Col" public."Colors" OPTIONS (column_name 'Col')
) SERVER loopback
OPTIONS (schema_name 'import_source', table_name 't5');
CONTEXT:  importing foreign table "t5"
ROLLBACK;
BEGIN;
CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' );
SELECT count(*)
FROM pg_foreign_server
WHERE srvname = 'fetch101'
AND srvoptions @> array['fetch_size=101'];
 count 
-------
     1
(1 row)

ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' );
SELECT count(*)
FROM pg_foreign_server
WHERE srvname = 'fetch101'
AND srvoptions @> array['fetch_size=101'];
 count 
-------
     0
(1 row)

SELECT count(*)
FROM pg_foreign_server
WHERE srvname = 'fetch101'
AND srvoptions @> array['fetch_size=202'];
 count 
-------
     1
(1 row)

CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' );
SELECT COUNT(*)
FROM pg_foreign_table
WHERE ftrelid = 'table30000'::regclass
AND ftoptions @> array['fetch_size=30000'];
 count 
-------
     1
(1 row)

ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000');
SELECT COUNT(*)
FROM pg_foreign_table
WHERE ftrelid = 'table30000'::regclass
AND ftoptions @> array['fetch_size=30000'];
 count 
-------
     0
(1 row)

SELECT COUNT(*)
FROM pg_foreign_table
WHERE ftrelid = 'table30000'::regclass
AND ftoptions @> array['fetch_size=60000'];
 count 
-------
     1
(1 row)

ROLLBACK;
-- ===================================================================
-- test partitionwise joins
-- ===================================================================
SET enable_partitionwise_join=on;
CREATE TABLE fprt1 (a int, b int, c varchar) PARTITION BY RANGE(a);
CREATE TABLE fprt1_p1 (LIKE fprt1);
CREATE TABLE fprt1_p2 (LIKE fprt1);
ALTER TABLE fprt1_p1 SET (autovacuum_enabled = 'false');
ALTER TABLE fprt1_p2 SET (autovacuum_enabled = 'false');
INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i;
INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i;
CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250)
	SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true');
CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500)
	SERVER loopback OPTIONS (TABLE_NAME 'fprt1_p2');
ANALYZE fprt1;
ANALYZE fprt1_p1;
ANALYZE fprt1_p2;
CREATE TABLE fprt2 (a int, b int, c varchar) PARTITION BY RANGE(b);
CREATE TABLE fprt2_p1 (LIKE fprt2);
CREATE TABLE fprt2_p2 (LIKE fprt2);
ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false');
ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false');
INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i;
INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i;
CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int)
	SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true');
ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250);
CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500)
	SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true');
ANALYZE fprt2;
ANALYZE fprt2_p1;
ANALYZE fprt2_p2;
-- inner join three tables
EXPLAIN (COSTS OFF)
SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
                                                     QUERY PLAN                                                     
--------------------------------------------------------------------------------------------------------------------
 Sort
   Sort Key: t1.a, t3.c
   ->  Append
         ->  Foreign Scan
               Relations: ((public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2)) INNER JOIN (public.ftprt1_p1 t3)
         ->  Foreign Scan
               Relations: ((public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2)) INNER JOIN (public.ftprt1_p2 t3)
(7 rows)

SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3;
  a  |  b  |  c   
-----+-----+------
   0 |   0 | 0000
 150 | 150 | 0003
 250 | 250 | 0005
 400 | 400 | 0008
(4 rows)

-- left outer join + nullable clause
EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
                                                                                                                     QUERY PLAN                                                                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan
   Output: t1.a, ftprt2_p1.b, ftprt2_p1.c
   Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2)
   Remote SQL: SELECT r5.a, r6.b, r6.c FROM (public.fprt1_p1 r5 LEFT JOIN public.fprt2_p1 r6 ON (((r5.a = r6.b)) AND ((r5.b = r6.a)) AND ((r6.a < 10)))) WHERE ((r5.a < 10)) ORDER BY r5.a ASC NULLS LAST, r6.b ASC NULLS LAST, r6.c ASC NULLS LAST
(4 rows)

SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3;
 a | b |  c   
---+---+------
 0 | 0 | 0000
 2 |   | 
 4 |   | 
 6 | 6 | 0000
 8 |   | 
(5 rows)

-- with whole-row reference; partitionwise join does not apply
EXPLAIN (COSTS OFF)
SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2;
                       QUERY PLAN                       
--------------------------------------------------------
 Sort
   Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2)
   ->  Hash Full Join
         Hash Cond: (t1.a = t2.b)
         ->  Append
               ->  Foreign Scan on ftprt1_p1 t1
               ->  Foreign Scan on ftprt1_p2 t1_1
         ->  Hash
               ->  Append
                     ->  Foreign Scan on ftprt2_p1 t2
                     ->  Foreign Scan on ftprt2_p2 t2_1
(11 rows)

SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2;
       wr       |       wr       
----------------+----------------
 (0,0,0000)     | (0,0,0000)
 (50,50,0001)   | 
 (100,100,0002) | 
 (150,150,0003) | (150,150,0003)
 (200,200,0004) | 
 (250,250,0005) | (250,250,0005)
 (300,300,0006) | 
 (350,350,0007) | 
 (400,400,0008) | (400,400,0008)
 (450,450,0009) | 
                | (75,75,0001)
                | (225,225,0004)
                | (325,325,0006)
                | (475,475,0009)
(14 rows)

-- join with lateral reference
EXPLAIN (COSTS OFF)
SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Sort
   Sort Key: t1.a, t1.b
   ->  Append
         ->  Foreign Scan
               Relations: (public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2)
         ->  Foreign Scan
               Relations: (public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2)
(7 rows)

SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
  a  |  b  
-----+-----
   0 |   0
 150 | 150
 250 | 250
 400 | 400
(4 rows)

-- with PHVs, partitionwise join selected but no join pushdown
EXPLAIN (COSTS OFF)
SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
                      QUERY PLAN                      
------------------------------------------------------
 Sort
   Sort Key: ftprt1_p1.a, ftprt2_p1.b
   ->  Append
         ->  Hash Full Join
               Hash Cond: (ftprt1_p1.a = ftprt2_p1.b)
               ->  Foreign Scan on ftprt1_p1
               ->  Hash
                     ->  Foreign Scan on ftprt2_p1
         ->  Hash Full Join
               Hash Cond: (ftprt1_p2.a = ftprt2_p2.b)
               ->  Foreign Scan on ftprt1_p2
               ->  Hash
                     ->  Foreign Scan on ftprt2_p2
(13 rows)

SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
  a  |  phv   |  b  |  phv   
-----+--------+-----+--------
   0 | t1_phv |   0 | t2_phv
  50 | t1_phv |     | 
 100 | t1_phv |     | 
 150 | t1_phv | 150 | t2_phv
 200 | t1_phv |     | 
 250 | t1_phv | 250 | t2_phv
 300 | t1_phv |     | 
 350 | t1_phv |     | 
 400 | t1_phv | 400 | t2_phv
 450 | t1_phv |     | 
     |        |  75 | t2_phv
     |        | 225 | t2_phv
     |        | 325 | t2_phv
     |        | 475 | t2_phv
(14 rows)

-- test FOR UPDATE; partitionwise join does not apply
EXPLAIN (COSTS OFF)
SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1;
                          QUERY PLAN                          
--------------------------------------------------------------
 LockRows
   ->  Sort
         Sort Key: t1.a
         ->  Hash Join
               Hash Cond: (t2.b = t1.a)
               ->  Append
                     ->  Foreign Scan on ftprt2_p1 t2
                     ->  Foreign Scan on ftprt2_p2 t2_1
               ->  Hash
                     ->  Append
                           ->  Foreign Scan on ftprt1_p1 t1
                           ->  Foreign Scan on ftprt1_p2 t1_1
(12 rows)

SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1;
  a  |  b  
-----+-----
   0 |   0
 150 | 150
 250 | 250
 400 | 400
(4 rows)

RESET enable_partitionwise_join;
-- ===================================================================
-- test partitionwise aggregates
-- ===================================================================
CREATE TABLE pagg_tab (a int, b int, c text) PARTITION BY RANGE(a);
CREATE TABLE pagg_tab_p1 (LIKE pagg_tab);
CREATE TABLE pagg_tab_p2 (LIKE pagg_tab);
CREATE TABLE pagg_tab_p3 (LIKE pagg_tab);
INSERT INTO pagg_tab_p1 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 10;
INSERT INTO pagg_tab_p2 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 20 and (i % 30) >= 10;
INSERT INTO pagg_tab_p3 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 30 and (i % 30) >= 20;
-- Create foreign partitions
CREATE FOREIGN TABLE fpagg_tab_p1 PARTITION OF pagg_tab FOR VALUES FROM (0) TO (10) SERVER loopback OPTIONS (table_name 'pagg_tab_p1');
CREATE FOREIGN TABLE fpagg_tab_p2 PARTITION OF pagg_tab FOR VALUES FROM (10) TO (20) SERVER loopback OPTIONS (table_name 'pagg_tab_p2');;
CREATE FOREIGN TABLE fpagg_tab_p3 PARTITION OF pagg_tab FOR VALUES FROM (20) TO (30) SERVER loopback OPTIONS (table_name 'pagg_tab_p3');;
ANALYZE pagg_tab;
ANALYZE fpagg_tab_p1;
ANALYZE fpagg_tab_p2;
ANALYZE fpagg_tab_p3;
-- When GROUP BY clause matches with PARTITION KEY.
-- Plan with partitionwise aggregates is disabled
SET enable_partitionwise_aggregate TO false;
EXPLAIN (COSTS OFF)
SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1;
                      QUERY PLAN                       
-------------------------------------------------------
 Sort
   Sort Key: fpagg_tab_p1.a
   ->  HashAggregate
         Group Key: fpagg_tab_p1.a
         Filter: (avg(fpagg_tab_p1.b) < '22'::numeric)
         ->  Append
               ->  Foreign Scan on fpagg_tab_p1
               ->  Foreign Scan on fpagg_tab_p2
               ->  Foreign Scan on fpagg_tab_p3
(9 rows)

-- Plan with partitionwise aggregates is enabled
SET enable_partitionwise_aggregate TO true;
EXPLAIN (COSTS OFF)
SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1;
                              QUERY PLAN                              
----------------------------------------------------------------------
 Sort
   Sort Key: fpagg_tab_p1.a
   ->  Append
         ->  Foreign Scan
               Relations: Aggregate on (public.fpagg_tab_p1 pagg_tab)
         ->  Foreign Scan
               Relations: Aggregate on (public.fpagg_tab_p2 pagg_tab)
         ->  Foreign Scan
               Relations: Aggregate on (public.fpagg_tab_p3 pagg_tab)
(9 rows)

SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1;
 a  | sum  | min | count 
----+------+-----+-------
  0 | 2000 |   0 |   100
  1 | 2100 |   1 |   100
 10 | 2000 |   0 |   100
 11 | 2100 |   1 |   100
 20 | 2000 |   0 |   100
 21 | 2100 |   1 |   100
(6 rows)

-- Check with whole-row reference
-- Should have all the columns in the target list for the given relation
EXPLAIN (VERBOSE, COSTS OFF)
SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1;
                               QUERY PLAN                               
------------------------------------------------------------------------
 Sort
   Output: t1.a, (count(((t1.*)::pagg_tab)))
   Sort Key: t1.a
   ->  Append
         ->  HashAggregate
               Output: t1.a, count(((t1.*)::pagg_tab))
               Group Key: t1.a
               Filter: (avg(t1.b) < '22'::numeric)
               ->  Foreign Scan on public.fpagg_tab_p1 t1
                     Output: t1.a, t1.*, t1.b
                     Remote SQL: SELECT a, b, c FROM public.pagg_tab_p1
         ->  HashAggregate
               Output: t1_1.a, count(((t1_1.*)::pagg_tab))
               Group Key: t1_1.a
               Filter: (avg(t1_1.b) < '22'::numeric)
               ->  Foreign Scan on public.fpagg_tab_p2 t1_1
                     Output: t1_1.a, t1_1.*, t1_1.b
                     Remote SQL: SELECT a, b, c FROM public.pagg_tab_p2
         ->  HashAggregate
               Output: t1_2.a, count(((t1_2.*)::pagg_tab))
               Group Key: t1_2.a
               Filter: (avg(t1_2.b) < '22'::numeric)
               ->  Foreign Scan on public.fpagg_tab_p3 t1_2
                     Output: t1_2.a, t1_2.*, t1_2.b
                     Remote SQL: SELECT a, b, c FROM public.pagg_tab_p3
(25 rows)

SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1;
 a  | count 
----+-------
  0 |   100
  1 |   100
 10 |   100
 11 |   100
 20 |   100
 21 |   100
(6 rows)

-- When GROUP BY clause does not match with PARTITION KEY.
EXPLAIN (COSTS OFF)
SELECT b, avg(a), max(a), count(*) FROM pagg_tab GROUP BY b HAVING sum(a) < 700 ORDER BY 1;
                      QUERY PLAN                      
------------------------------------------------------
 Sort
   Sort Key: fpagg_tab_p1.b
   ->  Finalize HashAggregate
         Group Key: fpagg_tab_p1.b
         Filter: (sum(fpagg_tab_p1.a) < 700)
         ->  Append
               ->  Partial HashAggregate
                     Group Key: fpagg_tab_p1.b
                     ->  Foreign Scan on fpagg_tab_p1
               ->  Partial HashAggregate
                     Group Key: fpagg_tab_p2.b
                     ->  Foreign Scan on fpagg_tab_p2
               ->  Partial HashAggregate
                     Group Key: fpagg_tab_p3.b
                     ->  Foreign Scan on fpagg_tab_p3
(15 rows)

-- Clean-up
RESET enable_partitionwise_aggregate;