CREATE SCHEMA regular_schema; SET search_path TO regular_schema; SET citus.next_shard_id TO 1920000; SET citus.shard_count TO 32; SET citus.shard_replication_factor TO 1; SET client_min_messages TO WARNING; SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); ?column? --------------------------------------------------------------------- 1 (1 row) SET client_min_messages TO NOTICE; -- Verify that the UDFs used to sync tenant schema metadata to workers -- fail on NULL input. SELECT citus_internal.add_tenant_schema(NULL, 1); ERROR: schema_id cannot be NULL SELECT citus_internal.add_tenant_schema(1, NULL); ERROR: colocation_id cannot be NULL SELECT citus_internal.delete_tenant_schema(NULL); ERROR: schema_id cannot be NULL SELECT citus_internal.unregister_tenant_schema_globally(1, NULL); ERROR: schema_name cannot be NULL SELECT citus_internal.unregister_tenant_schema_globally(NULL, 'text'); ERROR: schema_id cannot be NULL -- Verify that citus_internal.unregister_tenant_schema_globally can only -- be called on schemas that are dropped already. SELECT citus_internal.unregister_tenant_schema_globally('regular_schema'::regnamespace, 'regular_schema'); ERROR: schema is expected to be already dropped because this function is only expected to be called from Citus drop hook SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); ?column? --------------------------------------------------------------------- 1 (1 row) CREATE TABLE regular_schema.test_table(a int, b text); SELECT create_distributed_table('regular_schema.test_table', 'a'); create_distributed_table --------------------------------------------------------------------- (1 row) SET citus.enable_schema_based_sharding TO ON; -- show that regular_schema doesn't show up in pg_dist_schema SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'regular_schema'; ?column? --------------------------------------------------------------------- t (1 row) -- empty tenant CREATE SCHEMA "tenant\'_1"; CREATE SCHEMA IF NOT EXISTS "tenant\'_1"; NOTICE: schema "tenant\'_1" already exists, skipping -- non-empty tenant CREATE SCHEMA "tenant\'_2"; CREATE TABLE "tenant\'_2".test_table(a int, b text); -- empty tenant CREATE SCHEMA "tenant\'_3"; CREATE TABLE "tenant\'_3".test_table(a int, b text); DROP TABLE "tenant\'_3".test_table; -- add a node after creating tenant schemas SELECT 1 FROM citus_add_node('localhost', :worker_2_port); ?column? --------------------------------------------------------------------- 1 (1 row) ALTER SCHEMA "tenant\'_1" RENAME TO tenant_1; ALTER SCHEMA "tenant\'_2" RENAME TO tenant_2; ALTER SCHEMA "tenant\'_3" RENAME TO tenant_3; -- verify that create_distributed_table() and others fail when called on tenant tables SELECT create_distributed_table('tenant_2.test_table', 'a'); ERROR: table "test_table" is already distributed SELECT create_reference_table('tenant_2.test_table'); ERROR: table "test_table" is already distributed SELECT citus_add_local_table_to_metadata('tenant_2.test_table'); ERROR: table "test_table" is already distributed -- verify we don't allow update_distributed_table_colocation for tenant tables SELECT update_distributed_table_colocation('tenant_2.test_table', colocate_with => 'none'); ERROR: tenant_2.test_table is not allowed for update_distributed_table_colocation because it belongs to a distributed schema -- verify we also don't allow colocate_with a tenant table SELECT update_distributed_table_colocation('regular_schema.test_table', colocate_with => 'tenant_2.test_table'); ERROR: tenant_2.test_table is not allowed for colocate_with because it belongs to a distributed schema -- verify we do not allow undistribute_table for tenant tables CREATE TABLE tenant_2.undist_table(id int); SELECT undistribute_table('tenant_2.undist_table'); ERROR: tenant_2.undist_table is not allowed for undistribute_table because it belongs to a distributed schema -- verify we don't allow alter_distributed_table for tenant tables SELECT alter_distributed_table('tenant_2.test_table', colocate_with => 'none'); ERROR: tenant_2.test_table is not allowed for alter_distributed_table because it belongs to a distributed schema -- verify we also don't allow colocate_with a tenant table SELECT alter_distributed_table('regular_schema.test_table', colocate_with => 'tenant_2.test_table'); ERROR: tenant_2.test_table is not allowed for colocate_with because it belongs to a distributed schema -- verify we can set tenant table's schema to regular schema CREATE TABLE tenant_2.test_table2(id int); ALTER TABLE tenant_2.test_table2 SET SCHEMA regular_schema; NOTICE: undistributing table test_table2 in distributed schema tenant_2 before altering its schema -- verify that regular_schema.test_table2 does not exist in pg_dist_partition SELECT COUNT(*)=0 FROM pg_dist_partition WHERE logicalrelid = 'regular_schema.test_table2'::regclass AND partmethod = 'n' AND repmodel = 's' AND colocationid > 0; ?column? --------------------------------------------------------------------- t (1 row) -- verify that tenant_2.test_table2 does not exist SELECT * FROM tenant_2.test_table2; ERROR: relation "tenant_2.test_table2" does not exist -- verify we can set regular table's schema to distributed schema CREATE TABLE regular_schema.test_table3(id int); ALTER TABLE regular_schema.test_table3 SET SCHEMA tenant_2; NOTICE: Moving test_table3 into distributed schema tenant_2 -- verify that tenant_2.test_table3 is recorded in pg_dist_partition as a single-shard table. SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table3'::regclass AND partmethod = 'n' AND repmodel = 's' AND colocationid > 0; ?column? --------------------------------------------------------------------- t (1 row) -- verify that regular_schema.test_table3 does not exist SELECT * FROM regular_schema.test_table3; ERROR: relation "regular_schema.test_table3" does not exist -- verify we can set tenant table's schema to another distributed schema CREATE TABLE tenant_2.test_table4(id int); ALTER TABLE tenant_2.test_table4 SET SCHEMA tenant_3; NOTICE: undistributing table test_table4 in distributed schema tenant_2 before altering its schema NOTICE: Moving test_table4 into distributed schema tenant_3 -- verify that tenant_3.test_table4 is recorded in pg_dist_partition as a single-shard table. SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_3.test_table4'::regclass AND partmethod = 'n' AND repmodel = 's' AND colocationid > 0; ?column? --------------------------------------------------------------------- t (1 row) -- verify that tenant_2.test_table4 does not exist SELECT * FROM tenant_2.test_table4; ERROR: relation "tenant_2.test_table4" does not exist -- verify that we can put a local table in regular schema into distributed schema CREATE TABLE regular_schema.pg_local_tbl(id int); ALTER TABLE regular_schema.pg_local_tbl SET SCHEMA tenant_2; NOTICE: Moving pg_local_tbl into distributed schema tenant_2 -- verify that we can put a Citus local table in regular schema into distributed schema CREATE TABLE regular_schema.citus_local_tbl(id int); SELECT citus_add_local_table_to_metadata('regular_schema.citus_local_tbl'); citus_add_local_table_to_metadata --------------------------------------------------------------------- (1 row) ALTER TABLE regular_schema.citus_local_tbl SET SCHEMA tenant_2; NOTICE: Moving citus_local_tbl into distributed schema tenant_2 -- verify that we do not allow a hash distributed table in regular schema into distributed schema CREATE TABLE regular_schema.hash_dist_tbl(id int); SELECT create_distributed_table('regular_schema.hash_dist_tbl', 'id'); create_distributed_table --------------------------------------------------------------------- (1 row) ALTER TABLE regular_schema.hash_dist_tbl SET SCHEMA tenant_2; ERROR: distributed schema cannot have distributed tables HINT: Undistribute distributed tables before 'ALTER TABLE SET SCHEMA'. -- verify that we do not allow a reference table in regular schema into distributed schema CREATE TABLE regular_schema.ref_tbl(id int PRIMARY KEY); SELECT create_reference_table('regular_schema.ref_tbl'); create_reference_table --------------------------------------------------------------------- (1 row) ALTER TABLE regular_schema.ref_tbl SET SCHEMA tenant_2; ERROR: distributed schema cannot have distributed tables HINT: Undistribute distributed tables before 'ALTER TABLE SET SCHEMA'. -- verify that we can put a table in tenant schema into regular schema CREATE TABLE tenant_2.tenant_tbl(id int); ALTER TABLE tenant_2.tenant_tbl SET SCHEMA regular_schema; NOTICE: undistributing table tenant_tbl in distributed schema tenant_2 before altering its schema -- verify that we can put a table in tenant schema into another tenant schema CREATE TABLE tenant_2.tenant_tbl2(id int); ALTER TABLE tenant_2.tenant_tbl2 SET SCHEMA tenant_3; NOTICE: undistributing table tenant_tbl2 in distributed schema tenant_2 before altering its schema NOTICE: Moving tenant_tbl2 into distributed schema tenant_3 -- verify that we do not allow a local table in regular schema into distributed schema if it has foreign key to a non-reference table in another schema CREATE TABLE regular_schema.pg_local_tbl1(id int PRIMARY KEY); CREATE TABLE regular_schema.pg_local_tbl2(id int REFERENCES regular_schema.pg_local_tbl1(id)); ALTER TABLE regular_schema.pg_local_tbl2 SET SCHEMA tenant_2; ERROR: foreign keys from distributed schemas can only point to the same distributed schema or reference tables in regular schemas DETAIL: "tenant_2.pg_local_tbl2" references "regular_schema.pg_local_tbl1" via foreign key constraint "pg_local_tbl2_id_fkey" -- verify that we allow a local table in regular schema into distributed schema if it has foreign key to a reference table in another schema CREATE TABLE regular_schema.pg_local_tbl3(id int REFERENCES regular_schema.ref_tbl(id)); ALTER TABLE regular_schema.pg_local_tbl3 SET SCHEMA tenant_2; NOTICE: Moving pg_local_tbl3 into distributed schema tenant_2 -- verify that we do not allow a table in tenant schema into regular schema if it has foreign key to/from another table in the same schema CREATE TABLE tenant_2.tenant_tbl1(id int PRIMARY KEY); CREATE TABLE tenant_2.tenant_tbl2(id int REFERENCES tenant_2.tenant_tbl1(id)); ALTER TABLE tenant_2.tenant_tbl1 SET SCHEMA regular_schema; ERROR: set schema is not allowed for table tenant_tbl1 in distributed schema tenant_2 DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema ALTER TABLE tenant_2.tenant_tbl2 SET SCHEMA regular_schema; ERROR: set schema is not allowed for table tenant_tbl2 in distributed schema tenant_2 DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema -- verify that we do not allow a table in distributed schema into another distributed schema if it has foreign key to/from another table in the same schema CREATE TABLE tenant_2.tenant_tbl3(id int PRIMARY KEY); CREATE TABLE tenant_2.tenant_tbl4(id int REFERENCES tenant_2.tenant_tbl3(id)); ALTER TABLE tenant_2.tenant_tbl3 SET SCHEMA tenant_3; ERROR: set schema is not allowed for table tenant_tbl3 in distributed schema tenant_2 DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema ALTER TABLE tenant_2.tenant_tbl4 SET SCHEMA tenant_3; ERROR: set schema is not allowed for table tenant_tbl4 in distributed schema tenant_2 DETAIL: distributed schemas cannot have foreign keys from/to local tables or different schema -- alter set non-existent schema ALTER TABLE tenant_2.test_table SET SCHEMA ghost_schema; ERROR: schema "ghost_schema" does not exist ALTER TABLE IF EXISTS tenant_2.test_table SET SCHEMA ghost_schema; ERROR: schema "ghost_schema" does not exist -- alter set non-existent table ALTER TABLE tenant_2.ghost_table SET SCHEMA ghost_schema; ERROR: relation "tenant_2.ghost_table" does not exist ALTER TABLE IF EXISTS tenant_2.ghost_table SET SCHEMA ghost_schema; NOTICE: relation "ghost_table" does not exist, skipping -- (on coordinator) verify that colocation id is set for empty tenants too SELECT colocationid > 0 FROM pg_dist_schema WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_3'); ?column? --------------------------------------------------------------------- t t (2 rows) -- (on workers) verify that colocation id is set for empty tenants too SELECT result FROM run_command_on_workers($$ SELECT array_agg(colocationid > 0) FROM pg_dist_schema WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_3'); $$); result --------------------------------------------------------------------- {t,t} {t,t} (2 rows) -- Verify that tenant_2.test_table is recorded in pg_dist_partition as a -- single-shard table. SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass AND partmethod = 'n' AND repmodel = 's' AND colocationid > 0; ?column? --------------------------------------------------------------------- t (1 row) -- (on coordinator) verify that colocation id is properly set for non-empty tenant schema SELECT colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass ) FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2'; ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) verify that colocation id is properly set for non-empty tenant schema SELECT result FROM run_command_on_workers($$ SELECT colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_2.test_table'::regclass ) FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2'; $$); result --------------------------------------------------------------------- t t (2 rows) -- create a tenant table for tenant_1 after add_node CREATE TABLE tenant_1.test_table(a int, b text); -- (on coordinator) verify that colocation id is properly set for now-non-empty tenant schema SELECT colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_1.test_table'::regclass ) FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1'; ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) verify that colocation id is properly set for now-non-empty tenant schema SELECT result FROM run_command_on_workers($$ SELECT colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_1.test_table'::regclass ) FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1'; $$); result --------------------------------------------------------------------- t t (2 rows) -- verify that tenant_1 and tenant_2 have different colocation ids SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_schema WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_2'); ?column? --------------------------------------------------------------------- t (1 row) -- verify that we don't allow creating tenant tables via CREATE SCHEMA command CREATE SCHEMA schema_using_schema_elements CREATE TABLE test_table(a int, b text); ERROR: cannot create distributed schema and table in a single statement HINT: SET citus.enable_schema_based_sharding TO off, or create the schema and table in separate commands. CREATE SCHEMA tenant_4; CREATE TABLE tenant_4.tbl_1(a int, b text); CREATE TABLE tenant_4.tbl_2(a int, b text); -- verify that we don't allow creating a foreign table in a tenant schema, with a nice error message CREATE FOREIGN TABLE tenant_4.foreign_table ( id bigint not null, full_name text not null default '' ) SERVER fake_fdw_server OPTIONS (encoding 'utf-8', compression 'true', table_name 'foreign_table'); ERROR: cannot create a foreign table in a distributed schema -- verify that we don't allow creating a foreign table in a tenant schema CREATE TEMPORARY TABLE tenant_4.temp_table (a int, b text); ERROR: cannot create temporary relation in non-temporary schema CREATE TABLE tenant_4.partitioned_table(a int, b text, PRIMARY KEY (a)) PARTITION BY RANGE (a); CREATE TABLE tenant_4.partitioned_table_child_1 PARTITION OF tenant_4.partitioned_table FOR VALUES FROM (1) TO (2); CREATE TABLE tenant_4.another_partitioned_table(a int, b text, FOREIGN KEY (a) REFERENCES tenant_4.partitioned_table(a)) PARTITION BY RANGE (a); CREATE TABLE tenant_4.another_partitioned_table_child PARTITION OF tenant_4.another_partitioned_table FOR VALUES FROM (1) TO (2); -- verify that we allow creating partitioned tables in a tenant schema SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_4.partitioned_table_child_1'::regclass AND partmethod = 'n' AND repmodel = 's' AND colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_4.partitioned_table'::regclass); ?column? --------------------------------------------------------------------- t (1 row) SELECT EXISTS( SELECT 1 FROM pg_inherits WHERE inhrelid = 'tenant_4.partitioned_table_child_1'::regclass AND inhparent = 'tenant_4.partitioned_table'::regclass ) AS is_partition; is_partition --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_4.another_partitioned_table_child'::regclass AND partmethod = 'n' AND repmodel = 's' AND colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_4.another_partitioned_table'::regclass); ?column? --------------------------------------------------------------------- t (1 row) SELECT EXISTS( SELECT 1 FROM pg_inherits WHERE inhrelid = 'tenant_4.another_partitioned_table_child'::regclass AND inhparent = 'tenant_4.another_partitioned_table'::regclass ) AS is_partition; is_partition --------------------------------------------------------------------- t (1 row) -- verify the foreign key between parents SELECT EXISTS( SELECT 1 FROM pg_constraint WHERE conrelid = 'tenant_4.another_partitioned_table'::regclass AND confrelid = 'tenant_4.partitioned_table'::regclass AND contype = 'f' ) AS foreign_key_exists; foreign_key_exists --------------------------------------------------------------------- t (1 row) INSERT INTO tenant_4.another_partitioned_table VALUES (1, 'a'); ERROR: insert or update on table "another_partitioned_table_child_1920088" violates foreign key constraint "another_partitioned_table_a_fkey_1920087" DETAIL: Key (a)=(1) is not present in table "partitioned_table_1920085". CONTEXT: while executing command on localhost:xxxxx INSERT INTO tenant_4.partitioned_table VALUES (1, 'a'); INSERT INTO tenant_4.another_partitioned_table VALUES (1, 'a'); CREATE SCHEMA tenant_5; CREATE TABLE tenant_5.tbl_1(a int, b text); CREATE TABLE tenant_5.partitioned_table(a int, b text) PARTITION BY RANGE (a); -- verify that we don't allow creating a partition table that is child of a partitioned table in a different tenant schema CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas -- verify that we don't allow creating a local partition table that is child of a tenant partitioned table CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas SET citus.use_citus_managed_tables TO ON; CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas RESET citus.use_citus_managed_tables; CREATE TABLE regular_schema.local_partitioned_table(a int, b text) PARTITION BY RANGE (a); CREATE TABLE regular_schema.citus_local_partitioned_table(a int, b text) PARTITION BY RANGE (a); SELECT citus_add_local_table_to_metadata('regular_schema.citus_local_partitioned_table'); citus_add_local_table_to_metadata --------------------------------------------------------------------- (1 row) CREATE TABLE regular_schema.dist_partitioned_table(a int, b text) PARTITION BY RANGE (a); SELECT create_distributed_table('regular_schema.dist_partitioned_table', 'a'); create_distributed_table --------------------------------------------------------------------- (1 row) -- verify that we don't allow creating a partition table that is child of a non-tenant partitioned table CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.local_partitioned_table FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.citus_local_partitioned_table FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.dist_partitioned_table FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas CREATE TABLE tenant_4.parent_attach_test(a int, b text) PARTITION BY RANGE (a); CREATE TABLE tenant_4.child_attach_test(a int, b text); CREATE TABLE tenant_5.parent_attach_test(a int, b text) PARTITION BY RANGE (a); CREATE TABLE tenant_5.child_attach_test(a int, b text); CREATE TABLE regular_schema.parent_attach_test_local(a int, b text) PARTITION BY RANGE (a); CREATE TABLE regular_schema.parent_attach_test_citus_local(a int, b text) PARTITION BY RANGE (a); SELECT citus_add_local_table_to_metadata('regular_schema.parent_attach_test_citus_local'); citus_add_local_table_to_metadata --------------------------------------------------------------------- (1 row) CREATE TABLE regular_schema.parent_attach_test_dist(a int, b text) PARTITION BY RANGE (a); SELECT create_distributed_table('regular_schema.parent_attach_test_dist', 'a'); create_distributed_table --------------------------------------------------------------------- (1 row) CREATE TABLE regular_schema.child_attach_test_local(a int, b text); CREATE TABLE regular_schema.child_attach_test_citus_local(a int, b text); SELECT citus_add_local_table_to_metadata('regular_schema.child_attach_test_citus_local'); citus_add_local_table_to_metadata --------------------------------------------------------------------- (1 row) CREATE TABLE regular_schema.child_attach_test_dist(a int, b text); SELECT create_distributed_table('regular_schema.child_attach_test_dist', 'a'); create_distributed_table --------------------------------------------------------------------- (1 row) -- verify that we don't allow attaching a tenant table into a tenant partitioned table, if they are not in the same schema ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_5.child_attach_test FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas -- verify that we don't allow attaching a non-tenant table into a tenant partitioned table ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_local FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_citus_local FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_dist FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas -- verify that we don't allow attaching a tenant table into a non-tenant partitioned table ALTER TABLE regular_schema.parent_attach_test_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas ALTER TABLE regular_schema.parent_attach_test_citus_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas ALTER TABLE regular_schema.parent_attach_test_dist ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2); -- verify that we don't allow multi-level partitioning on tenant tables CREATE TABLE tenant_4.multi_level_test(a int, b text) PARTITION BY RANGE (a); ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_4.multi_level_test FOR VALUES FROM (1) TO (2); ERROR: Citus doesn't support multi-level partitioned tables DETAIL: Relation "multi_level_test" is partitioned table itself and it is also partition of relation "parent_attach_test". -- verify that we allow attaching a tenant table into a tenant partitioned table, if they are in the same schema SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_4.parent_attach_test'::regclass AND partmethod = 'n' AND repmodel = 's' AND colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_4.child_attach_test'::regclass); ?column? --------------------------------------------------------------------- t (1 row) SELECT EXISTS( SELECT 1 FROM pg_inherits WHERE inhrelid = 'tenant_4.child_attach_test'::regclass AND inhparent = 'tenant_4.parent_attach_test'::regclass ) AS is_partition; is_partition --------------------------------------------------------------------- t (1 row) -- errors out because shard replication factor > 1 SET citus.shard_replication_factor TO 2; CREATE TABLE tenant_4.tbl_3 AS SELECT 1 AS a, 'text' as b; ERROR: could not create single shard table: citus.shard_replication_factor is greater than 1 HINT: Consider setting citus.shard_replication_factor to 1 and try again SET citus.shard_replication_factor TO 1; -- verify that we allow creating tenant tables by using CREATE TABLE AS / SELECT INTO commands CREATE TABLE tenant_4.tbl_3 AS SELECT 1 AS a, 'text' as b; 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($$tenant_4.tbl_3$$) CREATE TEMP TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b; ERROR: cannot create temporary relation in non-temporary schema CREATE UNLOGGED TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b WITH NO DATA; -- the same command, no changes because of IF NOT EXISTS CREATE UNLOGGED TABLE IF NOT EXISTS tenant_4.tbl_4 AS SELECT 1 as a, 'text' as b WITH NO DATA; NOTICE: relation "tbl_4" already exists, skipping SELECT 1 as a, 'text' as b INTO tenant_4.tbl_5; 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($$tenant_4.tbl_5$$) -- verify we can query the newly created tenant tables SELECT * FROM tenant_4.tbl_3; a | b --------------------------------------------------------------------- 1 | text (1 row) SELECT COUNT(*) FROM tenant_4.tbl_5; count --------------------------------------------------------------------- 1 (1 row) CREATE TYPE employee_type AS (name text, salary numeric); -- verify that we don't allow creating tenant tables by using CREATE TABLE OF commands CREATE TABLE tenant_4.employees OF employee_type ( PRIMARY KEY (name), salary WITH OPTIONS DEFAULT 1000 ); ERROR: cannot create tables in a distributed schema using CREATE TABLE OF syntax -- verify that we act accordingly when if not exists is used CREATE TABLE IF NOT EXISTS tenant_4.tbl_6(a int, b text); CREATE TABLE IF NOT EXISTS tenant_4.tbl_6(a int, b text); NOTICE: relation "tbl_6" already exists, skipping SELECT logicalrelid, partmethod FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_4.tbl%' ORDER BY logicalrelid; logicalrelid | partmethod --------------------------------------------------------------------- tenant_4.tbl_1 | n tenant_4.tbl_2 | n tenant_4.tbl_3 | n tenant_4.tbl_4 | n tenant_4.tbl_5 | n tenant_4.tbl_6 | n (6 rows) CREATE TABLE regular_schema.local(a int, b text); CREATE TABLE regular_schema.citus_local(a int, b text); SELECT citus_add_local_table_to_metadata('regular_schema.citus_local'); citus_add_local_table_to_metadata --------------------------------------------------------------------- (1 row) CREATE TABLE regular_schema.dist(a int, b text); SELECT create_distributed_table('regular_schema.dist', 'a'); create_distributed_table --------------------------------------------------------------------- (1 row) -- verify that we can create a table LIKE another table CREATE TABLE tenant_5.test_table_like_1(LIKE tenant_5.tbl_1); -- using a table from the same schema CREATE TABLE tenant_5.test_table_like_2(LIKE tenant_4.tbl_1); -- using a table from another schema CREATE TABLE tenant_5.test_table_like_3(LIKE regular_schema.local); -- using a local table CREATE TABLE tenant_5.test_table_like_4(LIKE regular_schema.citus_local); -- using a citus local table CREATE TABLE tenant_5.test_table_like_5(LIKE regular_schema.dist); -- using a distributed table -- verify that all of them are converted to tenant tables SELECT COUNT(*) = 5 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_5.test_table_like_%' AND partmethod = 'n' AND repmodel = 's' AND colocationid = ( SELECT colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_5' ); ?column? --------------------------------------------------------------------- t (1 row) CREATE TABLE regular_schema.local_table_using_like(LIKE tenant_5.tbl_1); -- verify that regular_schema.local_table_using_like is not a tenant table SELECT COUNT(*) = 0 FROM pg_dist_partition WHERE logicalrelid = 'regular_schema.local_table_using_like'::regclass; ?column? --------------------------------------------------------------------- t (1 row) -- verify that INHERITS syntax is not supported when creating a tenant table CREATE TABLE tenant_5.test_table_inherits_1(x int) INHERITS (tenant_5.tbl_1); -- using a table from the same schema ERROR: tables in a distributed schema cannot inherit or be inherited CREATE TABLE tenant_5.test_table_inherits_2(x int) INHERITS (tenant_4.tbl_1); -- using a table from another schema ERROR: tables in a distributed schema cannot inherit or be inherited CREATE TABLE tenant_5.test_table_inherits_3(x int) INHERITS (regular_schema.local); -- using a local table ERROR: tables in a distributed schema cannot inherit or be inherited CREATE TABLE tenant_5.test_table_inherits_4(x int) INHERITS (regular_schema.citus_local); -- using a citus local table ERROR: tables in a distributed schema cannot inherit or be inherited CREATE TABLE tenant_5.test_table_inherits_5(x int) INHERITS (regular_schema.dist); -- using a distributed table ERROR: tables in a distributed schema cannot inherit or be inherited -- verify that INHERITS syntax is not supported when creating a local table based on a tenant table CREATE TABLE regular_schema.local_table_using_inherits(x int) INHERITS (tenant_5.tbl_1); ERROR: tables in a distributed schema cannot inherit or be inherited CREATE TABLE tenant_5.tbl_2(a int, b text); CREATE SCHEMA "CiTuS.TeeN_108"; ALTER SCHEMA "CiTuS.TeeN_108" RENAME TO citus_teen_proper; SELECT schemaid AS citus_teen_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'citus_teen_proper' \gset SELECT colocationid AS citus_teen_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'citus_teen_proper' \gset SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO citus_teen_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'citus_teen_proper' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) -- (on coordinator) verify that colocation id is set for the tenant with a weird name too SELECT :citus_teen_colocationid > 0; ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) verify that the same colocation id is used on workers too SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=1 FROM pg_dist_schema WHERE schemaid::regnamespace::text = ''citus_teen_proper'' AND colocationid = %s; $$);', :citus_teen_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) ALTER SCHEMA citus_teen_proper RENAME TO "CiTuS.TeeN_108"; SET citus.enable_schema_based_sharding TO OFF; -- Show that the tables created in tenant schemas are considered to be -- tenant tables even if the GUC was set to off when creating the table. CREATE TABLE tenant_5.tbl_3(a int, b text); SELECT COUNT(*)=1 FROM pg_dist_partition WHERE logicalrelid = 'tenant_5.tbl_3'::regclass; ?column? --------------------------------------------------------------------- t (1 row) SET citus.enable_schema_based_sharding TO ON; -- Verify that tables that belong to tenant_4 and tenant_5 are stored on -- different worker nodes due to order we followed when creating first tenant -- tables in each of them. SELECT COUNT(DISTINCT(nodename, nodeport))=2 FROM citus_shards WHERE table_name IN ('tenant_4.tbl_1'::regclass, 'tenant_5.tbl_1'::regclass); ?column? --------------------------------------------------------------------- t (1 row) -- show that all the tables in tenant_4 are colocated with each other. SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition WHERE logicalrelid::regclass::text LIKE 'tenant_4.%'; ?column? --------------------------------------------------------------------- t (1 row) -- verify the same for tenant_5 too SELECT COUNT(DISTINCT(colocationid))=1 FROM pg_dist_partition WHERE logicalrelid::regclass::text LIKE 'tenant_5.%'; ?column? --------------------------------------------------------------------- t (1 row) SELECT schemaid AS tenant_4_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_4' \gset SELECT colocationid AS tenant_4_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_4' \gset SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO tenant_4_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_4' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) SET client_min_messages TO WARNING; -- Rename it to a name that contains a single quote to verify that we properly -- escape its name when sending the command to delete the pg_dist_schema -- entry on workers. ALTER SCHEMA tenant_4 RENAME TO "tenant\'_4"; DROP SCHEMA "tenant\'_4", "CiTuS.TeeN_108" CASCADE; SET client_min_messages TO NOTICE; -- (on coordinator) Verify that dropping a tenant schema deletes the associated -- pg_dist_schema entry and pg_dist_colocation too. SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_4_schemaid; ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_4_colocationid; ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :citus_teen_schemaid; ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :citus_teen_colocationid; ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) Verify that dropping a tenant schema deletes the associated -- pg_dist_schema entry and pg_dist_colocation too. SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = (SELECT schemaid FROM tenant_4_schemaid) $$); result --------------------------------------------------------------------- t t (2 rows) SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = (SELECT schemaid FROM citus_teen_schemaid) $$); result --------------------------------------------------------------------- t t (2 rows) SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; $$);', :tenant_4_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE tenant_4_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; $$);', :citus_teen_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE citus_teen_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) -- show that we don't allow colocating a Citus table with a tenant table CREATE TABLE regular_schema.null_shard_key_1(a int, b text); SELECT create_distributed_table('regular_schema.null_shard_key_1', null, colocate_with => 'tenant_5.tbl_2'); ERROR: cannot colocate tables tbl_2 and null_shard_key_1 DETAIL: Cannot colocate tables with distributed schema tables by using colocate_with option. HINT: Consider using "CREATE TABLE" statement to create this table as a single-shard distributed table in the same schema to automatically colocate it with tenant_5.tbl_2 SELECT create_distributed_table('regular_schema.null_shard_key_1', 'a', colocate_with => 'tenant_5.tbl_2'); ERROR: cannot colocate tables tbl_2 and null_shard_key_1 DETAIL: Distribution column types don't match for tbl_2 and null_shard_key_1. CREATE TABLE regular_schema.null_shard_key_table_2(a int, b text); SELECT create_distributed_table('regular_schema.null_shard_key_table_2', null); create_distributed_table --------------------------------------------------------------------- (1 row) -- Show that we don't chose to colocate regular single-shard tables with -- tenant tables by default. SELECT * FROM pg_dist_schema WHERE colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'regular_schema.null_shard_key_table_2'::regclass ); schemaid | colocationid --------------------------------------------------------------------- (0 rows) -- save the colocation id used for tenant_5 SELECT colocationid AS tenant_5_old_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_5' \gset -- drop all the tables that belong to tenant_5 and create a new one DROP TABLE tenant_5.tbl_1, tenant_5.tbl_2, tenant_5.tbl_3; CREATE TABLE tenant_5.tbl_4(a int, b text); -- (on coordinator) verify that tenant_5 is still associated with the same colocation id SELECT colocationid = :tenant_5_old_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_5'; ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) verify that tenant_5 is still associated with the same colocation id SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT colocationid = %s FROM pg_dist_schema WHERE schemaid::regnamespace::text = ''tenant_5''; $$);', :tenant_5_old_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SELECT schemaid AS tenant_1_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1' \gset SELECT colocationid AS tenant_1_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1' \gset SELECT schemaid AS tenant_2_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2' \gset SELECT colocationid AS tenant_2_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2' \gset SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO tenant_1_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_1' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO tenant_2_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_2' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) SET client_min_messages TO WARNING; SET citus.enable_schema_based_sharding TO OFF; DROP SCHEMA tenant_1 CASCADE; CREATE ROLE test_non_super_user; ALTER ROLE test_non_super_user NOSUPERUSER; ALTER SCHEMA tenant_2 OWNER TO non_existing_role; ERROR: role "non_existing_role" does not exist ALTER SCHEMA tenant_2 OWNER TO test_non_super_user; SELECT pg_get_userbyid(nspowner) AS schema_owner FROM pg_namespace WHERE nspname = 'tenant_2'; schema_owner --------------------------------------------------------------------- test_non_super_user (1 row) select result from run_command_on_workers ($$ SELECT pg_get_userbyid(nspowner) AS schema_owner FROM pg_namespace WHERE nspname = 'tenant_2' $$); result --------------------------------------------------------------------- test_non_super_user test_non_super_user (2 rows) DROP OWNED BY test_non_super_user CASCADE; DROP ROLE test_non_super_user; SET client_min_messages TO NOTICE; -- (on coordinator) Verify that dropping a tenant schema always deletes -- the associated pg_dist_schema entry even if the the schema was -- dropped while the GUC was set to off. SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (:tenant_1_schemaid, :tenant_2_schemaid); ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (:tenant_1_colocationid, :tenant_2_colocationid); ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) Verify that dropping a tenant schema always deletes -- the associated pg_dist_schema entry even if the the schema was -- dropped while the GUC was set to off. SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (SELECT schemaid FROM tenant_1_schemaid UNION SELECT schemaid FROM tenant_2_schemaid) $$); result --------------------------------------------------------------------- t t (2 rows) SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); $$);', :tenant_1_colocationid, :tenant_2_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE tenant_1_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE tenant_2_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) SET citus.enable_schema_based_sharding TO ON; SET client_min_messages TO NOTICE; -- show that all schemaid values are unique and non-null in pg_dist_schema SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IS NULL; ?column? --------------------------------------------------------------------- t (1 row) SELECT (SELECT COUNT(*) FROM pg_dist_schema) = (SELECT COUNT(DISTINCT(schemaid)) FROM pg_dist_schema); ?column? --------------------------------------------------------------------- t (1 row) -- show that all colocationid values are unique and non-null in pg_dist_schema SELECT COUNT(*)=0 FROM pg_dist_schema WHERE colocationid IS NULL; ?column? --------------------------------------------------------------------- t (1 row) SELECT (SELECT COUNT(*) FROM pg_dist_schema) = (SELECT COUNT(DISTINCT(colocationid)) FROM pg_dist_schema); ?column? --------------------------------------------------------------------- t (1 row) CREATE TABLE public.cannot_be_a_tenant_table(a int, b text); -- show that we don't consider public schema as a tenant schema SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'public'; ?column? --------------------------------------------------------------------- t (1 row) DROP TABLE public.cannot_be_a_tenant_table; BEGIN; ALTER SCHEMA public RENAME TO public_renamed; CREATE SCHEMA public; -- Show that we don't consider public schema as a tenant schema, -- even if it's recreated. SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'public'; ?column? --------------------------------------------------------------------- t (1 row) ROLLBACK; CREATE TEMPORARY TABLE temp_table(a int, b text); -- show that we don't consider temporary schemas as tenant schemas SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = '%pg_temp%'; ?column? --------------------------------------------------------------------- t (1 row) DROP TABLE temp_table; -- test creating a tenant schema and a tenant table for it in the same transaction BEGIN; CREATE SCHEMA tenant_7; CREATE TABLE tenant_7.tbl_1(a int, b text); CREATE TABLE tenant_7.tbl_2(a int, b text); SELECT colocationid = ( SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_7.tbl_1'::regclass ) FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_7'; ?column? --------------------------------------------------------------------- t (1 row) -- make sure that both tables created in tenant_7 are colocated SELECT COUNT(DISTINCT(colocationid)) = 1 FROM pg_dist_partition WHERE logicalrelid IN ('tenant_7.tbl_1'::regclass, 'tenant_7.tbl_2'::regclass); ?column? --------------------------------------------------------------------- t (1 row) COMMIT; -- Test creating a tenant schema and a tenant table for it in the same transaction -- but this time rollback the transaction. BEGIN; CREATE SCHEMA tenant_8; CREATE TABLE tenant_8.tbl_1(a int, b text); CREATE TABLE tenant_8.tbl_2(a int, b text); ROLLBACK; SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_8'; ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant_8.%'; ?column? --------------------------------------------------------------------- t (1 row) -- Verify that citus.enable_schema_based_sharding and citus.use_citus_managed_tables -- GUC don't interfere with each other when creating a table in tenant schema. -- -- In utility hook, we check whether the CREATE TABLE command is issued on a tenant -- schema before checking whether citus.use_citus_managed_tables is set to ON to -- avoid converting the table into a Citus managed table unnecessarily. -- -- If the CREATE TABLE command is issued on a tenant schema, we skip the check -- for citus.use_citus_managed_tables. SET citus.use_citus_managed_tables TO ON; CREATE TABLE tenant_7.tbl_3(a int, b text, PRIMARY KEY(a)); RESET citus.use_citus_managed_tables; -- Verify that we don't unnecessarily convert a table into a Citus managed -- table when creating it with a pre-defined foreign key to a reference table. CREATE TABLE reference_table(a int PRIMARY KEY); SELECT create_reference_table('reference_table'); create_reference_table --------------------------------------------------------------------- (1 row) -- Notice that tenant_7.tbl_4 have foreign keys both to tenant_7.tbl_3 and -- to reference_table. CREATE TABLE tenant_7.tbl_4(a int REFERENCES reference_table, FOREIGN KEY(a) REFERENCES tenant_7.tbl_3(a) ON DELETE CASCADE); INSERT INTO tenant_7.tbl_3 VALUES (1, 'a'), (2, 'b'), (3, 'c'); INSERT INTO reference_table VALUES (1), (2), (3); INSERT INTO tenant_7.tbl_4 VALUES (1), (2), (3); DELETE FROM tenant_7.tbl_3 WHERE a < 3; SELECT * FROM tenant_7.tbl_4 ORDER BY a; a --------------------------------------------------------------------- 3 (1 row) SELECT COUNT(*)=2 FROM pg_dist_partition WHERE logicalrelid IN ('tenant_7.tbl_3'::regclass, 'tenant_7.tbl_4'::regclass) AND partmethod = 'n' AND repmodel = 's' AND colocationid = (SELECT colocationid FROM pg_dist_partition WHERE logicalrelid = 'tenant_7.tbl_1'::regclass); ?column? --------------------------------------------------------------------- t (1 row) CREATE TABLE local_table(a int PRIMARY KEY); -- fails because tenant tables cannot have foreign keys to local tables CREATE TABLE tenant_7.tbl_5(a int REFERENCES local_table(a)); ERROR: referenced table "local_table" must be a distributed table or a reference table DETAIL: To enforce foreign keys, the referencing and referenced rows need to be stored on the same node. HINT: You could use SELECT create_reference_table('local_table') to replicate the referenced table to all nodes or consider dropping the foreign key -- Fails because tenant tables cannot have foreign keys to tenant tables -- that belong to different tenant schemas. CREATE TABLE tenant_5.tbl_5(a int, b text, FOREIGN KEY(a) REFERENCES tenant_7.tbl_3(a)); ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table CREATE SCHEMA tenant_9; SELECT schemaid AS tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset SELECT colocationid AS tenant_9_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) DROP SCHEMA tenant_9; -- (on coordinator) Make sure that dropping an empty tenant schema -- doesn't leave any dangling entries in pg_dist_schema and -- pg_dist_colocation. SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_9_schemaid; ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_9_colocationid; ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) Make sure that dropping an empty tenant schema -- doesn't leave any dangling entries in pg_dist_schema and -- pg_dist_colocation. SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = (SELECT schemaid FROM tenant_9_schemaid) $$); result --------------------------------------------------------------------- t t (2 rows) SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; $$);', :tenant_9_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE tenant_9_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) CREATE TABLE tenant_3.search_path_test(a int); INSERT INTO tenant_3.search_path_test VALUES (1), (10); CREATE TABLE tenant_5.search_path_test(a int); INSERT INTO tenant_5.search_path_test VALUES (2); CREATE TABLE tenant_7.search_path_test(a int); INSERT INTO tenant_7.search_path_test VALUES (3); CREATE FUNCTION increment_one() RETURNS void LANGUAGE plpgsql AS $$ BEGIN UPDATE search_path_test SET a = a + 1; END; $$; CREATE FUNCTION decrement_one() RETURNS void LANGUAGE plpgsql AS $$ BEGIN UPDATE search_path_test SET a = a - 1; END; $$; SET search_path TO tenant_5; PREPARE list_tuples AS SELECT * FROM search_path_test ORDER BY a; SELECT * FROM search_path_test ORDER BY a; a --------------------------------------------------------------------- 2 (1 row) SET search_path TO tenant_3; DELETE FROM search_path_test WHERE a = 1; SELECT * FROM search_path_test ORDER BY a; a --------------------------------------------------------------------- 10 (1 row) SELECT regular_schema.increment_one(); increment_one --------------------------------------------------------------------- (1 row) EXECUTE list_tuples; a --------------------------------------------------------------------- 11 (1 row) SET search_path TO tenant_7; DROP TABLE search_path_test; SELECT * FROM pg_dist_partition WHERE logicalrelid::text = 'search_path_test'; logicalrelid | partmethod | partkey | colocationid | repmodel | autoconverted --------------------------------------------------------------------- (0 rows) SET search_path TO tenant_5; SELECT regular_schema.decrement_one(); decrement_one --------------------------------------------------------------------- (1 row) EXECUTE list_tuples; a --------------------------------------------------------------------- 1 (1 row) SET search_path TO regular_schema; CREATE USER test_other_super_user WITH superuser; \c - test_other_super_user SET citus.enable_schema_based_sharding TO ON; CREATE SCHEMA tenant_9; \c - postgres SET search_path TO regular_schema; SET citus.next_shard_id TO 1930000; SET citus.shard_count TO 32; SET citus.shard_replication_factor TO 1; SET client_min_messages TO NOTICE; SET citus.enable_schema_based_sharding TO ON; SELECT schemaid AS tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset SELECT colocationid AS tenant_9_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' \gset SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO tenant_9_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_9' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) DROP OWNED BY test_other_super_user; -- (on coordinator) Make sure that dropping an empty tenant schema -- (via DROP OWNED BY) doesn't leave any dangling entries in -- pg_dist_schema and pg_dist_colocation. SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = :tenant_9_schemaid; ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = :tenant_9_colocationid; ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) Make sure that dropping an empty tenant schema -- (via DROP OWNED BY) doesn't leave any dangling entries in -- pg_dist_schema and pg_dist_colocation. SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid = (SELECT schemaid FROM tenant_9_schemaid) $$); result --------------------------------------------------------------------- t t (2 rows) SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = %s; $$);', :tenant_9_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE tenant_9_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) DROP USER test_other_super_user; CREATE ROLE test_non_super_user WITH LOGIN; ALTER ROLE test_non_super_user NOSUPERUSER; GRANT CREATE ON DATABASE regression TO test_non_super_user; GRANT CREATE ON SCHEMA public TO test_non_super_user ; \c - test_non_super_user SET search_path TO regular_schema; SET citus.next_shard_id TO 1940000; SET citus.shard_count TO 32; SET citus.shard_replication_factor TO 1; SET client_min_messages TO NOTICE; SET citus.enable_schema_based_sharding TO ON; -- test create / drop tenant schema / table CREATE SCHEMA tenant_10; CREATE TABLE tenant_10.tbl_1(a int, b text); CREATE TABLE tenant_10.tbl_2(a int, b text); DROP TABLE tenant_10.tbl_2; CREATE SCHEMA tenant_11; SELECT schemaid AS tenant_10_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_10' \gset SELECT colocationid AS tenant_10_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_10' \gset SELECT schemaid AS tenant_11_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_11' \gset SELECT colocationid AS tenant_11_colocationid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_11' \gset SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO tenant_10_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_10' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) SELECT result FROM run_command_on_workers($$ SELECT schemaid INTO tenant_11_schemaid FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'tenant_11' $$); result --------------------------------------------------------------------- SELECT 1 SELECT 1 (2 rows) -- (on coordinator) Verify metadata for tenant schemas that are created via non-super-user. SELECT COUNT(DISTINCT(schemaid))=2 FROM pg_dist_schema WHERE schemaid IN (:tenant_10_schemaid, :tenant_11_schemaid); ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_colocation WHERE colocationid IN (:tenant_10_colocationid, :tenant_11_colocationid); ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) Verify metadata for tenant schemas that are created via non-super-user. SELECT result FROM run_command_on_workers($$ SELECT COUNT(DISTINCT(schemaid))=2 FROM pg_dist_schema WHERE schemaid IN (SELECT schemaid FROM tenant_10_schemaid UNION SELECT schemaid FROM tenant_11_schemaid) $$); result --------------------------------------------------------------------- t t (2 rows) SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(DISTINCT(colocationid))=2 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); $$);', :tenant_10_colocationid, :tenant_11_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SET client_min_messages TO WARNING; DROP SCHEMA tenant_10, tenant_11 CASCADE; SET client_min_messages TO NOTICE; -- (on coordinator) Verify that dropping a tenant schema via non-super-user -- deletes the associated pg_dist_schema entry. SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (:tenant_10_schemaid, :tenant_11_schemaid); ?column? --------------------------------------------------------------------- t (1 row) SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (:tenant_10_colocationid, :tenant_11_colocationid); ?column? --------------------------------------------------------------------- t (1 row) -- (on workers) Verify that dropping a tenant schema via non-super-user -- deletes the associated pg_dist_schema entry. SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_schema WHERE schemaid IN (SELECT schemaid FROM tenant_10_schemaid UNION SELECT schemaid FROM tenant_11_schemaid) $$); result --------------------------------------------------------------------- t t (2 rows) SELECT format( 'SELECT result FROM run_command_on_workers($$ SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid IN (%s, %s); $$);', :tenant_10_colocationid, :tenant_11_colocationid) AS verify_workers_query \gset :verify_workers_query result --------------------------------------------------------------------- t t (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE tenant_10_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) SELECT result FROM run_command_on_workers($$ DROP TABLE tenant_11_schemaid $$); result --------------------------------------------------------------------- DROP TABLE DROP TABLE (2 rows) \c - postgres REVOKE CREATE ON DATABASE regression FROM test_non_super_user; REVOKE CREATE ON SCHEMA public FROM test_non_super_user; DROP ROLE test_non_super_user; \c - - - :worker_1_port -- test creating a tenant table from workers CREATE TABLE tenant_3.tbl_1(a int, b text); ERROR: cannot create tables in a distributed schema from a worker node HINT: Connect to the coordinator node and try again. -- test creating a tenant schema from workers SET citus.enable_schema_based_sharding TO ON; CREATE SCHEMA worker_tenant_schema; ERROR: operation is not allowed on this node HINT: Connect to the coordinator and run it again. SET citus.enable_schema_based_sharding TO OFF; -- Enable the GUC on workers to make sure that the CREATE SCHEMA/ TABLE -- commands that we send to workers don't recursively try creating a -- tenant schema / table. ALTER SYSTEM SET citus.enable_schema_based_sharding TO ON; SELECT pg_reload_conf(); pg_reload_conf --------------------------------------------------------------------- t (1 row) \c - - - :worker_2_port ALTER SYSTEM SET citus.enable_schema_based_sharding TO ON; SELECT pg_reload_conf(); pg_reload_conf --------------------------------------------------------------------- t (1 row) -- Verify that citus_internal.unregister_tenant_schema_globally is a no-op -- on workers. SELECT citus_internal.unregister_tenant_schema_globally('tenant_3'::regnamespace, 'tenant_3'); unregister_tenant_schema_globally --------------------------------------------------------------------- (1 row) \c - - - :master_port SET search_path TO regular_schema; SET citus.next_shard_id TO 1950000; SET citus.shard_count TO 32; SET citus.shard_replication_factor TO 1; SET client_min_messages TO NOTICE; CREATE TABLE tenant_3.tbl_1(a int, b text); SET citus.enable_schema_based_sharding TO ON; CREATE SCHEMA tenant_6; CREATE TABLE tenant_6.tbl_1(a int, b text); -- verify pg_dist_partition entries for tenant_3.tbl_1 and tenant_6.tbl_1 SELECT COUNT(*)=2 FROM pg_dist_partition WHERE logicalrelid IN ('tenant_3.tbl_1'::regclass, 'tenant_6.tbl_1'::regclass) AND partmethod = 'n' AND repmodel = 's' AND colocationid > 0; ?column? --------------------------------------------------------------------- t (1 row) \c - - - :worker_1_port ALTER SYSTEM RESET citus.enable_schema_based_sharding; SELECT pg_reload_conf(); pg_reload_conf --------------------------------------------------------------------- t (1 row) \c - - - :worker_2_port ALTER SYSTEM RESET citus.enable_schema_based_sharding; SELECT pg_reload_conf(); pg_reload_conf --------------------------------------------------------------------- t (1 row) \c - - - :master_port SET search_path TO regular_schema; CREATE TABLE type_sing(a INT); -- errors out because shard_replication_factor = 2 SELECT create_distributed_table('type_sing', NULL, colocate_with:='none'); ERROR: could not create single shard table: citus.shard_replication_factor is greater than 1 HINT: Consider setting citus.shard_replication_factor to 1 and try again SET citus.shard_replication_factor TO 1; SELECT create_distributed_table('type_sing', NULL, colocate_with:='none'); create_distributed_table --------------------------------------------------------------------- (1 row) SET citus.enable_schema_based_sharding TO ON; CREATE SCHEMA type_sch; CREATE TABLE type_sch.tbl (a INT); SELECT table_name, citus_table_type FROM public.citus_tables WHERE table_name::text LIKE 'type_%'; table_name | citus_table_type --------------------------------------------------------------------- type_sch.tbl | schema type_sing | distributed (2 rows) SELECT table_name, citus_table_type FROM citus_shards WHERE table_name::text LIKE 'type_%' AND nodeport IN (:worker_1_port, :worker_2_port); table_name | citus_table_type --------------------------------------------------------------------- type_sch.tbl | schema type_sing | distributed (2 rows) RESET citus.enable_schema_based_sharding; -- test citus_schemas SET citus.enable_schema_based_sharding TO ON; CREATE USER citus_schema_role SUPERUSER; SET ROLE citus_schema_role; CREATE SCHEMA citus_sch1; CREATE TABLE citus_sch1.tbl1(a INT); CREATE TABLE citus_sch1.tbl2(a INT); RESET ROLE; CREATE SCHEMA citus_sch2; CREATE TABLE citus_sch2.tbl1(a INT); SET citus.enable_schema_based_sharding TO OFF; INSERT INTO citus_sch1.tbl1 SELECT * FROM generate_series(1, 10000); INSERT INTO citus_sch1.tbl2 SELECT * FROM generate_series(1, 5000); INSERT INTO citus_sch2.tbl1 SELECT * FROM generate_series(1, 12000); SELECT cs.schema_name, cs.colocation_id = ctc.colocation_id AS correct_colocation_id, cs.schema_size = ctc.calculated_size AS correct_size, cs.schema_owner FROM public.citus_schemas cs JOIN ( SELECT c.relnamespace, ct.colocation_id, pg_size_pretty(sum(citus_total_relation_size(ct.table_name))) AS calculated_size FROM public.citus_tables ct, pg_class c WHERE ct.table_name::oid = c.oid GROUP BY 1, 2 ) ctc ON cs.schema_name = ctc.relnamespace WHERE cs.schema_name::text LIKE 'citus\_sch_' ORDER BY cs.schema_name::text; schema_name | correct_colocation_id | correct_size | schema_owner --------------------------------------------------------------------- citus_sch1 | t | t | citus_schema_role citus_sch2 | t | t | postgres (2 rows) -- test empty schema and empty tables SET citus.enable_schema_based_sharding TO ON; CREATE SCHEMA citus_empty_sch1; CREATE SCHEMA citus_empty_sch2; CREATE TABLE citus_empty_sch2.tbl1(a INT); SET citus.enable_schema_based_sharding TO OFF; SELECT schema_name, schema_size FROM public.citus_schemas WHERE schema_name::text LIKE 'citus\_empty\_sch_' ORDER BY schema_name::text; schema_name | schema_size --------------------------------------------------------------------- citus_empty_sch1 | 0 bytes citus_empty_sch2 | 0 bytes (2 rows) -- test with non-privileged role CREATE USER citus_schema_nonpri; SET ROLE citus_schema_nonpri; SET client_min_messages TO ERROR; SELECT schema_name, colocation_id > 0 AS colocation_id_visible, schema_size IS NOT NULL AS schema_size_visible, schema_owner FROM public.citus_schemas WHERE schema_name::text LIKE 'citus\_sch_' ORDER BY schema_name::text; schema_name | colocation_id_visible | schema_size_visible | schema_owner --------------------------------------------------------------------- citus_sch1 | t | f | citus_schema_role citus_sch2 | t | f | postgres (2 rows) RESET client_min_messages; RESET ROLE; -- test using citus_tables from workers \c - - - :worker_1_port SELECT schema_name, colocation_id > 0 AS colocation_id_visible, schema_size IS NOT NULL AS schema_size_visible, schema_owner FROM public.citus_schemas WHERE schema_name::text LIKE 'citus\_sch_' ORDER BY schema_name::text; schema_name | colocation_id_visible | schema_size_visible | schema_owner --------------------------------------------------------------------- citus_sch1 | t | t | citus_schema_role citus_sch2 | t | t | postgres (2 rows) \c - - - :master_port SET search_path TO regular_schema; -- test we handle create schema with authorization properly for distributed schema SET citus.enable_schema_based_sharding TO ON; CREATE ROLE authschema; CREATE SCHEMA AUTHORIZATION authschema; SET citus.enable_schema_based_sharding TO OFF; SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=1 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'authschema'; $$); result --------------------------------------------------------------------- t t t (3 rows) -- mat view can be created under tenant schema SET citus.enable_schema_based_sharding TO ON; SET citus.shard_replication_factor TO 1; CREATE SCHEMA sc1; CREATE TABLE sc1.t1 (a int); CREATE MATERIALIZED VIEW sc1.v1 AS SELECT * FROM sc1.t1; SET citus.enable_schema_based_sharding TO OFF; -- on coordinator, verify that schema is distributed SELECT colocationid > 0 FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'sc1'; ?column? --------------------------------------------------------------------- t (1 row) -- on workers, verify that schema is distributed SELECT result FROM run_command_on_workers($$ SELECT array_agg(colocationid > 0) FROM pg_dist_schema WHERE schemaid::regnamespace::text = 'sc1' $$); result --------------------------------------------------------------------- {t} {t} (2 rows) SET client_min_messages TO WARNING; DROP SCHEMA regular_schema, tenant_3, tenant_5, tenant_7, tenant_6, type_sch, citus_sch1, citus_sch2, citus_empty_sch1, citus_empty_sch2, authschema, sc1 CASCADE; DROP ROLE citus_schema_role, citus_schema_nonpri, authschema; SELECT citus_remove_node('localhost', :master_port); citus_remove_node --------------------------------------------------------------------- (1 row)