From 94c66da56a0d670c9fac96ed3fa1cc359eb6a3c5 Mon Sep 17 00:00:00 2001
From: Yasuo Honda <yasuo.honda@gmail.com>
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

