--
-- REPLICATE_REF_TABLES_ON_COORDINATOR
--
CREATE SCHEMA replicate_ref_to_coordinator;
SET search_path TO 'replicate_ref_to_coordinator';
SET citus.shard_replication_factor TO 1;
SET citus.shard_count TO 4;
SET citus.next_shard_id TO 8000000;
SET citus.next_placement_id TO 8000000;
--- enable logging to see which tasks are executed locally
SET citus.log_local_commands TO ON;
CREATE TABLE squares(a int, b int);
SELECT create_reference_table('squares');
 create_reference_table
---------------------------------------------------------------------

(1 row)

INSERT INTO squares SELECT i, i * i FROM generate_series(1, 10) i;
-- should be executed locally
SELECT count(*) FROM squares;
NOTICE:  executing the command locally: SELECT count(*) AS count FROM replicate_ref_to_coordinator.squares_8000000 squares
 count
---------------------------------------------------------------------
    10
(1 row)

-- create a second reference table
CREATE TABLE numbers(a int);
SELECT create_reference_table('numbers');
 create_reference_table
---------------------------------------------------------------------

(1 row)

INSERT INTO numbers VALUES (20), (21);
NOTICE:  executing the command locally: INSERT INTO replicate_ref_to_coordinator.numbers_8000001 AS citus_table_alias (a) VALUES (20), (21)
-- INSERT ... SELECT between reference tables
BEGIN;
EXPLAIN INSERT INTO squares SELECT a, a*a FROM numbers;
                                          QUERY PLAN
---------------------------------------------------------------------
 Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0)
   Task Count: 1
   Tasks Shown: All
   ->  Task
         Node: host=localhost port=xxxxx dbname=regression
         ->  Insert on squares_8000000 citus_table_alias  (cost=0.00..41.88 rows=2550 width=8)
               ->  Seq Scan on numbers_8000001 numbers  (cost=0.00..41.88 rows=2550 width=8)
(7 rows)

INSERT INTO squares SELECT a, a*a FROM numbers;
SELECT * FROM squares WHERE a >= 20 ORDER BY a;
 a  |  b
---------------------------------------------------------------------
 20 | 400
 21 | 441
(2 rows)

ROLLBACK;
BEGIN;
EXPLAIN INSERT INTO numbers SELECT a FROM squares WHERE a < 3;
                                          QUERY PLAN
---------------------------------------------------------------------
 Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0)
   Task Count: 1
   Tasks Shown: All
   ->  Task
         Node: host=localhost port=xxxxx dbname=regression
         ->  Insert on numbers_8000001 citus_table_alias  (cost=0.00..38.25 rows=753 width=4)
               ->  Seq Scan on squares_8000000 squares  (cost=0.00..38.25 rows=753 width=4)
                     Filter: (a < 3)
(8 rows)

INSERT INTO numbers SELECT a FROM squares WHERE a < 3;
SELECT * FROM numbers ORDER BY a;
 a
---------------------------------------------------------------------
  1
  2
 20
 21
(4 rows)

ROLLBACK;
-- Make sure we hide shard tables ...
SELECT citus_table_is_visible('numbers_8000001'::regclass::oid);
 citus_table_is_visible
---------------------------------------------------------------------
 f
(1 row)

-- Join between reference tables and local tables
CREATE TABLE local_table(a int);
INSERT INTO local_table VALUES (2), (4), (7), (20);
EXPLAIN SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers;
                                  QUERY PLAN
---------------------------------------------------------------------
 Merge Join  (cost=359.57..860.00 rows=32512 width=8)
   Merge Cond: (local_table.a = numbers_8000001.a)
   ->  Sort  (cost=179.78..186.16 rows=2550 width=4)
         Sort Key: local_table.a
         ->  Seq Scan on local_table  (cost=0.00..35.50 rows=2550 width=4)
   ->  Sort  (cost=179.78..186.16 rows=2550 width=4)
         Sort Key: numbers_8000001.a
         ->  Seq Scan on numbers_8000001  (cost=0.00..35.50 rows=2550 width=4)
(8 rows)

SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers ORDER BY 1;
 a  | a
---------------------------------------------------------------------
 20 | 20
(1 row)

-- test non equijoin
SELECT lt.a, sq.a, sq.b
FROM local_table lt
JOIN squares sq ON sq.a > lt.a and sq.b > 90
ORDER BY 1,2,3;
 a | a  |  b
---------------------------------------------------------------------
 2 | 10 | 100
 4 | 10 | 100
 7 | 10 | 100
(3 rows)

-- error if in transaction block
BEGIN;
SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers ORDER BY 1;
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
ROLLBACK;
-- error if in a DO block
DO $$
BEGIN
	PERFORM local_table.a, numbers.a FROM local_table NATURAL JOIN numbers;
END;
$$;
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
CONTEXT:  SQL statement "SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers"
PL/pgSQL function inline_code_block line 3 at PERFORM
-- test plpgsql function
CREATE FUNCTION test_reference_local_join_plpgsql_func()
RETURNS void AS $$
BEGIN
	INSERT INTO local_table VALUES (21);
	INSERT INTO numbers VALUES (4);
	PERFORM local_table.a, numbers.a FROM local_table NATURAL JOIN numbers ORDER BY 1;
	RAISE EXCEPTION '';
	PERFORM local_table.a, numbers.a FROM local_table NATURAL JOIN numbers ORDER BY 1;
END;
$$ LANGUAGE plpgsql;
SELECT test_reference_local_join_plpgsql_func();
NOTICE:  executing the command locally: INSERT INTO replicate_ref_to_coordinator.numbers_8000001 (a) VALUES (4)
CONTEXT:  SQL statement "INSERT INTO numbers VALUES (4)"
PL/pgSQL function test_reference_local_join_plpgsql_func() line 4 at SQL statement
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
CONTEXT:  SQL statement "SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers ORDER BY 1"
PL/pgSQL function test_reference_local_join_plpgsql_func() line 5 at PERFORM
SELECT sum(a) FROM local_table;
 sum
---------------------------------------------------------------------
  33
(1 row)

SELECT sum(a) FROM numbers;
NOTICE:  executing the command locally: SELECT sum(a) AS sum FROM replicate_ref_to_coordinator.numbers_8000001 numbers
 sum
---------------------------------------------------------------------
  41
(1 row)

-- error if in procedure's subtransaction
CREATE PROCEDURE test_reference_local_join_proc() AS $$
SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers ORDER BY 1;
$$ LANGUAGE sql;
CALL test_reference_local_join_proc();
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
CONTEXT:  SQL function "test_reference_local_join_proc" statement 1
-- error if in a transaction block even if reference table is not in search path
CREATE SCHEMA s1;
CREATE TABLE s1.ref(a int);
SELECT create_reference_table('s1.ref');
 create_reference_table
---------------------------------------------------------------------

(1 row)

BEGIN;
SELECT local_table.a, r.a FROM local_table NATURAL JOIN s1.ref r ORDER BY 1;
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
ROLLBACK;
BEGIN;
WITH t1 AS (
	SELECT random() r, a FROM local_table
) SELECT count(*) FROM t1, numbers WHERE t1.a = numbers.a AND r < 0.5;
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
END;
BEGIN;
WITH t1 AS (
	SELECT random() r, a FROM numbers
) SELECT count(*) FROM t1, local_table WHERE t1.a = local_table.a AND r < 0.5;
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
END;
BEGIN;
SELECT count(*) FROM local_table
WHERE EXISTS(SELECT random() FROM numbers WHERE local_table.a = numbers.a);
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
END;
BEGIN;
SELECT count(*) FROM numbers
WHERE EXISTS(SELECT random() FROM local_table WHERE local_table.a = numbers.a);
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
END;
DROP SCHEMA s1 CASCADE;
NOTICE:  drop cascades to 2 other objects
DETAIL:  drop cascades to table s1.ref
drop cascades to table s1.ref_8000002
-- error if inside a SQL UDF call
CREATE or replace FUNCTION test_reference_local_join_func()
RETURNS SETOF RECORD AS $$
SET LOCAL citus.enable_local_execution to false;
INSERT INTO numbers VALUES (2);
SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers ORDER BY 1;
$$ LANGUAGE sql;
SELECT test_reference_local_join_func();
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
CONTEXT:  SQL function "test_reference_local_join_func" statement 3
-- shouldn't plan locally if modifications happen in CTEs, ...
WITH ins AS (INSERT INTO numbers VALUES (1) RETURNING *)
SELECT * FROM numbers, local_table;
ERROR:  relation local_table is not distributed
WITH t AS (SELECT *, random() x FROM numbers FOR UPDATE)
SELECT * FROM numbers, local_table
WHERE EXISTS (SELECT * FROM t WHERE t.x = numbers.a);
ERROR:  relation local_table is not distributed
-- but this should be fine
WITH t AS (SELECT *, random() x FROM numbers)
SELECT * FROM numbers, local_table
WHERE EXISTS (SELECT * FROM t WHERE t.x = numbers.a);
 a | a
---------------------------------------------------------------------
(0 rows)

-- shouldn't plan locally even if distributed table is in CTE or subquery
CREATE TABLE dist(a int);
SELECT create_distributed_table('dist', 'a');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

INSERT INTO dist VALUES (20),(30);
WITH t AS (SELECT *, random() x FROM dist)
SELECT * FROM numbers, local_table
WHERE EXISTS (SELECT * FROM t WHERE t.x = numbers.a);
ERROR:  relation local_table is not distributed
-- test CTE being reference/local join for distributed query
WITH t as (SELECT n.a, random() x FROM numbers n NATURAL JOIN local_table l)
SELECT a FROM t NATURAL JOIN dist;
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
 -- error if FOR UPDATE/FOR SHARE
SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers FOR SHARE;
ERROR:  could not run distributed query with FOR UPDATE/SHARE commands
HINT:  Consider using an equality filter on the distributed table's partition column.
SELECT local_table.a, numbers.a FROM local_table NATURAL JOIN numbers FOR UPDATE;
ERROR:  could not run distributed query with FOR UPDATE/SHARE commands
HINT:  Consider using an equality filter on the distributed table's partition column.
--
-- Joins between reference tables and views shouldn't be planned locally.
--
CREATE VIEW numbers_v AS SELECT * FROM numbers WHERE a=1;
SELECT public.coordinator_plan($Q$
EXPLAIN (COSTS FALSE)
	SELECT * FROM squares JOIN numbers_v ON squares.a = numbers_v.a;
$Q$);
       coordinator_plan
---------------------------------------------------------------------
 Custom Scan (Citus Adaptive)
   Task Count: 1
(2 rows)

CREATE VIEW local_table_v AS SELECT * FROM local_table WHERE a BETWEEN 1 AND 10;
SELECT public.coordinator_plan($Q$
EXPLAIN (COSTS FALSE)
	SELECT * FROM squares JOIN local_table_v ON squares.a = local_table_v.a;
$Q$);
                coordinator_plan
---------------------------------------------------------------------
 Custom Scan (Citus Adaptive)
   ->  Distributed Subplan XXX_1
         ->  Seq Scan on local_table
               Filter: ((a >= 1) AND (a <= 10))
   Task Count: 1
(5 rows)

DROP VIEW numbers_v, local_table_v;
--
-- Joins between reference tables and materialized views are allowed to
-- be planned locally.
--
CREATE MATERIALIZED VIEW numbers_v AS SELECT * FROM numbers WHERE a BETWEEN 1 AND 10;
NOTICE:  executing the command locally: SELECT a FROM replicate_ref_to_coordinator.numbers_8000001 numbers WHERE ((a OPERATOR(pg_catalog.>=) 1) AND (a OPERATOR(pg_catalog.<=) 10))
REFRESH MATERIALIZED VIEW numbers_v;
SELECT public.plan_is_distributed($Q$
EXPLAIN (COSTS FALSE)
	SELECT * FROM squares JOIN numbers_v ON squares.a = numbers_v.a;
$Q$);
 plan_is_distributed
---------------------------------------------------------------------
 f
(1 row)

BEGIN;
SELECT * FROM squares JOIN numbers_v ON squares.a = numbers_v.a;
ERROR:  cannot join local tables and reference tables in a transaction block, udf block, or distributed CTE subquery
END;
--
-- Joins between reference tables, local tables, and function calls shouldn't
-- be planned locally.
--
SELECT count(*)
FROM local_table a, numbers b, generate_series(1, 10) c
WHERE a.a = b.a AND a.a = c;
ERROR:  relation local_table is not distributed
-- but it should be okay if the function call is not a data source
SELECT public.plan_is_distributed($Q$
EXPLAIN (COSTS FALSE)
SELECT abs(a.a) FROM local_table a, numbers b WHERE a.a = b.a;
$Q$);
 plan_is_distributed
---------------------------------------------------------------------
 f
(1 row)

SELECT public.plan_is_distributed($Q$
EXPLAIN (COSTS FALSE)
SELECT a.a FROM local_table a, numbers b WHERE a.a = b.a ORDER BY abs(a.a);
$Q$);
 plan_is_distributed
---------------------------------------------------------------------
 f
(1 row)

-- verify that we can drop columns from reference tables replicated to the coordinator
-- see https://github.com/citusdata/citus/issues/3279
ALTER TABLE squares DROP COLUMN b;
-- clean-up
SET client_min_messages TO ERROR;
DROP SCHEMA replicate_ref_to_coordinator CASCADE;
-- Make sure the shard was dropped
SELECT 'numbers_8000001'::regclass::oid;
ERROR:  relation "numbers_8000001" does not exist
SET search_path TO DEFAULT;
RESET client_min_messages;
