CREATE SCHEMA distributed_domain;
SET search_path TO distributed_domain;
SET citus.next_shard_id TO 93631000;

-- verify domain is not already present on workers
SELECT * FROM run_command_on_workers($$
    SELECT 'distributed_domain.age'::regtype;
$$) ORDER BY 1,2;
CREATE DOMAIN age AS int CHECK( VALUE >= 0 );
-- check domain exists on workers to proof the domain got propagated
SELECT * FROM run_command_on_workers($$
    SELECT 'distributed_domain.age'::regtype;
$$) ORDER BY 1,2;

-- verify the constraint triggers when operations that conflict are pushed to the shards
CREATE TABLE foo (a int);
SELECT create_distributed_table('foo', 'a', shard_count => 2);
CREATE TABLE bar (a age);
SELECT create_distributed_table('bar', 'a', shard_count => 2);
INSERT INTO foo (a) VALUES (-1); -- foo can have negative values
INSERT INTO bar (a) SELECT a FROM foo; -- bar cannot

DROP TABLE bar; -- need to drop this one directly, there is currently a bug in drop schema cascade when it drops both a table and the type of the distribution column at the same time

-- create a domain that is not propagated
SET citus.enable_ddl_propagation TO off;
CREATE DOMAIN us_postal_code AS TEXT
    CHECK(
        VALUE ~ '^\d{5}$'
    OR VALUE ~ '^\d{5}-\d{4}$'
    );
RESET citus.enable_ddl_propagation;

-- and use in distributed table to trigger domain propagation
CREATE TABLE us_snail_addy (
   address_id SERIAL PRIMARY KEY,
   street1 TEXT NOT NULL,
   street2 TEXT,
   street3 TEXT,
   city TEXT NOT NULL,
   postal us_postal_code NOT NULL
);
SELECT create_distributed_table('us_snail_addy', 'address_id');

-- defaults are marked as a constraint on the statement, which makes it interesting for deparsing, so some extensive
-- test coverage to make sure it works in all strange cases.
CREATE SCHEMA distributed_domain_constraints;
SET search_path TO distributed_domain_constraints;
CREATE DOMAIN with_default AS int DEFAULT 0;
CREATE DOMAIN age_with_default AS int DEFAULT 0 CHECK (value > 0);
CREATE DOMAIN age_with_default2 AS int CHECK (value > 0) DEFAULT 0;
CREATE DOMAIN age_with_default3 AS int CHECK (value > 0) DEFAULT NULL;
CREATE DOMAIN age_with_default4 AS int NOT NULL CHECK (value > 0) DEFAULT NULL;

-- test casting with worker queries
-- should simply work, has no check constraints
SELECT * FROM run_command_on_workers($$
    SELECT NULL::distributed_domain_constraints.with_default;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 0::distributed_domain_constraints.with_default;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 1::distributed_domain_constraints.with_default;
$$) ORDER BY 1,2;

-- has a constraint where the number needs to be greater than 0
SELECT * FROM run_command_on_workers($$
    SELECT NULL::distributed_domain_constraints.age_with_default;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 0::distributed_domain_constraints.age_with_default;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 1::distributed_domain_constraints.age_with_default;
$$) ORDER BY 1,2;

-- has a constraint where the number needs to be greater than 0
SELECT * FROM run_command_on_workers($$
    SELECT NULL::distributed_domain_constraints.age_with_default2;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 0::distributed_domain_constraints.age_with_default2;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 1::distributed_domain_constraints.age_with_default2;
$$) ORDER BY 1,2;

-- has a constraint where the number needs to be greater than 0
SELECT * FROM run_command_on_workers($$
    SELECT NULL::distributed_domain_constraints.age_with_default3;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 0::distributed_domain_constraints.age_with_default3;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 1::distributed_domain_constraints.age_with_default3;
$$) ORDER BY 1,2;

-- has a constraint where the number needs to be greater than 0 and not null
SELECT * FROM run_command_on_workers($$
    SELECT NULL::distributed_domain_constraints.age_with_default4;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 0::distributed_domain_constraints.age_with_default4;
$$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$
    SELECT 1::distributed_domain_constraints.age_with_default4;
$$) ORDER BY 1,2;

-- test usage in distributed tables
-- we use all domains defined earlier and insert all possible values, NULL, default, non-violation, violation. Some of
-- the default values will violate a constraint.
-- Based on the constraints on the domain we see different errors and will end up with a final set of data stored.
CREATE TABLE use_default (a int, b with_default);
SELECT create_distributed_table('use_default', 'a', shard_count => 2);
INSERT INTO use_default (a, b) VALUES (0, NULL);
INSERT INTO use_default (a) VALUES (1);
INSERT INTO use_default (a, b) VALUES (2, 1);
INSERT INTO use_default (a, b) VALUES (3, -1);
SELECT * FROM use_default ORDER BY a;

CREATE TABLE use_age_default (a int, b age_with_default);
SELECT create_distributed_table('use_age_default', 'a', shard_count => 2);
INSERT INTO use_age_default (a, b) VALUES (0, NULL);
INSERT INTO use_age_default (a) VALUES (1);
INSERT INTO use_age_default (a, b) VALUES (2, 1);
INSERT INTO use_age_default (a, b) VALUES (3, -1);
-- also load some data with copy to verify coercions.
\COPY use_age_default FROM STDIN DELIMITER AS ',';
4, -1
\.
\COPY use_age_default FROM STDIN DELIMITER AS ',';
5, 1
\.
SELECT * FROM use_age_default ORDER BY a;

CREATE TABLE use_age_default2 (a int, b age_with_default2);
SELECT create_distributed_table('use_age_default2', 'a', shard_count => 2);
INSERT INTO use_age_default2 (a, b) VALUES (0, NULL);
INSERT INTO use_age_default2 (a) VALUES (1);
INSERT INTO use_age_default2 (a, b) VALUES (2, 1);
INSERT INTO use_age_default2 (a, b) VALUES (3, -1);
SELECT * FROM use_age_default2 ORDER BY a;

CREATE TABLE use_age_default3 (a int, b age_with_default3);
SELECT create_distributed_table('use_age_default3', 'a', shard_count => 2);
INSERT INTO use_age_default3 (a, b) VALUES (0, NULL);
INSERT INTO use_age_default3 (a) VALUES (1);
INSERT INTO use_age_default3 (a, b) VALUES (2, 1);
INSERT INTO use_age_default3 (a, b) VALUES (3, -1);
SELECT * FROM use_age_default3 ORDER BY a;

CREATE TABLE use_age_default4 (a int, b age_with_default4);
SELECT create_distributed_table('use_age_default4', 'a', shard_count => 2);
INSERT INTO use_age_default4 (a, b) VALUES (0, NULL);
INSERT INTO use_age_default4 (a) VALUES (1);
INSERT INTO use_age_default4 (a, b) VALUES (2, 1);
INSERT INTO use_age_default4 (a, b) VALUES (3, -1);
SELECT * FROM use_age_default4 ORDER BY a;

SET client_min_messages TO warning;
DROP SCHEMA distributed_domain_constraints CASCADE;
RESET client_min_messages;

-- same tests as above, with with just in time propagation of domains
CREATE SCHEMA distributed_domain_constraints;
SET citus.enable_ddl_propagation TO off;
CREATE DOMAIN with_default AS int DEFAULT 0;
CREATE DOMAIN age_with_default AS int DEFAULT 0 CHECK (value > 0);
CREATE DOMAIN age_with_default2 AS int CHECK (value > 0) DEFAULT 0;
CREATE DOMAIN age_with_default3 AS int CHECK (value > 0) DEFAULT NULL;
CREATE DOMAIN age_with_default4 AS int NOT NULL CHECK (value > 0) DEFAULT NULL;
RESET citus.enable_ddl_propagation;

-- use all domains in tables to get them propagated
CREATE TABLE use_default (a int, b with_default);
SELECT create_distributed_table('use_default', 'a', shard_count => 2);
INSERT INTO use_default (a, b) VALUES (0, NULL);
INSERT INTO use_default (a) VALUES (1);
INSERT INTO use_default (a, b) VALUES (2, 1);
INSERT INTO use_default (a, b) VALUES (3, -1);
SELECT * FROM use_default ORDER BY a;

CREATE TABLE use_age_default (a int, b age_with_default);
SELECT create_distributed_table('use_age_default', 'a', shard_count => 2);
INSERT INTO use_age_default (a, b) VALUES (0, NULL);
INSERT INTO use_age_default (a) VALUES (1);
INSERT INTO use_age_default (a, b) VALUES (2, 1);
INSERT INTO use_age_default (a, b) VALUES (3, -1);
SELECT * FROM use_age_default ORDER BY a;

CREATE TABLE use_age_default2 (a int, b age_with_default2);
SELECT create_distributed_table('use_age_default2', 'a', shard_count => 2);
INSERT INTO use_age_default2 (a, b) VALUES (0, NULL);
INSERT INTO use_age_default2 (a) VALUES (1);
INSERT INTO use_age_default2 (a, b) VALUES (2, 1);
INSERT INTO use_age_default2 (a, b) VALUES (3, -1);
SELECT * FROM use_age_default2 ORDER BY a;

CREATE TABLE use_age_default3 (a int, b age_with_default3);
SELECT create_distributed_table('use_age_default3', 'a', shard_count => 2);
INSERT INTO use_age_default3 (a, b) VALUES (0, NULL);
INSERT INTO use_age_default3 (a) VALUES (1);
INSERT INTO use_age_default3 (a, b) VALUES (2, 1);
INSERT INTO use_age_default3 (a, b) VALUES (3, -1);
SELECT * FROM use_age_default3 ORDER BY a;

CREATE TABLE use_age_default4 (a int, b age_with_default4);
SELECT create_distributed_table('use_age_default4', 'a', shard_count => 2);
INSERT INTO use_age_default4 (a, b) VALUES (0, NULL);
INSERT INTO use_age_default4 (a) VALUES (1);
INSERT INTO use_age_default4 (a, b) VALUES (2, 1);
INSERT INTO use_age_default4 (a, b) VALUES (3, -1);
SELECT * FROM use_age_default4 ORDER BY a;

-- clean up
SET client_min_messages TO warning;
DROP SCHEMA distributed_domain_constraints CASCADE;
RESET client_min_messages;

CREATE SCHEMA postgres_domain_examples;
SET search_path TO postgres_domain_examples;

-- make sure the function gets automatically propagated when we propagate the domain
SET citus.enable_ddl_propagation TO off;
create function sql_is_distinct_from(anyelement, anyelement)
    returns boolean language sql
    as 'select $1 is distinct from $2 limit 1';
RESET citus.enable_ddl_propagation;

CREATE DOMAIN inotnull int
    CHECK (sql_is_distinct_from(value, null));

SELECT * FROM run_command_on_workers($$ SELECT 1::postgres_domain_examples.inotnull; $$);
SELECT * FROM run_command_on_workers($$ SELECT null::postgres_domain_examples.inotnull; $$);

-- create a domain with sql function as a default value
SET citus.enable_ddl_propagation TO off;
create function random_between(min int, max int)
    returns int language sql
    as 'SELECT round(random()*($2-$1))+$1;';
RESET citus.enable_ddl_propagation;

-- this verifies the function in the default expression is found and distributed, otherwise the creation of the domain would fail on the workers.
CREATE DOMAIN with_random_default int DEFAULT random_between(100, 200);

SET client_min_messages TO warning;
DROP SCHEMA postgres_domain_examples CASCADE;
RESET client_min_messages;

SET search_path TO distributed_domain;

-- verify drops are propagated
CREATE DOMAIN will_drop AS text DEFAULT 'foo';
DROP DOMAIN will_drop;
-- verify domain is dropped from workers
SELECT * FROM run_command_on_workers($$ SELECT 'dropped?'::distributed_domain.will_drop; $$);

-- verify the type modifiers are deparsed correctly, both for direct propagation as well as on demand propagation
CREATE DOMAIN varcharmod AS varchar(3);
SELECT * FROM run_command_on_workers($$ SELECT '12345'::distributed_domain.varcharmod; $$) ORDER BY 1,2;

SET citus.enable_ddl_propagation TO off;
CREATE DOMAIN varcharmod_ondemand AS varchar(3);
RESET citus.enable_ddl_propagation;
CREATE TABLE use_varcharmod_ondemand (a int, b varcharmod_ondemand);
SELECT create_distributed_table('use_varcharmod_ondemand', 'a');
-- epxected error due to value being too long for varchar(3)
INSERT INTO use_varcharmod_ondemand VALUES (1,'12345');
SELECT * FROM run_command_on_workers($$ SELECT '12345'::distributed_domain.varcharmod_ondemand; $$) ORDER BY 1,2;

-- section testing default altering
CREATE DOMAIN alter_default AS text DEFAULT 'foo';
CREATE TABLE use_alter_default (a int, b alter_default);
SELECT create_distributed_table('use_alter_default', 'a', shard_count => 4);
INSERT INTO use_alter_default (a) VALUES (1);
ALTER DOMAIN alter_default SET DEFAULT 'bar';
INSERT INTO use_alter_default (a) VALUES (2);
ALTER DOMAIN alter_default DROP DEFAULT;
INSERT INTO use_alter_default (a) VALUES (3);
SELECT * FROM use_alter_default ORDER BY 1,2;

-- add new dependency while adding default
CREATE DOMAIN add_default_with_function AS int;
SET citus.enable_ddl_propagation TO off;
create function random_between(min int, max int)
    returns int language sql
    as 'SELECT round(random()*($2-$1))+$1;';
RESET citus.enable_ddl_propagation;
ALTER DOMAIN add_default_with_function SET DEFAULT random_between(100, 200);
CREATE TABLE use_add_default_with_function (a int, b add_default_with_function);
SELECT create_distributed_table('use_add_default_with_function', 'a', shard_count => 4);
INSERT INTO use_add_default_with_function (a) VALUES (1);

-- altering NULL/NOT NULL
CREATE DOMAIN alter_null AS int;
CREATE TABLE use_alter_null (a int, b alter_null);
SELECT create_distributed_table('use_alter_null', 'a');
INSERT INTO use_alter_null (a) VALUES (1);
ALTER DOMAIN alter_null SET NOT NULL;
TRUNCATE use_alter_null;
ALTER DOMAIN alter_null SET NOT NULL;
INSERT INTO use_alter_null (a) VALUES (2);
ALTER DOMAIN alter_null DROP NOT NULL;
INSERT INTO use_alter_null (a) VALUES (3);
SELECT * FROM use_alter_null ORDER BY 1;

-- testing adding/dropping constraints
SET citus.enable_ddl_propagation TO off;
create function sql_is_distinct_from(anyelement, anyelement)
    returns boolean language sql
    as 'select $1 is distinct from $2 limit 1';
RESET citus.enable_ddl_propagation;

CREATE DOMAIN alter_add_constraint int;
ALTER DOMAIN alter_add_constraint ADD CONSTRAINT check_distinct CHECK (sql_is_distinct_from(value, null));
SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.alter_add_constraint; $$);
SELECT * FROM run_command_on_workers($$ SELECT null::distributed_domain.alter_add_constraint; $$);

ALTER DOMAIN alter_add_constraint DROP CONSTRAINT check_distinct;
SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.alter_add_constraint; $$);
SELECT * FROM run_command_on_workers($$ SELECT null::distributed_domain.alter_add_constraint; $$);
ALTER DOMAIN alter_add_constraint DROP CONSTRAINT IF EXISTS check_distinct;

ALTER DOMAIN alter_add_constraint ADD CONSTRAINT check_distinct CHECK (sql_is_distinct_from(value, null));
ALTER DOMAIN alter_add_constraint RENAME CONSTRAINT check_distinct TO check_distinct_renamed;
ALTER DOMAIN alter_add_constraint DROP CONSTRAINT check_distinct_renamed;

-- test validating invalid constraints
CREATE DOMAIN age_invalid AS int NOT NULL DEFAULT 0;
CREATE TABLE use_age_invalid (a int, b age_invalid);
SELECT create_distributed_table('use_age_invalid', 'a', shard_count => 4);
INSERT INTO use_age_invalid VALUES (1,1), (2, 2), (3, 0), (4, -1);
ALTER DOMAIN age_invalid ADD CONSTRAINT check_age_positive CHECK (value>=0) NOT VALID;
-- should fail, even though constraint is not valid
INSERT INTO use_age_invalid VALUES (5,-1);
-- reading violating data of an non-valid constraint errors in citus
SELECT * FROM use_age_invalid ORDER BY 1;
-- should fail since there is data in the table that violates the check
ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive;
DELETE FROM use_age_invalid WHERE b < 0;
-- should succeed now since the violating data has been removed
ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive;
-- still fails for constraint
INSERT INTO use_age_invalid VALUES (5,-1);
SELECT * FROM use_age_invalid ORDER BY 1;
-- verify we can validate a constraint that is already validated, can happen when we add a node while a domain constraint was not validated
ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive;

-- test changing the owner of a domain
CREATE ROLE domain_owner;

CREATE DOMAIN alter_domain_owner AS int;
ALTER DOMAIN alter_domain_owner OWNER TO domain_owner;

SELECT u.rolname
FROM pg_type t
         JOIN pg_roles u
              ON (t.typowner = u.oid)
WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype;

SELECT * FROM run_command_on_workers($$
    SELECT u.rolname
      FROM pg_type t
      JOIN pg_roles u
        ON (t.typowner = u.oid)
     WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype;
$$) ORDER BY 1,2;

DROP DOMAIN alter_domain_owner;

SET citus.enable_ddl_propagation TO off;
CREATE DOMAIN alter_domain_owner AS int;
ALTER DOMAIN alter_domain_owner OWNER TO domain_owner;
RESET citus.enable_ddl_propagation;

CREATE TABLE use_alter_domain_owner (a int, b alter_domain_owner);
SELECT create_distributed_table('use_alter_domain_owner', 'a', shard_count => 4);

SELECT u.rolname
FROM pg_type t
         JOIN pg_roles u
              ON (t.typowner = u.oid)
WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype;

SELECT * FROM run_command_on_workers($$
    SELECT u.rolname
      FROM pg_type t
      JOIN pg_roles u
        ON (t.typowner = u.oid)
     WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype;
$$) ORDER BY 1,2;

-- rename the domain
ALTER DOMAIN alter_domain_owner RENAME TO renamed_domain;
SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.renamed_domain; $$) ORDER BY 1,2;

-- move schema
SET citus.enable_ddl_propagation TO off;
CREATE SCHEMA distributed_domain_moved;
RESET citus.enable_ddl_propagation;
ALTER DOMAIN renamed_domain SET SCHEMA distributed_domain_moved;

-- test collation
CREATE COLLATION german_phonebook (provider = icu, locale = 'de-u-co-phonebk');

CREATE DOMAIN with_collation AS text COLLATE german_phonebook NOT NULL;
SELECT run_command_on_workers($$ SELECT typcollation::regcollation FROM pg_type WHERE oid = 'distributed_domain.with_collation'::regtype; $$);
DROP DOMAIN with_collation;

SET citus.enable_ddl_propagation TO off;
CREATE DOMAIN with_collation AS text COLLATE german_phonebook NOT NULL;
RESET citus.enable_ddl_propagation;

CREATE TABLE use_with_collation (a int, b with_collation);
SELECT create_reference_table('use_with_collation');
SELECT run_command_on_workers($$ SELECT typcollation::regcollation FROM pg_type WHERE oid = 'distributed_domain.with_collation'::regtype; $$);

INSERT INTO use_with_collation VALUES (1, U&'\00E4sop'), (2, 'Vossr');
SELECT * FROM use_with_collation WHERE b < 'b';

-- test domain backed by array
CREATE DOMAIN domain_array AS int[] NOT NULL CHECK (array_length(value,1) >= 2);
SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.domain_array; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT ARRAY[1]::distributed_domain.domain_array; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT ARRAY[1,2]::distributed_domain.domain_array; $$) ORDER BY 1,2;

DROP DOMAIN domain_array;

SET citus.enable_ddl_propagation TO off;
CREATE DOMAIN domain_array AS int[] NOT NULL CHECK (array_length(value,1) >= 2);
RESET citus.enable_ddl_propagation;

CREATE TABLE use_domain_array (a int, b domain_array);
SELECT create_distributed_table('use_domain_array', 'a');
INSERT INTO use_domain_array VALUES (1, NULL);
INSERT INTO use_domain_array VALUES (2, ARRAY[1]);
INSERT INTO use_domain_array VALUES (3, ARRAY[1,2]);
SELECT * FROM use_domain_array ORDER BY 1;

-- add nameless constraint
CREATE DOMAIN nameless_constraint AS int;
ALTER DOMAIN nameless_constraint ADD CHECK (value > 0);
SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.nameless_constraint; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.nameless_constraint; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT (-1)::distributed_domain.nameless_constraint; $$) ORDER BY 1,2;

-- Test domains over domains
create domain vchar4 varchar(4);
create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x');
create domain dtop dinter check (substring(VALUE, 2, 1) = '1');
create table dtest(f1 dtop, id bigserial);
SELECT create_distributed_table('dtest', 'id');

insert into dtest values('x123');
insert into dtest values('123');
insert into dtest values('x234');

DROP TABLE dtest;

DROP DOMAIN IF EXISTS dtop;
DROP DOMAIN vchar4;
DROP DOMAIN vchar4 CASCADE;

-- drop multiple domains at once, for which one is not distributed
CREATE DOMAIN domain1 AS int;
CREATE DOMAIN domain2 AS text;
SET citus.enable_ddl_propagation TO off;
CREATE DOMAIN domain3 AS text;
RESET citus.enable_ddl_propagation;

SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.domain1; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain2; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain3; $$) ORDER BY 1,2;

DROP DOMAIN domain1, domain2, domain3;

SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.domain1; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain2; $$) ORDER BY 1,2;
SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain3; $$) ORDER BY 1,2;

DROP DOMAIN IF EXISTS domain_does_not_exist;

SET client_min_messages TO warning;
DROP SCHEMA distributed_domain, distributed_domain_moved CASCADE;
DROP ROLE domain_owner;
