diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 7ee0aa8ca0756675415a2a3b196188b776f276fc..2609d4a8eaf340fa2b725014ac383de3deaa36f8 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -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> diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 8294b29b285124caac3f769641c205383b72c966..b15cda60f8912279ae4a8673074bc02ba1319536 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -275,6 +275,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, @@ -2886,6 +2888,7 @@ AlterTableGetLockLevel(List *cmds) case AT_SetOptions: case AT_ResetOptions: case AT_SetStorage: + case AT_AlterConstraint: case AT_ValidateConstraint: cmd_lockmode = ShareUpdateExclusiveLock; break; @@ -3124,6 +3127,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ATPrepAddInherit(rel); pass = AT_PASS_MISC; break; + case AT_AlterConstraint: /* ALTER CONSTRAINT */ + ATSimplePermissions(rel, ATT_TABLE); + break; case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ ATSimplePermissions(rel, ATT_TABLE); /* Recursion occurs during execution phase */ @@ -3302,6 +3308,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; @@ -6173,6 +6182,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, ©Tuple->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, ©Tuple->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 * diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 509422675001e25ee7f3a2a2b52988473bf8874b..5e7db52cebc94405f5900ca67992ad83f1c27f60 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -1941,6 +1941,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 { diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 6723647e2e397126e6e2ba480068fd40748f1e59..9453e1dfdfa61215a6f205c874210e490bbc33b9 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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 diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 04668a8886cec869cab4466ab51083a5f0276e6e..0299bfe873070b81eb5dd36fd24a37893d09d0d7 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -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 diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 377b36c226b5baa394ca69290f463696ab5e4207..531c881f6318255b272ff1008ed65de356b7f952 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -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;