diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 7ee0aa8ca0..2609d4a8ea 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 ] name
ALTER [ COLUMN ] column_name SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
ADD table_constraint [ NOT VALID ]
ADD table_constraint_using_index
+ ALTER CONSTRAINT constraint_name [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
VALIDATE CONSTRAINT constraint_name
DROP CONSTRAINT [ IF EXISTS ] constraint_name [ RESTRICT | CASCADE ]
DISABLE TRIGGER [ trigger_name | ALL | USER ]
@@ -316,6 +317,16 @@ ALTER TABLE [ IF EXISTS ] name
+
+ ALTER CONSTRAINT
+
+
+ This form alters the attributes of a constraint that was previously
+ created. Currently only foreign key constraints may be altered.
+
+
+
+
VALIDATE CONSTRAINT
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8294b29b28..b15cda60f8 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 5094226750..5e7db52ceb 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 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 VALIDATE CONSTRAINT ... */
| VALIDATE CONSTRAINT name
{
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6723647e2e..9453e1dfdf 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 04668a8886..0299bfe873 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 377b36c226..531c881f63 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;