From 94c66da56a0d670c9fac96ed3fa1cc359eb6a3c5 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 26 Mar 2026 08:40:12 +0900 Subject: [PATCH v2] Restore tgdeferrable and tginitdeferred after NOT ENFORCED then ENFORCED When toggling a foreign key constraint from NOT ENFORCED to ENFORCED, the deferrable and initdeferred flags were not copied from the existing constraint to the new trigger structure, causing them to be lost. Message-Id: CAKmOUTms2nkxEZDdcrsjq5P3b2L_PR266Hv8kW5pANwmVaRJJQ@mail.gmail.com --- src/backend/commands/tablecmds.c | 2 + src/test/regress/expected/foreign_key.out | 58 +++++++++++++++++++++++ src/test/regress/sql/foreign_key.sql | 36 ++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c69c12dc014..dd00e36c69d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -12558,6 +12558,8 @@ ATExecAlterFKConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon, fkconstraint->fk_matchtype = currcon->confmatchtype; fkconstraint->fk_upd_action = currcon->confupdtype; fkconstraint->fk_del_action = currcon->confdeltype; + fkconstraint->deferrable = currcon->condeferrable; + fkconstraint->initdeferred = currcon->condeferred; /* Create referenced triggers */ if (currcon->conrelid == fkrelid) diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 9ae4dbf1b0a..4ab8ed63ff4 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1349,6 +1349,64 @@ UPDATE pktable SET id = 10 WHERE id = 5; -- doesn't match PK, but no error. INSERT INTO fktable VALUES (0, 20); ROLLBACK; +-- verify that tgdeferrable/tginitdeferred are preserved after NOT ENFORCED -> ENFORCED +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT ENFORCED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey ENFORCED; +SELECT tgdeferrable, tginitdeferred FROM pg_trigger +WHERE tgconstraint = (SELECT oid FROM pg_constraint + WHERE conrelid = 'fktable'::regclass + AND conname = 'fktable_fk_fkey'); + tgdeferrable | tginitdeferred +--------------+---------------- + t | t + t | t + t | t + t | t +(4 rows) + +-- verify actual behavior: violation should be deferred to end of transaction +BEGIN; +-- doesn't match PK, but no error yet (INITIALLY DEFERRED) +INSERT INTO fktable VALUES (2, 20); +-- should catch error from INSERT at commit +COMMIT; +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey" +DETAIL: Key (fk)=(20) is not present in table "pktable". +-- reset +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; +-- same, but with DEFERRABLE INITIALLY IMMEDIATE: tginitdeferred should be false +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDIATE; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT ENFORCED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey ENFORCED; +SELECT tgdeferrable, tginitdeferred FROM pg_trigger +WHERE tgconstraint = (SELECT oid FROM pg_constraint + WHERE conrelid = 'fktable'::regclass + AND conname = 'fktable_fk_fkey'); + tgdeferrable | tginitdeferred +--------------+---------------- + t | f + t | f + t | f + t | f +(4 rows) + +-- verify actual behavior: violation should be caught immediately (INITIALLY IMMEDIATE) +-- doesn't match PK, error at INSERT time +INSERT INTO fktable VALUES (2, 20); +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey" +DETAIL: Key (fk)=(20) is not present in table "pktable". +-- verify that SET CONSTRAINTS DEFERRED still works +BEGIN; +SET CONSTRAINTS fktable_fk_fkey DEFERRED; +-- doesn't match PK, but no error yet (explicitly deferred) +INSERT INTO fktable VALUES (2, 20); +-- should catch error from INSERT at commit +COMMIT; +ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey" +DETAIL: Key (fk)=(20) is not present in table "pktable". +-- reset +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; -- try additional syntax ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; -- illegal options diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 3b8c95bf893..d5d6b9a8cef 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -1023,6 +1023,42 @@ UPDATE pktable SET id = 10 WHERE id = 5; INSERT INTO fktable VALUES (0, 20); ROLLBACK; +-- verify that tgdeferrable/tginitdeferred are preserved after NOT ENFORCED -> ENFORCED +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT ENFORCED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey ENFORCED; +SELECT tgdeferrable, tginitdeferred FROM pg_trigger +WHERE tgconstraint = (SELECT oid FROM pg_constraint + WHERE conrelid = 'fktable'::regclass + AND conname = 'fktable_fk_fkey'); +-- verify actual behavior: violation should be deferred to end of transaction +BEGIN; +-- doesn't match PK, but no error yet (INITIALLY DEFERRED) +INSERT INTO fktable VALUES (2, 20); +-- should catch error from INSERT at commit +COMMIT; +-- reset +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; +-- same, but with DEFERRABLE INITIALLY IMMEDIATE: tginitdeferred should be false +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDIATE; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT ENFORCED; +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey ENFORCED; +SELECT tgdeferrable, tginitdeferred FROM pg_trigger +WHERE tgconstraint = (SELECT oid FROM pg_constraint + WHERE conrelid = 'fktable'::regclass + AND conname = 'fktable_fk_fkey'); +-- verify actual behavior: violation should be caught immediately (INITIALLY IMMEDIATE) +-- doesn't match PK, error at INSERT time +INSERT INTO fktable VALUES (2, 20); +-- verify that SET CONSTRAINTS DEFERRED still works +BEGIN; +SET CONSTRAINTS fktable_fk_fkey DEFERRED; +-- doesn't match PK, but no error yet (explicitly deferred) +INSERT INTO fktable VALUES (2, 20); +-- should catch error from INSERT at commit +COMMIT; +-- reset +ALTER TABLE FKTABLE ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; -- try additional syntax ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE; -- 2.53.0