-- -- PG15 -- SHOW server_version \gset SELECT substring(:'server_version', '\d+')::int > 14 AS server_version_above_fourteen \gset \if :server_version_above_fourteen \else \q \endif CREATE SCHEMA pg15; SET search_path TO pg15; SET citus.next_shard_id TO 960000; SET citus.shard_count TO 4; -- -- In PG15, there is an added option to use ICU as global locale provider. -- pg_collation has three locale-related fields: collcollate and collctype, -- which are libc-related fields, and a new one colliculocale, which is the -- ICU-related field. Only the libc-related fields or the ICU-related field -- is set, never both. -- Relevant PG commits: -- f2553d43060edb210b36c63187d52a632448e1d2 -- 54637508f87bd5f07fb9406bac6b08240283be3b -- -- fail, needs "locale" CREATE COLLATION german_phonebook_test (provider = icu, lc_collate = 'de-u-co-phonebk'); ERROR: parameter "locale" must be specified -- fail, needs "locale" CREATE COLLATION german_phonebook_test (provider = icu, lc_collate = 'de-u-co-phonebk', lc_ctype = 'de-u-co-phonebk'); ERROR: parameter "locale" must be specified -- works CREATE COLLATION german_phonebook_test (provider = icu, locale = 'de-u-co-phonebk'); -- with icu provider, colliculocale will be set, collcollate and collctype will be null SELECT result FROM run_command_on_all_nodes(' SELECT collcollate FROM pg_collation WHERE collname = ''german_phonebook_test''; '); result --------------------------------------------------------------------- (3 rows) SELECT result FROM run_command_on_all_nodes(' SELECT collctype FROM pg_collation WHERE collname = ''german_phonebook_test''; '); result --------------------------------------------------------------------- (3 rows) SELECT result FROM run_command_on_all_nodes(' SELECT colliculocale FROM pg_collation WHERE collname = ''german_phonebook_test''; '); result --------------------------------------------------------------------- de-u-co-phonebk de-u-co-phonebk de-u-co-phonebk (3 rows) -- with non-icu provider, colliculocale will be null, collcollate and collctype will be set CREATE COLLATION default_provider (provider = libc, lc_collate = "POSIX", lc_ctype = "POSIX"); SELECT result FROM run_command_on_all_nodes(' SELECT collcollate FROM pg_collation WHERE collname = ''default_provider''; '); result --------------------------------------------------------------------- POSIX POSIX POSIX (3 rows) SELECT result FROM run_command_on_all_nodes(' SELECT collctype FROM pg_collation WHERE collname = ''default_provider''; '); result --------------------------------------------------------------------- POSIX POSIX POSIX (3 rows) SELECT result FROM run_command_on_all_nodes(' SELECT colliculocale FROM pg_collation WHERE collname = ''default_provider''; '); result --------------------------------------------------------------------- (3 rows) -- -- In PG15, Renaming triggers on partitioned tables had two problems -- recurses to renaming the triggers on the partitions as well. -- Here we test that distributed triggers behave the same way. -- Relevant PG commit: -- 80ba4bb383538a2ee846fece6a7b8da9518b6866 -- SET citus.enable_unsafe_triggers TO true; CREATE TABLE sale( sale_date date not null, state_code text, product_sku text, units integer) PARTITION BY list (state_code); ALTER TABLE sale ADD CONSTRAINT sale_pk PRIMARY KEY (state_code, sale_date); CREATE TABLE sale_newyork PARTITION OF sale FOR VALUES IN ('NY'); CREATE TABLE sale_california PARTITION OF sale FOR VALUES IN ('CA'); CREATE TABLE record_sale( operation_type text not null, product_sku text, state_code text, units integer, PRIMARY KEY(state_code, product_sku, operation_type, units)); SELECT create_distributed_table('sale', 'state_code'); create_distributed_table --------------------------------------------------------------------- (1 row) SELECT create_distributed_table('record_sale', 'state_code', colocate_with := 'sale'); create_distributed_table --------------------------------------------------------------------- (1 row) CREATE OR REPLACE FUNCTION record_sale() RETURNS trigger AS $$ BEGIN INSERT INTO pg15.record_sale(operation_type, product_sku, state_code, units) VALUES (TG_OP, NEW.product_sku, NEW.state_code, NEW.units); RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER record_sale_trigger AFTER INSERT OR UPDATE OR DELETE ON sale FOR EACH ROW EXECUTE FUNCTION pg15.record_sale(); CREATE VIEW sale_triggers AS SELECT tgname, tgrelid::regclass, tgenabled FROM pg_trigger WHERE tgrelid::regclass::text like 'sale%' ORDER BY 1, 2; SELECT * FROM sale_triggers ORDER BY 1, 2; tgname | tgrelid | tgenabled --------------------------------------------------------------------- record_sale_trigger | sale | O record_sale_trigger | sale_newyork | O record_sale_trigger | sale_california | O truncate_trigger_xxxxxxx | sale | O truncate_trigger_xxxxxxx | sale_california | O truncate_trigger_xxxxxxx | sale_newyork | O (6 rows) ALTER TRIGGER "record_sale_trigger" ON "pg15"."sale" RENAME TO "new_record_sale_trigger"; SELECT * FROM sale_triggers ORDER BY 1, 2; tgname | tgrelid | tgenabled --------------------------------------------------------------------- new_record_sale_trigger | sale | O new_record_sale_trigger | sale_newyork | O new_record_sale_trigger | sale_california | O truncate_trigger_xxxxxxx | sale | O truncate_trigger_xxxxxxx | sale_california | O truncate_trigger_xxxxxxx | sale_newyork | O (6 rows) -- -- In PG15, For GENERATED columns, all dependencies of the generation -- expression are recorded as NORMAL dependencies of the column itself. -- This requires CASCADE to drop generated cols with the original col. -- Test this behavior in distributed table, specifically with -- undistribute_table within a transaction. -- Relevant PG Commit: cb02fcb4c95bae08adaca1202c2081cfc81a28b5 -- CREATE TABLE generated_stored_ref ( col_1 int, col_2 int, col_3 int generated always as (col_1+col_2) stored, col_4 int, col_5 int generated always as (col_4*2-col_1) stored ); SELECT create_reference_table ('generated_stored_ref'); create_reference_table --------------------------------------------------------------------- (1 row) -- populate the table INSERT INTO generated_stored_ref (col_1, col_4) VALUES (1,2), (11,12); INSERT INTO generated_stored_ref (col_1, col_2, col_4) VALUES (100,101,102), (200,201,202); SELECT * FROM generated_stored_ref ORDER BY 1,2,3,4,5; col_1 | col_2 | col_3 | col_4 | col_5 --------------------------------------------------------------------- 1 | | | 2 | 3 11 | | | 12 | 13 100 | 101 | 201 | 102 | 104 200 | 201 | 401 | 202 | 204 (4 rows) -- fails, CASCADE must be specified -- will test CASCADE inside the transcation ALTER TABLE generated_stored_ref DROP COLUMN col_1; ERROR: cannot drop column col_1 of table generated_stored_ref because other objects depend on it DETAIL: column col_3 of table generated_stored_ref depends on column col_1 of table generated_stored_ref column col_5 of table generated_stored_ref depends on column col_1 of table generated_stored_ref HINT: Use DROP ... CASCADE to drop the dependent objects too. BEGIN; -- drops col_1, col_3, col_5 ALTER TABLE generated_stored_ref DROP COLUMN col_1 CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to column col_3 of table generated_stored_ref drop cascades to column col_5 of table generated_stored_ref ALTER TABLE generated_stored_ref DROP COLUMN col_4; -- show that undistribute_table works fine SELECT undistribute_table('generated_stored_ref'); NOTICE: creating a new table for pg15.generated_stored_ref NOTICE: moving the data of pg15.generated_stored_ref NOTICE: dropping the old pg15.generated_stored_ref NOTICE: renaming the new table to pg15.generated_stored_ref undistribute_table --------------------------------------------------------------------- (1 row) INSERT INTO generated_stored_ref VALUES (5); SELECT * FROM generated_stored_REF ORDER BY 1; col_2 --------------------------------------------------------------------- 5 101 201 (5 rows) ROLLBACK; -- Clean up DROP SCHEMA pg15 CASCADE; NOTICE: drop cascades to 7 other objects DETAIL: drop cascades to collation german_phonebook_test drop cascades to collation default_provider drop cascades to table sale drop cascades to table record_sale drop cascades to function record_sale() drop cascades to view sale_triggers drop cascades to table generated_stored_ref