]> granicus.if.org Git - postgresql/commitdiff
ALTER TABLE ... ALTER CONSTRAINT for FKs
authorSimon Riggs <simon@2ndQuadrant.com>
Fri, 28 Jun 2013 23:27:30 +0000 (00:27 +0100)
committerSimon Riggs <simon@2ndQuadrant.com>
Fri, 28 Jun 2013 23:27:30 +0000 (00:27 +0100)
Allow constraint attributes to be altered,
so the default setting of NOT DEFERRABLE
can be altered to DEFERRABLE and back.

Review by Abhijit Menon-Sen

doc/src/sgml/ref/alter_table.sgml
src/backend/commands/tablecmds.c
src/backend/parser/gram.y
src/include/nodes/parsenodes.h
src/test/regress/expected/foreign_key.out
src/test/regress/sql/foreign_key.sql

index 7ee0aa8ca0756675415a2a3b196188b776f276fc..2609d4a8eaf340fa2b725014ac383de3deaa36f8 100644 (file)
@@ -46,6 +46,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
     ADD <replaceable class="PARAMETER">table_constraint</replaceable> [ NOT VALID ]
     ADD <replaceable class="PARAMETER">table_constraint_using_index</replaceable>
+    ALTER CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
     VALIDATE CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable>
     DROP CONSTRAINT [ IF EXISTS ]  <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
     DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
@@ -316,6 +317,16 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ALTER CONSTRAINT</literal></term>
+    <listitem>
+     <para>
+      This form alters the attributes of a constraint that was previously
+      created. Currently only foreign key constraints may be altered.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>VALIDATE CONSTRAINT</literal></term>
     <listitem>
index 50341f6ef9ade55945b5234a1dd7038eb3ff36d9..ea1c309add82916e6f07e4f4437a3e07ba49a641 100644 (file)
@@ -276,6 +276,8 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel,
 static void AlterSeqNamespaces(Relation classRel, Relation rel,
                                   Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
                                   LOCKMODE lockmode);
+static void ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
+                                                bool recurse, bool recursing, LOCKMODE lockmode);
 static void ATExecValidateConstraint(Relation rel, char *constrName,
                                                 bool recurse, bool recursing, LOCKMODE lockmode);
 static int transformColumnNameList(Oid relId, List *colList,
@@ -2887,6 +2889,7 @@ AlterTableGetLockLevel(List *cmds)
                        case AT_SetOptions:
                        case AT_ResetOptions:
                        case AT_SetStorage:
+                       case AT_AlterConstraint:
                        case AT_ValidateConstraint:
                                cmd_lockmode = ShareUpdateExclusiveLock;
                                break;
@@ -3125,6 +3128,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
                        ATPrepAddInherit(rel);
                        pass = AT_PASS_MISC;
                        break;
+               case AT_AlterConstraint:                /* ALTER CONSTRAINT */
+                       ATSimplePermissions(rel, ATT_TABLE);
+                       pass = AT_PASS_MISC;
+                       break;
                case AT_ValidateConstraint:             /* VALIDATE CONSTRAINT */
                        ATSimplePermissions(rel, ATT_TABLE);
                        /* Recursion occurs during execution phase */
@@ -3304,6 +3311,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                case AT_AddIndexConstraint:             /* ADD CONSTRAINT USING INDEX */
                        ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
                        break;
+               case AT_AlterConstraint:                /* ALTER CONSTRAINT */
+                       ATExecAlterConstraint(rel, cmd, false, false, lockmode);
+                       break;
                case AT_ValidateConstraint:             /* VALIDATE CONSTRAINT */
                        ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
                        break;
@@ -6175,6 +6185,135 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
        heap_close(pkrel, NoLock);
 }
 
+/*
+ * ALTER TABLE ALTER CONSTRAINT
+ *
+ * Update the attributes of a constraint.
+ *
+ * Currently only works for Foreign Key constraints.
+ * Foreign keys do not inherit, so we purposely ignore the
+ * recursion bit here, but we keep the API the same for when
+ * other constraint types are supported.
+ */
+static void
+ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
+                                                bool recurse, bool recursing, LOCKMODE lockmode)
+{
+       Relation        conrel;
+       SysScanDesc scan;
+       ScanKeyData key;
+       HeapTuple       contuple;
+       Form_pg_constraint currcon = NULL;
+       Constraint      *cmdcon = NULL;
+       bool            found = false;
+
+       Assert(IsA(cmd->def, Constraint));
+       cmdcon = (Constraint *) cmd->def;
+
+       conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+       /*
+        * Find and check the target constraint
+        */
+       ScanKeyInit(&key,
+                               Anum_pg_constraint_conrelid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(RelationGetRelid(rel)));
+       scan = systable_beginscan(conrel, ConstraintRelidIndexId,
+                                                         true, SnapshotNow, 1, &key);
+
+       while (HeapTupleIsValid(contuple = systable_getnext(scan)))
+       {
+               currcon = (Form_pg_constraint) GETSTRUCT(contuple);
+               if (strcmp(NameStr(currcon->conname), cmdcon->conname) == 0)
+               {
+                       found = true;
+                       break;
+               }
+       }
+
+       if (!found)
+               ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                errmsg("constraint \"%s\" of relation \"%s\" does not exist",
+                                               cmdcon->conname, RelationGetRelationName(rel))));
+
+       if (currcon->contype != CONSTRAINT_FOREIGN)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
+                                               cmdcon->conname, RelationGetRelationName(rel))));
+
+       if (currcon->condeferrable != cmdcon->deferrable ||
+               currcon->condeferred != cmdcon->initdeferred)
+       {
+               HeapTuple       copyTuple;
+               HeapTuple       tgtuple;
+               Form_pg_constraint copy_con;
+               Form_pg_trigger copy_tg;
+               ScanKeyData tgkey;
+               SysScanDesc tgscan;
+               Relation        tgrel;
+
+               /*
+                * Now update the catalog, while we have the door open.
+                */
+               copyTuple = heap_copytuple(contuple);
+               copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
+               copy_con->condeferrable = cmdcon->deferrable;
+               copy_con->condeferred = cmdcon->initdeferred;
+               simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
+               CatalogUpdateIndexes(conrel, copyTuple);
+
+               InvokeObjectPostAlterHook(ConstraintRelationId,
+                                                                 HeapTupleGetOid(contuple), 0);
+
+               heap_freetuple(copyTuple);
+
+               /*
+                * Now we need to update the multiple entries in pg_trigger
+                * that implement the constraint.
+                */
+               tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
+
+               ScanKeyInit(&tgkey,
+                                       Anum_pg_trigger_tgconstraint,
+                                       BTEqualStrategyNumber, F_OIDEQ,
+                                       ObjectIdGetDatum(HeapTupleGetOid(contuple)));
+
+               tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
+                                                                       SnapshotNow, 1, &tgkey);
+
+               while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
+               {
+                       copyTuple = heap_copytuple(tgtuple);
+                       copy_tg = (Form_pg_trigger) GETSTRUCT(copyTuple);
+                       copy_tg->tgdeferrable = cmdcon->deferrable;
+                       copy_tg->tginitdeferred = cmdcon->initdeferred;
+                       simple_heap_update(tgrel, &copyTuple->t_self, copyTuple);
+                       CatalogUpdateIndexes(tgrel, copyTuple);
+
+                       InvokeObjectPostAlterHook(TriggerRelationId,
+                                                                               HeapTupleGetOid(tgtuple), 0);
+
+                       heap_freetuple(copyTuple);
+               }
+
+               systable_endscan(tgscan);
+
+               heap_close(tgrel, RowExclusiveLock);
+
+               /*
+                * Invalidate relcache so that others see the new attributes.
+                */
+               CacheInvalidateRelcache(rel);
+       }
+
+       systable_endscan(scan);
+
+       heap_close(conrel, RowExclusiveLock);
+}
+
 /*
  * ALTER TABLE VALIDATE CONSTRAINT
  *
index c41f1b512f6b3a1da605bcfce37583c98aad6512..0fc5b13d015bd6b80293914213e6e2f4cf2af68e 100644 (file)
@@ -1943,6 +1943,21 @@ alter_table_cmd:
                                        n->def = $2;
                                        $$ = (Node *)n;
                                }
+                       /* ALTER TABLE <name> ALTER CONSTRAINT ... */
+                       | ALTER CONSTRAINT name ConstraintAttributeSpec
+                               {
+                                       AlterTableCmd *n = makeNode(AlterTableCmd);
+                                       Constraint *c = makeNode(Constraint);
+                                       n->subtype = AT_AlterConstraint;
+                                       n->def = (Node *) c;
+                                       c->contype = CONSTR_FOREIGN; /* others not supported, yet */
+                                       c->conname = $3;
+                                       processCASbits($4, @4, "ALTER CONSTRAINT statement",
+                                                                       &c->deferrable,
+                                                                       &c->initdeferred,
+                                                                       NULL, NULL, yyscanner);
+                                       $$ = (Node *)n;
+                               }
                        /* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
                        | VALIDATE CONSTRAINT name
                                {
index 6723647e2e397126e6e2ba480068fd40748f1e59..9453e1dfdfa61215a6f205c874210e490bbc33b9 100644 (file)
@@ -1209,6 +1209,7 @@ typedef enum AlterTableType
        AT_AddConstraint,                       /* add constraint */
        AT_AddConstraintRecurse,        /* internal to commands/tablecmds.c */
        AT_ReAddConstraint,                     /* internal to commands/tablecmds.c */
+       AT_AlterConstraint,                     /* alter constraint */
        AT_ValidateConstraint,          /* validate constraint */
        AT_ValidateConstraintRecurse,           /* internal to commands/tablecmds.c */
        AT_ProcessedConstraint,         /* pre-processed add constraint (local in
index 04668a8886cec869cab4466ab51083a5f0276e6e..0299bfe873070b81eb5dd36fd24a37893d09d0d7 100644 (file)
@@ -1132,6 +1132,15 @@ 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
@@ -1142,6 +1151,16 @@ 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
index 377b36c226b5baa394ca69290f463696ab5e4207..531c881f6318255b272ff1008ed65de356b7f952 100644 (file)
@@ -818,6 +818,13 @@ 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;
@@ -831,6 +838,19 @@ 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;