From 0e110ee5a9dff54b01506a1cb18bd3e5342dee76 Mon Sep 17 00:00:00 2001 From: Colm Date: Mon, 15 Dec 2025 10:12:18 +0000 Subject: [PATCH] PG18: drop constraints ONLY and NOT VALID fks on partitioned tables (#8374) This commit verifies that PG18 behavior relating to partitioned tables is propagated and behaves consistently on Citus distributed tables: - Allow dropping of constraints ONLY on partitioned tables - Allow NOT VALID foreign key constraints on partitioned tables --- src/test/regress/expected/pg18.out | 202 +++++++++++++++++++++++++++++ src/test/regress/sql/pg18.sql | 136 +++++++++++++++++++ 2 files changed, 338 insertions(+) diff --git a/src/test/regress/expected/pg18.out b/src/test/regress/expected/pg18.out index e303104b2..4b5c8ccdc 100644 --- a/src/test/regress/expected/pg18.out +++ b/src/test/regress/expected/pg18.out @@ -1415,6 +1415,208 @@ INSERT INTO NE_CHECK_TBL (x) VALUES (5), (15), (8), (12); ERROR: new row for relation "ne_check_tbl_4754044" violates check constraint "check_x2_4754044" DETAIL: Failing row contains (15, null). CONTEXT: while executing command on localhost:xxxxx +-- 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 (4dea33ce7) 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" applies 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 have 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 +-- As with dropping constraints only on the partitioned tables, for +-- NOT VALID foreign keys, we verify that foreign key declarations +-- that use NOT VALID work correctly in Citus by repeating the tests +-- of the PG commit (b663b9436) on a table that is a distributed +-- table in Citus, in addition to a Postgres partitioned table. +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_4754072" violates foreign key constraint "fk_partitioned_fk_2_a_b_fkey_4754072" +DETAIL: Key (a, b)=(1000, 1000) is not present in table "fk_notpartitioned_pk_4754060". +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; -- PG18 Feature: Generated Virtual Columns -- PG18 commit: https://github.com/postgres/postgres/commit/83ea6c540 -- Verify that generated virtual columns are supported on distributed tables. diff --git a/src/test/regress/sql/pg18.sql b/src/test/regress/sql/pg18.sql index 399d62f26..9a6f32b62 100644 --- a/src/test/regress/sql/pg18.sql +++ b/src/test/regress/sql/pg18.sql @@ -893,6 +893,142 @@ ALTER TABLE NE_CHECK_TBL -- CHECK_X2 is ENFORCED, so these inserts should fail INSERT INTO NE_CHECK_TBL (x) VALUES (5), (15), (8), (12); +-- 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 (4dea33ce7) 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" applies 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 have 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 + +-- As with dropping constraints only on the partitioned tables, for +-- NOT VALID foreign keys, we verify that foreign key declarations +-- that use NOT VALID work correctly in Citus by repeating the tests +-- of the PG commit (b663b9436) on a table that is a distributed +-- table in Citus, in addition to a Postgres partitioned table. + +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; + -- PG18 Feature: Generated Virtual Columns -- PG18 commit: https://github.com/postgres/postgres/commit/83ea6c540