From c611838208ef413974470db1359c1d55a04c00f7 Mon Sep 17 00:00:00 2001 From: Colm McHugh Date: Fri, 5 Dec 2025 16:05:47 +0000 Subject: [PATCH] PG18: verify partition constraint and foreign key behavior. --- src/test/regress/expected/pg18.out | 197 +++++++++++++++++++++++++++++ src/test/regress/sql/pg18.sql | 130 +++++++++++++++++++ 2 files changed, 327 insertions(+) diff --git a/src/test/regress/expected/pg18.out b/src/test/regress/expected/pg18.out index 191a3f1e8..6965480ff 100644 --- a/src/test/regress/expected/pg18.out +++ b/src/test/regress/expected/pg18.out @@ -1802,6 +1802,203 @@ NOTICE: renaming the new table to pg18_nn.generated_stored_ref (4 rows) ROLLBACK; +-- PG18 Feature: dropping of constraints ONLY on partitioned tables +-- PG18 commit: https://github.com/postgres/postgres/commit/4dea33ce7 +-- Here we verify that dropping constraints ONLY on partitioned tables +-- works correctly in Citus. This is done by repeating the tests of the +-- PG commit (see above) on a table that is a distributed table in Citus, +-- in addition to a Postgres partitioned table. +CREATE TABLE partitioned_table ( + a int, + b char(3) +) PARTITION BY LIST (a); +SELECT create_distributed_table('partitioned_table', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- check that violating rows are correctly reported +CREATE TABLE part_2 (LIKE partitioned_table); +INSERT INTO part_2 VALUES (3, 'aaa'); +ALTER TABLE partitioned_table ATTACH PARTITION part_2 FOR VALUES IN (2); +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($$pg18_nn.part_2$$) +ERROR: partition constraint of relation "part_2" is violated by some row +-- should be ok after deleting the bad row +DELETE FROM part_2; +ALTER TABLE partitioned_table ATTACH PARTITION part_2 FOR VALUES IN (2); +-- PG18's "cannot add NOT NULL or check constraints to *only* the parent, when +-- partitions exist" extends to Citus distributed tables as well. +ALTER TABLE ONLY partitioned_table ALTER b SET NOT NULL; +ERROR: constraint must be added to child tables too +HINT: Do not specify the ONLY keyword. +ALTER TABLE ONLY partitioned_table ADD CONSTRAINT check_b CHECK (b <> 'zzz'); +ERROR: constraint must be added to child tables too +-- Dropping constraints from parent should be ok +ALTER TABLE partitioned_table ALTER b SET NOT NULL; +ALTER TABLE ONLY partitioned_table ALTER b DROP NOT NULL; +ALTER TABLE partitioned_table ADD CONSTRAINT check_b CHECK (b <> 'zzz'); +ALTER TABLE ONLY partitioned_table DROP CONSTRAINT check_b; +-- ... and the partitions still has the NOT NULL constraint: +select relname, attname, attnotnull +from pg_class inner join pg_attribute on (oid=attrelid) +where relname = 'part_2' and attname = 'b' ; + relname | attname | attnotnull +--------------------------------------------------------------------- + part_2 | b | t +(1 row) + +-- ... and the check_b constraint: +select relname, conname, pg_get_expr(conbin, conrelid, true) +from pg_class inner join pg_constraint on (pg_class.oid=conrelid) +where relname = 'part_2' and conname = 'check_b' ; + relname | conname | pg_get_expr +--------------------------------------------------------------------- + part_2 | check_b | b <> 'zzz'::bpchar +(1 row) + +-- PG18 Feature: partitioned tables can have NOT VALID foreign keys +-- PG18 commit: https://github.com/postgres/postgres/commit/b663b9436 +CREATE TABLE fk_notpartitioned_pk (a int, b int, PRIMARY KEY (a, b), c int); +CREATE TABLE fk_partitioned_fk (b int, a int) PARTITION BY RANGE (a, b); +SELECT create_reference_table('fk_notpartitioned_pk'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_distributed_table('fk_partitioned_fk', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID; +-- Attaching a child table with the same valid foreign key constraint. +CREATE TABLE fk_partitioned_fk_1 (a int, b int); +ALTER TABLE fk_partitioned_fk_1 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FOR VALUES FROM (0,0) TO (1000,1000); +-- Child constraint will remain valid. +SELECT conname, convalidated, conrelid::regclass FROM pg_constraint +WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid; + conname | convalidated | conrelid +--------------------------------------------------------------------- + fk_partitioned_fk_a_b_fkey | f | fk_partitioned_fk + fk_partitioned_fk_1_a_b_fkey | t | fk_partitioned_fk_1 +(2 rows) + +-- Validate the constraint +ALTER TABLE fk_partitioned_fk VALIDATE CONSTRAINT fk_partitioned_fk_a_b_fkey; +-- All constraints are now valid. +SELECT conname, convalidated, conrelid::regclass FROM pg_constraint +WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid; + conname | convalidated | conrelid +--------------------------------------------------------------------- + fk_partitioned_fk_a_b_fkey | t | fk_partitioned_fk + fk_partitioned_fk_1_a_b_fkey | t | fk_partitioned_fk_1 +(2 rows) + +-- Attaching a child with a NOT VALID constraint. +CREATE TABLE fk_partitioned_fk_2 (a int, b int); +INSERT INTO fk_partitioned_fk_2 VALUES(1000, 1000); -- doesn't exist in referenced table +ALTER TABLE fk_partitioned_fk_2 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID; +-- It will fail because the attach operation implicitly validates the data. +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000); +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($$pg18_nn.fk_partitioned_fk_2$$) +ERROR: insert or update on table "fk_partitioned_fk_2_4754095" violates foreign key constraint "fk_partitioned_fk_2_a_b_fkey_4754095" +DETAIL: Key (a, b)=(1000, 1000) is not present in table "fk_notpartitioned_pk_4754083". +CONTEXT: while executing command on localhost:xxxxx +-- Remove the invalid data and try again. +TRUNCATE fk_partitioned_fk_2; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000); +-- The child constraint will also be valid. +SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_partitioned_fk_2'::regclass; + conname | convalidated +--------------------------------------------------------------------- + fk_partitioned_fk_2_a_b_fkey | t +(1 row) + +-- Test case where the child constraint is invalid, the grandchild constraint +-- is valid, and the validation for the grandchild should be skipped when a +-- valid constraint is applied to the top parent. +CREATE TABLE fk_partitioned_fk_3 (a int, b int) PARTITION BY RANGE (a, b); +ALTER TABLE fk_partitioned_fk_3 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID; +SELECT create_distributed_table('fk_partitioned_fk_3', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE fk_partitioned_fk_3_1 (a int, b int); +ALTER TABLE fk_partitioned_fk_3_1 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk; +SELECT create_distributed_table('fk_partitioned_fk_3_1', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_1 FOR VALUES FROM (2000,2000) TO (3000,3000); +-- Fails because Citus does not support multi-level (grandchild) partitions +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FOR VALUES FROM (2000,2000) TO (3000,3000); +ERROR: Citus doesn't support multi-level partitioned tables +DETAIL: Relation "fk_partitioned_fk_3" is partitioned table itself and it is also partition of relation "fk_partitioned_fk". +-- All constraints are now valid, except for fk_partitioned_fk_3 +-- because the attach failed because of Citus not yet supporting +-- multi-level partitions. +SELECT conname, convalidated, conrelid::regclass FROM pg_constraint +WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid; + conname | convalidated | conrelid +--------------------------------------------------------------------- + fk_partitioned_fk_a_b_fkey | t | fk_partitioned_fk + fk_partitioned_fk_1_a_b_fkey | t | fk_partitioned_fk_1 + fk_partitioned_fk_2_a_b_fkey | t | fk_partitioned_fk_2 + fk_partitioned_fk_3_a_b_fkey | f | fk_partitioned_fk_3 + fk_partitioned_fk_3_1_a_b_fkey | t | fk_partitioned_fk_3_1 +(5 rows) + +DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk CASCADE; +NOTICE: drop cascades to constraint fk_partitioned_fk_3_a_b_fkey on table fk_partitioned_fk_3 +-- NOT VALID foreign key on a non-partitioned table referencing a partitioned table +CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b); +SELECT create_distributed_table('fk_partitioned_pk', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000); +CREATE TABLE fk_notpartitioned_fk (b int, a int); +SELECT create_distributed_table('fk_notpartitioned_fk', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID; +-- Constraint will be invalid. +SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass; + conname | convalidated +--------------------------------------------------------------------- + fk_notpartitioned_fk_a_b_fkey | f + fk_notpartitioned_fk_a_b_fkey_1 | f +(2 rows) + +ALTER TABLE fk_notpartitioned_fk VALIDATE CONSTRAINT fk_notpartitioned_fk_a_b_fkey; +-- All constraints are now valid. +SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass; + conname | convalidated +--------------------------------------------------------------------- + fk_notpartitioned_fk_a_b_fkey | t + fk_notpartitioned_fk_a_b_fkey_1 | t +(2 rows) + +DROP TABLE fk_notpartitioned_fk, fk_partitioned_pk; -- cleanup with minimum verbosity SET client_min_messages TO ERROR; RESET search_path; diff --git a/src/test/regress/sql/pg18.sql b/src/test/regress/sql/pg18.sql index 4c7112f9a..19a5648a4 100644 --- a/src/test/regress/sql/pg18.sql +++ b/src/test/regress/sql/pg18.sql @@ -1120,6 +1120,136 @@ BEGIN; SELECT * FROM generated_stored_ref; ROLLBACK; +-- PG18 Feature: dropping of constraints ONLY on partitioned tables +-- PG18 commit: https://github.com/postgres/postgres/commit/4dea33ce7 + +-- Here we verify that dropping constraints ONLY on partitioned tables +-- works correctly in Citus. This is done by repeating the tests of the +-- PG commit (see above) on a table that is a distributed table in Citus, +-- in addition to a Postgres partitioned table. + +CREATE TABLE partitioned_table ( + a int, + b char(3) +) PARTITION BY LIST (a); + +SELECT create_distributed_table('partitioned_table', 'a'); + +-- check that violating rows are correctly reported +CREATE TABLE part_2 (LIKE partitioned_table); +INSERT INTO part_2 VALUES (3, 'aaa'); +ALTER TABLE partitioned_table ATTACH PARTITION part_2 FOR VALUES IN (2); + +-- should be ok after deleting the bad row +DELETE FROM part_2; +ALTER TABLE partitioned_table ATTACH PARTITION part_2 FOR VALUES IN (2); + +-- PG18's "cannot add NOT NULL or check constraints to *only* the parent, when +-- partitions exist" extends to Citus distributed tables as well. + +ALTER TABLE ONLY partitioned_table ALTER b SET NOT NULL; +ALTER TABLE ONLY partitioned_table ADD CONSTRAINT check_b CHECK (b <> 'zzz'); + +-- Dropping constraints from parent should be ok +ALTER TABLE partitioned_table ALTER b SET NOT NULL; +ALTER TABLE ONLY partitioned_table ALTER b DROP NOT NULL; +ALTER TABLE partitioned_table ADD CONSTRAINT check_b CHECK (b <> 'zzz'); +ALTER TABLE ONLY partitioned_table DROP CONSTRAINT check_b; + +-- ... and the partitions still has the NOT NULL constraint: +select relname, attname, attnotnull +from pg_class inner join pg_attribute on (oid=attrelid) +where relname = 'part_2' and attname = 'b' ; +-- ... and the check_b constraint: +select relname, conname, pg_get_expr(conbin, conrelid, true) +from pg_class inner join pg_constraint on (pg_class.oid=conrelid) +where relname = 'part_2' and conname = 'check_b' ; + +-- PG18 Feature: partitioned tables can have NOT VALID foreign keys +-- PG18 commit: https://github.com/postgres/postgres/commit/b663b9436 + +CREATE TABLE fk_notpartitioned_pk (a int, b int, PRIMARY KEY (a, b), c int); +CREATE TABLE fk_partitioned_fk (b int, a int) PARTITION BY RANGE (a, b); + +SELECT create_reference_table('fk_notpartitioned_pk'); +SELECT create_distributed_table('fk_partitioned_fk', 'a'); + +ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID; + +-- Attaching a child table with the same valid foreign key constraint. +CREATE TABLE fk_partitioned_fk_1 (a int, b int); +ALTER TABLE fk_partitioned_fk_1 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FOR VALUES FROM (0,0) TO (1000,1000); + +-- Child constraint will remain valid. +SELECT conname, convalidated, conrelid::regclass FROM pg_constraint +WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid; + +-- Validate the constraint +ALTER TABLE fk_partitioned_fk VALIDATE CONSTRAINT fk_partitioned_fk_a_b_fkey; + +-- All constraints are now valid. +SELECT conname, convalidated, conrelid::regclass FROM pg_constraint +WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid; + +-- Attaching a child with a NOT VALID constraint. +CREATE TABLE fk_partitioned_fk_2 (a int, b int); +INSERT INTO fk_partitioned_fk_2 VALUES(1000, 1000); -- doesn't exist in referenced table +ALTER TABLE fk_partitioned_fk_2 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID; + +-- It will fail because the attach operation implicitly validates the data. +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000); + +-- Remove the invalid data and try again. +TRUNCATE fk_partitioned_fk_2; +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000); + +-- The child constraint will also be valid. +SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_partitioned_fk_2'::regclass; + +-- Test case where the child constraint is invalid, the grandchild constraint +-- is valid, and the validation for the grandchild should be skipped when a +-- valid constraint is applied to the top parent. +CREATE TABLE fk_partitioned_fk_3 (a int, b int) PARTITION BY RANGE (a, b); +ALTER TABLE fk_partitioned_fk_3 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk NOT VALID; +SELECT create_distributed_table('fk_partitioned_fk_3', 'a'); + +CREATE TABLE fk_partitioned_fk_3_1 (a int, b int); +ALTER TABLE fk_partitioned_fk_3_1 ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk; +SELECT create_distributed_table('fk_partitioned_fk_3_1', 'a'); + +ALTER TABLE fk_partitioned_fk_3 ATTACH PARTITION fk_partitioned_fk_3_1 FOR VALUES FROM (2000,2000) TO (3000,3000); + +-- Fails because Citus does not support multi-level (grandchild) partitions +ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FOR VALUES FROM (2000,2000) TO (3000,3000); + +-- All constraints are now valid, except for fk_partitioned_fk_3 +-- because the attach failed because of Citus not yet supporting +-- multi-level partitions. +SELECT conname, convalidated, conrelid::regclass FROM pg_constraint +WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid; + +DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk CASCADE; + +-- NOT VALID foreign key on a non-partitioned table referencing a partitioned table +CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b); +SELECT create_distributed_table('fk_partitioned_pk', 'a'); +CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000); + +CREATE TABLE fk_notpartitioned_fk (b int, a int); +SELECT create_distributed_table('fk_notpartitioned_fk', 'a'); +ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID; + +-- Constraint will be invalid. +SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass; + +ALTER TABLE fk_notpartitioned_fk VALIDATE CONSTRAINT fk_notpartitioned_fk_a_b_fkey; + +-- All constraints are now valid. +SELECT conname, convalidated FROM pg_constraint WHERE conrelid = 'fk_notpartitioned_fk'::regclass; + +DROP TABLE fk_notpartitioned_fk, fk_partitioned_pk; + -- cleanup with minimum verbosity SET client_min_messages TO ERROR; RESET search_path;