]> granicus.if.org Git - postgresql/commitdiff
Fix overlooked relcache invalidation in ALTER TABLE ... ALTER CONSTRAINT.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 3 May 2015 15:30:24 +0000 (11:30 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 3 May 2015 15:30:24 +0000 (11:30 -0400)
When altering the deferredness state of a foreign key constraint, we
correctly updated the catalogs and then invalidated the relcache state for
the target relation ... but that's not the only relation with relevant
triggers.  Must invalidate the other table as well, or the state change
fails to take effect promptly for operations triggered on the other table.
Per bug #13224 from Christian Ullrich.

In passing, reorganize regression test case for this feature so that it
isn't randomly injected into the middle of an unrelated test sequence.

Oversight in commit f177cbfe676dc2c7ca2b206c54d6bf819feeea8b.  Back-patch
to 9.4 where the faulty code was added.

src/backend/commands/tablecmds.c
src/test/regress/expected/foreign_key.out
src/test/regress/sql/foreign_key.sql

index fd350d2dec9a5eea67051ac730945c468d180dbe..3453c4d8b3c0097c91a4ee6a9429135204f34a53 100644 (file)
@@ -6308,12 +6308,12 @@ static void
 ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
                                          bool recurse, bool recursing, LOCKMODE lockmode)
 {
+       Constraint *cmdcon;
        Relation        conrel;
        SysScanDesc scan;
        ScanKeyData key;
        HeapTuple       contuple;
        Form_pg_constraint currcon = NULL;
-       Constraint *cmdcon = NULL;
        bool            found = false;
 
        Assert(IsA(cmd->def, Constraint));
@@ -6359,10 +6359,11 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
                HeapTuple       copyTuple;
                HeapTuple       tgtuple;
                Form_pg_constraint copy_con;
-               Form_pg_trigger copy_tg;
+               List       *otherrelids = NIL;
                ScanKeyData tgkey;
                SysScanDesc tgscan;
                Relation        tgrel;
+               ListCell   *lc;
 
                /*
                 * Now update the catalog, while we have the door open.
@@ -6395,8 +6396,16 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 
                while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
                {
+                       Form_pg_trigger copy_tg;
+
                        copyTuple = heap_copytuple(tgtuple);
                        copy_tg = (Form_pg_trigger) GETSTRUCT(copyTuple);
+
+                       /* Remember OIDs of other relation(s) involved in FK constraint */
+                       if (copy_tg->tgrelid != RelationGetRelid(rel))
+                               otherrelids = list_append_unique_oid(otherrelids,
+                                                                                                        copy_tg->tgrelid);
+
                        copy_tg->tgdeferrable = cmdcon->deferrable;
                        copy_tg->tginitdeferred = cmdcon->initdeferred;
                        simple_heap_update(tgrel, &copyTuple->t_self, copyTuple);
@@ -6413,9 +6422,16 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
                heap_close(tgrel, RowExclusiveLock);
 
                /*
-                * Invalidate relcache so that others see the new attributes.
+                * Invalidate relcache so that others see the new attributes.  We must
+                * inval both the named rel and any others having relevant triggers.
+                * (At present there should always be exactly one other rel, but
+                * there's no need to hard-wire such an assumption here.)
                 */
                CacheInvalidateRelcache(rel);
+               foreach(lc, otherrelids)
+               {
+                       CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+               }
        }
 
        systable_endscan(scan);
index 0299bfe873070b81eb5dd36fd24a37893d09d0d7..8c47babb6dcc9951db4243065e61712eff563efb 100644 (file)
@@ -1132,15 +1132,6 @@ CREATE TEMP TABLE fktable (
     id int primary key,
     fk int references pktable deferrable initially deferred
 );
--- check ALTER CONSTRAINT
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
--- illegal option
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED;
-ERROR:  constraint declared INITIALLY DEFERRED must be DEFERRABLE
-LINE 1: ...e ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY ...
-                                                             ^
--- reset
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY DEFERRED;
 INSERT INTO pktable VALUES (5, 10);
 BEGIN;
 -- doesn't match PK, but no error yet
@@ -1151,16 +1142,6 @@ UPDATE fktable SET id = id + 1;
 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".
--- change the constraint definition and retest
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDIATE;
-BEGIN;
--- doesn't match PK, should throw error now
-INSERT INTO fktable VALUES (0, 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".
-COMMIT;
--- reset
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY DEFERRED;
 -- check same case when insert is in a different subtransaction than update
 BEGIN;
 -- doesn't match PK, but no error yet
@@ -1198,6 +1179,30 @@ ROLLBACK TO savept1;
 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".
+--
+-- check ALTER CONSTRAINT
+--
+INSERT INTO fktable VALUES (1, 5);
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDIATE;
+BEGIN;
+-- doesn't match FK, should throw error now
+UPDATE pktable SET id = 10 WHERE id = 5;
+ERROR:  update or delete on table "pktable" violates foreign key constraint "fktable_fk_fkey" on table "fktable"
+DETAIL:  Key (id)=(5) is still referenced from table "fktable".
+COMMIT;
+BEGIN;
+-- doesn't match PK, should throw error now
+INSERT INTO fktable VALUES (0, 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".
+COMMIT;
+-- try additional syntax
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
+-- illegal option
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED;
+ERROR:  constraint declared INITIALLY DEFERRED must be DEFERRABLE
+LINE 1: ...e ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY ...
+                                                             ^
 -- test order of firing of FK triggers when several RI-induced changes need to
 -- be made to the same row.  This was broken by subtransaction-related
 -- changes in 8.0.
index 531c881f6318255b272ff1008ed65de356b7f952..53276e4d673b96b0e184439101584bbe5da2b00d 100644 (file)
@@ -818,13 +818,6 @@ CREATE TEMP TABLE fktable (
     fk int references pktable deferrable initially deferred
 );
 
--- check ALTER CONSTRAINT
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
--- illegal option
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED;
--- reset
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY DEFERRED;
-
 INSERT INTO pktable VALUES (5, 10);
 
 BEGIN;
@@ -838,19 +831,6 @@ UPDATE fktable SET id = id + 1;
 -- should catch error from initial INSERT
 COMMIT;
 
--- change the constraint definition and retest
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDIATE;
-
-BEGIN;
-
--- doesn't match PK, should throw error now
-INSERT INTO fktable VALUES (0, 20);
-
-COMMIT;
-
--- reset
-ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY DEFERRED;
-
 -- check same case when insert is in a different subtransaction than update
 
 BEGIN;
@@ -900,6 +880,33 @@ ROLLBACK TO savept1;
 -- should catch error from initial INSERT
 COMMIT;
 
+--
+-- check ALTER CONSTRAINT
+--
+
+INSERT INTO fktable VALUES (1, 5);
+
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDIATE;
+
+BEGIN;
+
+-- doesn't match FK, should throw error now
+UPDATE pktable SET id = 10 WHERE id = 5;
+
+COMMIT;
+
+BEGIN;
+
+-- doesn't match PK, should throw error now
+INSERT INTO fktable VALUES (0, 20);
+
+COMMIT;
+
+-- try additional syntax
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
+-- illegal option
+ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY DEFERRED;
+
 -- test order of firing of FK triggers when several RI-induced changes need to
 -- be made to the same row.  This was broken by subtransaction-related
 -- changes in 8.0.