--
-- Testing ALTER TABLE on columnar tables.
--
CREATE SCHEMA columnar_alter;
SET search_path tO columnar_alter, public;
CREATE TABLE test_alter_table (a int, b int, c int) USING columnar;
WITH sample_data AS (VALUES
    (1, 2, 3),
    (4, 5, 6),
    (7, 8, 9)
)
INSERT INTO test_alter_table SELECT * FROM sample_data;
-- drop a column
ALTER TABLE test_alter_table DROP COLUMN a;
select
  version_major, version_minor, reserved_stripe_id, reserved_row_number
  from columnar_test_helpers.columnar_storage_info('test_alter_table');
 version_major | version_minor | reserved_stripe_id | reserved_row_number
---------------------------------------------------------------------
             2 |             0 |                  2 |              150001
(1 row)

-- test analyze
ANALYZE test_alter_table;
-- verify select queries run as expected
SELECT * FROM test_alter_table;
 b | c
---------------------------------------------------------------------
 2 | 3
 5 | 6
 8 | 9
(3 rows)

SELECT a FROM test_alter_table;
ERROR:  column "a" does not exist
SELECT b FROM test_alter_table;
 b
---------------------------------------------------------------------
 2
 5
 8
(3 rows)

-- verify insert runs as expected
INSERT INTO test_alter_table (SELECT 3, 5, 8);
ERROR:  INSERT has more expressions than target columns
INSERT INTO test_alter_table (SELECT 5, 8);
-- add a column with no defaults
ALTER TABLE test_alter_table ADD COLUMN d int;
SELECT * FROM test_alter_table;
 b | c | d
---------------------------------------------------------------------
 2 | 3 |
 5 | 6 |
 8 | 9 |
 5 | 8 |
(4 rows)

INSERT INTO test_alter_table (SELECT 3, 5, 8);
SELECT * FROM test_alter_table;
 b | c | d
---------------------------------------------------------------------
 2 | 3 |
 5 | 6 |
 8 | 9 |
 5 | 8 |
 3 | 5 | 8
(5 rows)

select
  version_major, version_minor, reserved_stripe_id, reserved_row_number
  from columnar_test_helpers.columnar_storage_info('test_alter_table');
 version_major | version_minor | reserved_stripe_id | reserved_row_number
---------------------------------------------------------------------
             2 |             0 |                  4 |              450001
(1 row)

-- add a fixed-length column with default value
ALTER TABLE test_alter_table ADD COLUMN e int default 3;
SELECT * from test_alter_table;
 b | c | d | e
---------------------------------------------------------------------
 2 | 3 |   | 3
 5 | 6 |   | 3
 8 | 9 |   | 3
 5 | 8 |   | 3
 3 | 5 | 8 | 3
(5 rows)

INSERT INTO test_alter_table (SELECT 1, 2, 4, 8);
SELECT * from test_alter_table;
 b | c | d | e
---------------------------------------------------------------------
 2 | 3 |   | 3
 5 | 6 |   | 3
 8 | 9 |   | 3
 5 | 8 |   | 3
 3 | 5 | 8 | 3
 1 | 2 | 4 | 8
(6 rows)

select
  version_major, version_minor, reserved_stripe_id, reserved_row_number
  from columnar_test_helpers.columnar_storage_info('test_alter_table');
 version_major | version_minor | reserved_stripe_id | reserved_row_number
---------------------------------------------------------------------
             2 |             0 |                  5 |              600001
(1 row)

-- add a variable-length column with default value
ALTER TABLE test_alter_table ADD COLUMN f text DEFAULT 'TEXT ME';
SELECT * from test_alter_table;
 b | c | d | e |    f
---------------------------------------------------------------------
 2 | 3 |   | 3 | TEXT ME
 5 | 6 |   | 3 | TEXT ME
 8 | 9 |   | 3 | TEXT ME
 5 | 8 |   | 3 | TEXT ME
 3 | 5 | 8 | 3 | TEXT ME
 1 | 2 | 4 | 8 | TEXT ME
(6 rows)

INSERT INTO test_alter_table (SELECT 1, 2, 4, 8, 'ABCDEF');
SELECT * from test_alter_table;
 b | c | d | e |    f
---------------------------------------------------------------------
 2 | 3 |   | 3 | TEXT ME
 5 | 6 |   | 3 | TEXT ME
 8 | 9 |   | 3 | TEXT ME
 5 | 8 |   | 3 | TEXT ME
 3 | 5 | 8 | 3 | TEXT ME
 1 | 2 | 4 | 8 | TEXT ME
 1 | 2 | 4 | 8 | ABCDEF
(7 rows)

-- drop couple of columns
ALTER TABLE test_alter_table DROP COLUMN c;
ALTER TABLE test_alter_table DROP COLUMN e;
ANALYZE test_alter_table;
SELECT * from test_alter_table;
 b | d |    f
---------------------------------------------------------------------
 2 |   | TEXT ME
 5 |   | TEXT ME
 8 |   | TEXT ME
 5 |   | TEXT ME
 3 | 8 | TEXT ME
 1 | 4 | TEXT ME
 1 | 4 | ABCDEF
(7 rows)

SELECT count(*) from test_alter_table;
 count
---------------------------------------------------------------------
     7
(1 row)

SELECT count(t.*) from test_alter_table t;
 count
---------------------------------------------------------------------
     7
(1 row)

-- unsupported default values
ALTER TABLE test_alter_table ADD COLUMN g boolean DEFAULT isfinite(current_date);
ALTER TABLE test_alter_table ADD COLUMN h DATE DEFAULT current_date;
SELECT * FROM test_alter_table;
ERROR:  unsupported default value for column "g"
HINT:  Expression is either mutable or does not evaluate to constant value
ALTER TABLE test_alter_table ALTER COLUMN g DROP DEFAULT;
SELECT * FROM test_alter_table;
ERROR:  unsupported default value for column "h"
HINT:  Expression is either mutable or does not evaluate to constant value
ALTER TABLE test_alter_table ALTER COLUMN h DROP DEFAULT;
ANALYZE test_alter_table;
SELECT * FROM test_alter_table;
 b | d |    f    | g | h
---------------------------------------------------------------------
 2 |   | TEXT ME |   |
 5 |   | TEXT ME |   |
 8 |   | TEXT ME |   |
 5 |   | TEXT ME |   |
 3 | 8 | TEXT ME |   |
 1 | 4 | TEXT ME |   |
 1 | 4 | ABCDEF  |   |
(7 rows)

-- unsupported type change
ALTER TABLE test_alter_table ADD COLUMN i int;
ALTER TABLE test_alter_table ADD COLUMN j float;
ALTER TABLE test_alter_table ADD COLUMN k text;
-- this is valid type change
ALTER TABLE test_alter_table ALTER COLUMN i TYPE float;
-- this is not valid
ALTER TABLE test_alter_table ALTER COLUMN j TYPE int;
-- text / varchar conversion is valid both ways
ALTER TABLE test_alter_table ALTER COLUMN k TYPE varchar(20);
ALTER TABLE test_alter_table ALTER COLUMN k TYPE text;
-- rename column
ALTER TABLE test_alter_table RENAME COLUMN k TO k_renamed;
-- rename table
ALTER TABLE test_alter_table RENAME TO test_alter_table_renamed;
DROP TABLE test_alter_table_renamed;
-- https://github.com/citusdata/citus/issues/4602
create domain str_domain as text not null;
create table domain_test (a int, b int) using columnar;
insert into domain_test values (1, 2);
insert into domain_test values (1, 2);
-- the following should error out since the domain is not nullable
alter table domain_test add column c str_domain;
ERROR:  domain str_domain does not allow null values
-- but this should succeed
alter table domain_test add column c str_domain DEFAULT 'x';
SELECT * FROM domain_test;
 a | b | c
---------------------------------------------------------------------
 1 | 2 | x
 1 | 2 | x
(2 rows)

-- similar to "add column c str_domain DEFAULT 'x'", both were getting
-- stucked before fixing https://github.com/citusdata/citus/issues/5164
BEGIN;
  ALTER TABLE domain_test ADD COLUMN d INT DEFAULT random();
ROLLBACK;
BEGIN;
  ALTER TABLE domain_test ADD COLUMN d SERIAL;
  SELECT * FROM domain_test ORDER BY 1,2,3,4;
 a | b | c | d
---------------------------------------------------------------------
 1 | 2 | x | 1
 1 | 2 | x | 2
(2 rows)

ROLLBACK;
set default_table_access_method TO 'columnar';
CREATE TABLE has_volatile AS
SELECT * FROM generate_series(1,10) id;
ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int;
SELECT id, col4 < 10000 FROM has_volatile ORDER BY id;
 id | ?column?
---------------------------------------------------------------------
  1 | t
  2 | t
  3 | t
  4 | t
  5 | t
  6 | t
  7 | t
  8 | t
  9 | t
 10 | t
(10 rows)

-- https://github.com/citusdata/citus/issues/4601
CREATE TABLE itest13 (a int) using columnar;
INSERT INTO itest13 VALUES (1), (2), (3);
ALTER TABLE itest13 ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY;
SELECT * FROM itest13 ORDER BY a;
 a | c
---------------------------------------------------------------------
 1 | 1
 2 | 2
 3 | 3
(3 rows)

create table atacc1 (a int) using columnar;
insert into atacc1 values(1);
-- should error out. It previously crashed.
alter table atacc1
  add column b float8 not null default random(),
  add primary key(a);
-- Add a generate column with an expression value
create table test_gen_ex (x int) using columnar;
INSERT INTO test_gen_ex VALUES (1), (2), (3);
ALTER TABLE test_gen_ex ADD COLUMN y int generated always as (x+1) stored;
SELECT * FROM test_gen_ex;
 x | y
---------------------------------------------------------------------
 1 | 2
 2 | 3
 3 | 4
(3 rows)

-- check removing all columns while having some data to simulate
-- table with non-zero rows but zero-columns.
-- https://github.com/citusdata/citus/issues/4626
BEGIN;
create table local(y int);
insert into local values (1), (2);
alter table local drop column y;
CREATE TABLE zero_col_columnar (like local) USING COLUMNAR;
ALTER TABLE local RENAME TO local_xxxxx;
INSERT INTO zero_col_columnar SELECT * FROM local_xxxxx;
COMMIT;
SELECT * FROM zero_col_columnar;
--
(2 rows)

SELECT count(*) FROM zero_col_columnar;
 count
---------------------------------------------------------------------
     2
(1 row)

EXPLAIN (costs off, summary off) SELECT * FROM zero_col_columnar;
                             QUERY PLAN
---------------------------------------------------------------------
 Custom Scan (ColumnarScan) on zero_col_columnar
   Columnar Projected Columns: <columnar optimized out all columns>
(2 rows)

INSERT INTO zero_col_columnar DEFAULT VALUES;
INSERT INTO zero_col_columnar DEFAULT VALUES;
INSERT INTO zero_col_columnar DEFAULT VALUES;
SELECT * FROM zero_col_columnar;
--
(5 rows)

SELECT count(*) FROM zero_col_columnar;
 count
---------------------------------------------------------------------
     5
(1 row)

EXPLAIN (costs off, summary off) SELECT * FROM zero_col_columnar;
                             QUERY PLAN
---------------------------------------------------------------------
 Custom Scan (ColumnarScan) on zero_col_columnar
   Columnar Projected Columns: <columnar optimized out all columns>
(2 rows)

VACUUM VERBOSE zero_col_columnar;
INFO:  statistics for "zero_col_columnar":
storage id: xxxxx
total file size: 16384, total data size: 0
compression rate: 1.00x
total row count: 5, stripe count: 4, average rows per stripe: 1
chunk count: 0, containing data for dropped columns: 0

ANALYZE zero_col_columnar;
VACUUM FULL zero_col_columnar;
SELECT * FROM zero_col_columnar;
--
(5 rows)

TRUNCATE zero_col_columnar;
SELECT * FROM zero_col_columnar;
--
(0 rows)

DROP TABLE zero_col_columnar;
CREATE TABLE zero_col_columnar(a int) USING columnar;
INSERT INTO zero_col_columnar SELECT i FROM generate_series(1, 5) i;
alter table zero_col_columnar drop column a;
SELECT * FROM zero_col_columnar;
--
(5 rows)

INSERT INTO zero_col_columnar DEFAULT VALUES;
INSERT INTO zero_col_columnar DEFAULT VALUES;
INSERT INTO zero_col_columnar DEFAULT VALUES;
SELECT * FROM zero_col_columnar;
--
(8 rows)

VACUUM VERBOSE zero_col_columnar;
INFO:  statistics for "zero_col_columnar":
storage id: xxxxx
total file size: 49152, total data size: 60
compression rate: 0.40x
total row count: 8, stripe count: 4, average rows per stripe: 2
chunk count: 4, containing data for dropped columns: 4, zstd compressed: 4

ANALYZE zero_col_columnar;
VACUUM FULL zero_col_columnar;
SELECT * FROM zero_col_columnar;
--
(8 rows)

-- Add constraints
-- Add a CHECK constraint
CREATE TABLE products (
    product_no integer,
    name text,
    price int CONSTRAINT price_constraint CHECK (price > 0)
) USING columnar;
-- first insert should fail
INSERT INTO products VALUES (1, 'bread', 0);
ERROR:  new row for relation "products" violates check constraint "price_constraint"
DETAIL:  Failing row contains (1, bread, 0).
INSERT INTO products VALUES (1, 'bread', 10);
ALTER TABLE products ADD CONSTRAINT dummy_constraint CHECK (price > product_no);
-- first insert should fail
INSERT INTO products VALUES (2, 'shampoo', 1);
ERROR:  new row for relation "products" violates check constraint "dummy_constraint"
DETAIL:  Failing row contains (2, shampoo, 1).
INSERT INTO products VALUES (2, 'shampoo', 20);
ALTER TABLE products DROP CONSTRAINT dummy_constraint;
INSERT INTO products VALUES (3, 'pen', 2);
SELECT * FROM products ORDER BY 1;
 product_no |  name   | price
---------------------------------------------------------------------
          1 | bread   |    10
          2 | shampoo |    20
          3 | pen     |     2
(3 rows)

-- Add a UNIQUE constraint
CREATE TABLE products_unique (
    product_no integer UNIQUE,
    name text,
    price numeric
) USING columnar;
ALTER TABLE products ADD COLUMN store_id text UNIQUE;
-- Add a PRIMARY KEY constraint
CREATE TABLE products_primary (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
) USING columnar;
BEGIN;
  ALTER TABLE products DROP COLUMN store_id;
  ALTER TABLE products ADD COLUMN store_id text PRIMARY KEY;
ERROR:  column "store_id" contains null values
ROLLBACK;
-- Add an EXCLUSION constraint (should fail)
CREATE TABLE circles (
    c circle,
    EXCLUDE USING gist (c WITH &&)
) USING columnar;
ERROR:  unsupported access method for the index on columnar table circles
-- Row level security
CREATE TABLE public.row_level_security_col (id int, pgUser CHARACTER VARYING) USING columnar;
CREATE USER user1;
CREATE USER user2;
INSERT INTO public.row_level_security_col VALUES (1, 'user1'), (2, 'user2');
GRANT SELECT, UPDATE, INSERT, DELETE ON public.row_level_security_col TO user1;
GRANT SELECT, UPDATE, INSERT, DELETE ON public.row_level_security_col TO user2;
CREATE POLICY policy_col ON public.row_level_security_col FOR ALL TO PUBLIC USING (pgUser = current_user);
ALTER TABLE public.row_level_security_col ENABLE ROW LEVEL SECURITY;
SELECT * FROM public.row_level_security_col ORDER BY 1;
 id | pguser
---------------------------------------------------------------------
  1 | user1
  2 | user2
(2 rows)

SET ROLE user1;
SELECT * FROM public.row_level_security_col;
 id | pguser
---------------------------------------------------------------------
  1 | user1
(1 row)

SET ROLE user2;
SELECT * FROM public.row_level_security_col;
 id | pguser
---------------------------------------------------------------------
  2 | user2
(1 row)

RESET ROLE;
DROP TABLE public.row_level_security_col;
DROP USER user1;
DROP USER user2;
SET client_min_messages TO WARNING;
DROP SCHEMA columnar_alter CASCADE;
