--
-- MULTI_TRUNCATE
--
SET citus.next_shard_id TO 1210000;
CREATE SCHEMA multi_truncate;
SET search_path TO multi_truncate;
-- helper view that prints out local table names and sizes in the schema
CREATE VIEW table_sizes AS
SELECT
  c.relname as name,
  pg_catalog.pg_table_size(c.oid) > 0 as has_data
FROM pg_catalog.pg_class c
     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
      AND n.nspname = 'multi_truncate'
ORDER BY 1;
--
-- truncate for append distribution
-- expect all shards to be dropped
--
CREATE TABLE test_truncate_append(a int);
SELECT create_distributed_table('test_truncate_append', 'a', 'append');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

-- verify no error is thrown when no shards are present
TRUNCATE TABLE test_truncate_append;
SELECT master_create_empty_shard('test_truncate_append') AS new_shard_id \gset
UPDATE pg_dist_shard SET shardminvalue = 1, shardmaxvalue = 500
WHERE shardid = :new_shard_id;
SELECT count(*) FROM test_truncate_append;
 count
---------------------------------------------------------------------
     0
(1 row)

INSERT INTO test_truncate_append values (1);
SELECT count(*) FROM test_truncate_append;
 count
---------------------------------------------------------------------
     1
(1 row)

-- create some more shards
SELECT master_create_empty_shard('test_truncate_append');
 master_create_empty_shard
---------------------------------------------------------------------
                   1210001
(1 row)

SELECT master_create_empty_shard('test_truncate_append');
 master_create_empty_shard
---------------------------------------------------------------------
                   1210002
(1 row)

-- verify 3 shards are presents
SELECT shardid FROM pg_dist_shard where logicalrelid = 'test_truncate_append'::regclass ORDER BY shardid;
 shardid
---------------------------------------------------------------------
 1210000
 1210001
 1210002
(3 rows)

TRUNCATE TABLE test_truncate_append;
-- verify data is truncated from the table
SELECT count(*) FROM test_truncate_append;
 count
---------------------------------------------------------------------
     0
(1 row)

-- verify no shard exists anymore
SELECT shardid FROM pg_dist_shard where logicalrelid = 'test_truncate_append'::regclass;
 shardid
---------------------------------------------------------------------
(0 rows)

-- command can run inside transaction
BEGIN; TRUNCATE TABLE test_truncate_append; COMMIT;
DROP TABLE test_truncate_append;
--
-- truncate for range distribution
-- expect shard to be present, data to be truncated
--
CREATE TABLE test_truncate_range(a int);
SELECT create_distributed_table('test_truncate_range', 'a', 'range');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

-- verify no error is thrown when no shards are present
TRUNCATE TABLE test_truncate_range;
SELECT master_create_empty_shard('test_truncate_range') AS new_shard_id \gset
UPDATE pg_dist_shard SET shardminvalue = 1, shardmaxvalue = 500
WHERE shardid = :new_shard_id;
SELECT master_create_empty_shard('test_truncate_range') AS new_shard_id \gset
UPDATE pg_dist_shard SET shardminvalue = 501, shardmaxvalue = 1500
WHERE shardid = :new_shard_id;
SELECT master_create_empty_shard('test_truncate_range') AS new_shard_id \gset
UPDATE pg_dist_shard SET shardminvalue = 1501, shardmaxvalue = 2500
WHERE shardid = :new_shard_id;
SELECT count(*) FROM test_truncate_range;
 count
---------------------------------------------------------------------
     0
(1 row)

INSERT INTO test_truncate_range values (1);
INSERT INTO test_truncate_range values (1001);
INSERT INTO test_truncate_range values (2000);
INSERT INTO test_truncate_range values (100);
SELECT count(*) FROM test_truncate_range;
 count
---------------------------------------------------------------------
     4
(1 row)

-- verify 3 shards are presents
SELECT shardid FROM pg_dist_shard where logicalrelid = 'test_truncate_range'::regclass ORDER BY shardid;
 shardid
---------------------------------------------------------------------
 1210003
 1210004
 1210005
(3 rows)

TRUNCATE TABLE test_truncate_range;
-- verify data is truncated from the table
SELECT count(*) FROM test_truncate_range;
 count
---------------------------------------------------------------------
     0
(1 row)

-- verify 3 shards are still present
SELECT shardid FROM pg_dist_shard where logicalrelid = 'test_truncate_range'::regclass ORDER BY shardid;
 shardid
---------------------------------------------------------------------
 1210003
 1210004
 1210005
(3 rows)

-- verify that truncate can be aborted
INSERT INTO test_truncate_range VALUES (1);
BEGIN; TRUNCATE TABLE test_truncate_range; ROLLBACK;
SELECT count(*) FROM test_truncate_range;
 count
---------------------------------------------------------------------
     1
(1 row)

DROP TABLE test_truncate_range;
--
-- truncate for hash distribution.
-- expect shard to be present, data to be truncated
--
CREATE TABLE test_truncate_hash(a int);
SELECT create_distributed_table('test_truncate_hash', 'a', 'hash');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

INSERT INTO test_truncate_hash values (1);
INSERT INTO test_truncate_hash values (1001);
INSERT INTO test_truncate_hash values (2000);
INSERT INTO test_truncate_hash values (100);
-- verify that truncate can be aborted
INSERT INTO test_truncate_hash VALUES (1);
BEGIN; TRUNCATE TABLE test_truncate_hash; ROLLBACK;
SELECT count(*) FROM test_truncate_hash;
 count
---------------------------------------------------------------------
     5
(1 row)

DROP TABLE test_truncate_hash;
-- test with table with spaces in it
SET citus.shard_replication_factor TO 1;
CREATE TABLE "a b hash" (a int, b int);
SELECT create_distributed_table('"a b hash"', 'a', 'hash');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

INSERT INTO "a b hash" values (1, 0);
SELECT * from "a b hash";
 a | b
---------------------------------------------------------------------
 1 | 0
(1 row)

TRUNCATE TABLE "a b hash";
SELECT * from "a b hash";
 a | b
---------------------------------------------------------------------
(0 rows)

DROP TABLE "a b hash";
-- now with append
CREATE TABLE "a b append" (a int, b int);
SELECT create_distributed_table('"a b append"', 'a', 'append');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

SELECT master_create_empty_shard('"a b append"') AS new_shard_id \gset
UPDATE pg_dist_shard SET shardminvalue = 1, shardmaxvalue = 500
WHERE shardid = :new_shard_id;
SELECT master_create_empty_shard('"a b append"') AS new_shard_id \gset
UPDATE pg_dist_shard SET shardminvalue = 501, shardmaxvalue = 1000
WHERE shardid = :new_shard_id;
INSERT INTO "a b append" values (1, 1);
INSERT INTO "a b append" values (600, 600);
SELECT * FROM "a b append" ORDER BY a;
  a  |  b
---------------------------------------------------------------------
   1 |   1
 600 | 600
(2 rows)

TRUNCATE TABLE "a b append";
-- verify all shards are dropped
SELECT shardid FROM pg_dist_shard where logicalrelid = '"a b append"'::regclass;
 shardid
---------------------------------------------------------------------
(0 rows)

DROP TABLE "a b append";
-- Truncate local data only
CREATE TABLE test_local_truncate (x int, y int);
INSERT INTO test_local_truncate VALUES (1,2);
SELECT create_distributed_table('test_local_truncate', 'x', colocate_with => 'none');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$multi_truncate.test_local_truncate$$)
 create_distributed_table
---------------------------------------------------------------------

(1 row)

BEGIN;
SET LOCAL citus.enable_ddl_propagation TO off;
TRUNCATE test_local_truncate;
COMMIT;
-- Ensure distributed data is not truncated
SELECT * FROM test_local_truncate;
 x | y
---------------------------------------------------------------------
 1 | 2
(1 row)

-- Undistribute table
SELECT citus_drop_all_shards('test_local_truncate', 'public', 'test_local_truncate');
 citus_drop_all_shards
---------------------------------------------------------------------
                     4
(1 row)

CREATE TABLE temp_pg_dist_partition_row AS SELECT * FROM pg_dist_partition WHERE logicalrelid = 'test_local_truncate'::regclass;
DELETE FROM pg_dist_partition WHERE logicalrelid = 'test_local_truncate'::regclass;
-- Ensure local data is truncated
SELECT * FROM test_local_truncate;
 x | y
---------------------------------------------------------------------
(0 rows)

INSERT INTO pg_dist_partition SELECT * FROM temp_pg_dist_partition_row;
DROP TABLE test_local_truncate;
DROP TABLE temp_pg_dist_partition_row;
-- Truncate local data, but roll back
CREATE TABLE test_local_truncate (x int, y int);
INSERT INTO test_local_truncate VALUES (1,2);
SELECT create_distributed_table('test_local_truncate', 'x', colocate_with => 'none');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$multi_truncate.test_local_truncate$$)
 create_distributed_table
---------------------------------------------------------------------

(1 row)

BEGIN;
SET LOCAL citus.enable_ddl_propagation TO off;
TRUNCATE test_local_truncate;
ROLLBACK;
-- Ensure distributed data is not truncated
SELECT * FROM test_local_truncate;
 x | y
---------------------------------------------------------------------
 1 | 2
(1 row)

-- Undistribute table
SELECT citus_drop_all_shards('test_local_truncate', 'public', 'test_local_truncate');
 citus_drop_all_shards
---------------------------------------------------------------------
                     4
(1 row)

DELETE FROM pg_dist_partition WHERE logicalrelid = 'test_local_truncate'::regclass;
-- Ensure local data is not truncated
SELECT * FROM test_local_truncate;
 x | y
---------------------------------------------------------------------
 1 | 2
(1 row)

DROP TABLE test_local_truncate;
-- Test truncate_local_data_after_distributing_table UDF
CREATE TABLE referenced_table(id int UNIQUE, test_column int);
INSERT INTO referenced_table SELECT x,x FROM generate_series(1,10000) x;
CREATE TABLE referencing_table(id int, ref_id int REFERENCES referenced_table(id));
INSERT INTO referencing_table SELECT * FROM referenced_table;
-- The following will fail as the table is not distributed
SELECT truncate_local_data_after_distributing_table('referenced_table');
ERROR:  supplied parameter is not a distributed relation
DETAIL:  This UDF only truncates local records of distributed tables.
-- Test foreign keys from local tables to distributed tables
-- We can not truncate local tables until all the local foreign keys are removed.
SELECT create_distributed_table('referenced_table', 'id');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$multi_truncate.referenced_table$$)
 create_distributed_table
---------------------------------------------------------------------

(1 row)

-- The following will fail, as local referencing_table has fk references
SELECT truncate_local_data_after_distributing_table('referenced_table');
ERROR:  cannot truncate a table referenced in a foreign key constraint by a local table
DETAIL:  Table "referencing_table" references "referenced_table"
-- Test foreign keys between distributed tables
SELECT create_distributed_table('referencing_table', 'ref_id');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$multi_truncate.referencing_table$$)
 create_distributed_table
---------------------------------------------------------------------

(1 row)

BEGIN;
-- The following will no longer fail as the referencing table is now distributed
SELECT truncate_local_data_after_distributing_table('referenced_table');
NOTICE:  truncate cascades to table "referencing_table"
 truncate_local_data_after_distributing_table
---------------------------------------------------------------------

(1 row)

SELECT * FROM table_sizes;
       name        | has_data
---------------------------------------------------------------------
 referenced_table  | f
 referencing_table | f
(2 rows)

ROLLBACK;
-- observe that none of the tables are truncated
SELECT * FROM table_sizes;
       name        | has_data
---------------------------------------------------------------------
 referenced_table  | t
 referencing_table | t
(2 rows)

-- test that if we truncate the referencing table, only said table is affected
BEGIN;
SELECT truncate_local_data_after_distributing_table('referencing_table');
 truncate_local_data_after_distributing_table
---------------------------------------------------------------------

(1 row)

SELECT * FROM table_sizes;
       name        | has_data
---------------------------------------------------------------------
 referenced_table  | t
 referencing_table | f
(2 rows)

ROLLBACK;
-- however if we truncate referenced table, both of the tables get truncated
-- because we supply the CASCADE option
-- test that if we truncate the referencing table, only said table is affected
BEGIN;
SELECT truncate_local_data_after_distributing_table('referenced_table');
NOTICE:  truncate cascades to table "referencing_table"
 truncate_local_data_after_distributing_table
---------------------------------------------------------------------

(1 row)

SELECT * FROM table_sizes;
       name        | has_data
---------------------------------------------------------------------
 referenced_table  | f
 referencing_table | f
(2 rows)

ROLLBACK;
DROP TABLE referencing_table;
DROP TABLE referenced_table;
-- test truncating reference tables
CREATE TABLE ref(id int UNIQUE, data int);
INSERT INTO ref SELECT x,x FROM generate_series(1,10000) x;
SELECT create_reference_table('ref');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$multi_truncate.ref$$)
 create_reference_table
---------------------------------------------------------------------

(1 row)

CREATE TABLE dist(id int, ref_id int REFERENCES ref(id));
INSERT INTO dist SELECT x,x FROM generate_series(1,10000) x;
-- test that we do not cascade truncates to local referencing tables
SELECT truncate_local_data_after_distributing_table('ref');
NOTICE:  truncate cascades to table "dist"
 truncate_local_data_after_distributing_table
---------------------------------------------------------------------

(1 row)

-- test that we allow distributing tables that have foreign keys to reference tables
SELECT create_distributed_table('dist','id');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$multi_truncate.dist$$)
 create_distributed_table
---------------------------------------------------------------------

(1 row)

SHOW citus.multi_shard_modify_mode;
 citus.multi_shard_modify_mode
---------------------------------------------------------------------
 parallel
(1 row)

-- the following should truncate ref and dist
BEGIN;
SELECT truncate_local_data_after_distributing_table('ref');
NOTICE:  truncate cascades to table "dist"
 truncate_local_data_after_distributing_table
---------------------------------------------------------------------

(1 row)

SELECT * FROM table_sizes;
    name     | has_data
---------------------------------------------------------------------
 dist        | f
 ref         | f
 ref_1210032 | t
(3 rows)

ROLLBACK;
-- the following should truncate dist table only
BEGIN;
SELECT truncate_local_data_after_distributing_table('dist');
 truncate_local_data_after_distributing_table
---------------------------------------------------------------------

(1 row)

SELECT * FROM table_sizes;
    name     | has_data
---------------------------------------------------------------------
 dist        | f
 ref         | f
 ref_1210032 | t
(3 rows)

ROLLBACK;
DROP TABLE ref, dist;
-- tests for issue 1770
CREATE TABLE t1(a int, b int);
INSERT INTO t1 VALUES(1,1);
SELECT create_distributed_table('t1', 'a');
NOTICE:  Copying data from local table...
NOTICE:  copying the data has completed
DETAIL:  The local data in the table is no longer visible, but is still on disk.
HINT:  To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$multi_truncate.t1$$)
 create_distributed_table
---------------------------------------------------------------------

(1 row)

ALTER TABLE t1 ADD CONSTRAINT t1_a_check CHECK(a > 2) NOT VALID;
-- will error out with
-- "ERROR:  CHECK CONSTRAINT "t1_a_check" of relation "t1" is violated by some row"
ALTER TABLE t1 VALIDATE CONSTRAINT t1_a_check;
ERROR:  check constraint "t1_a_check" of relation "t1" is violated by some row
-- remove violating row
DELETE FROM t1 where a = 1;
-- verify no rows in t1
SELECT * FROM t1;
 a | b
---------------------------------------------------------------------
(0 rows)

-- this will still error out
ALTER TABLE t1 VALIDATE CONSTRAINT t1_a_check;
ERROR:  check constraint "t1_a_check" of relation "t1" is violated by some row
-- The check will pass when the local copies are truncated
SELECT truncate_local_data_after_distributing_table('t1');
 truncate_local_data_after_distributing_table
---------------------------------------------------------------------

(1 row)

ALTER TABLE t1 VALIDATE CONSTRAINT t1_a_check;
CREATE TABLE tbl(a INT);
SELECT create_distributed_table('tbl', 'a');
 create_distributed_table
---------------------------------------------------------------------

(1 row)

CREATE VIEW vw AS SELECT * FROM tbl;
-- throws an error because vw is not a table
TRUNCATE TABLE tbl, vw CASCADE;
ERROR:  "vw" is not a table
SET client_min_messages TO ERROR;
DROP VIEW table_sizes;
DROP TABLE t1;
DROP SCHEMA multi_truncate CASCADE;
