ALTER TABLE ... ALTER CONSTRAINT
While fiddling with FK tuning, it was useful to be able to enable and
disable the DEFERRED mode of constraints.
That is not currently possible in SQL, so I wrote this patch. Without
this you have to drop and then re-add a constraint, which is
impractical for large tables.
e.g.
CREATE TABLE fktable (id integer, fk integer REFERENCES pktable (id));
ALTER TABLE foo
ALTER CONSTRAINT fktable_fk_fkey DEFERRED INITIALLY IMMEDIATE;
Includes docs and tests.
Currently works for FKs only. Potentially other constraints can be
supported in future.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
alter_table_alter_constraint.v1.sqlapplication/octet-stream; name=alter_table_alter_constraint.v1.sqlDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5437626..27146bb 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 ]
@@ -317,6 +318,15 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>ALTER CONSTRAINT</literal></term>
+ <listitem>
+ <para>
+ This form alters the attributes of a constraint that was previously created.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>VALIDATE CONSTRAINT</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8294b29..c29b16e 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;
@@ -6174,6 +6183,130 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
}
/*
+ * ALTER TABLE ALTER CONSTRAINT
+ *
+ * Update the attributes of a constraint.
+ */
+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
*
* XXX The reason we handle recursion here rather than at Phase 1 is because
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5094226..5e7db52 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 6723647..9453e1d 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 04668a8..0299bfe 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 377b36c..531c881 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;
On 2013-06-08 21:45:24 +0100, Simon Riggs wrote:
While fiddling with FK tuning, it was useful to be able to enable and
disable the DEFERRED mode of constraints.That is not currently possible in SQL, so I wrote this patch. Without
this you have to drop and then re-add a constraint, which is
impractical for large tables.e.g.
CREATE TABLE fktable (id integer, fk integer REFERENCES pktable (id));ALTER TABLE foo
ALTER CONSTRAINT fktable_fk_fkey DEFERRED INITIALLY IMMEDIATE;Includes docs and tests.
Currently works for FKs only. Potentially other constraints can be
supported in future.
I haven't looked at the patch in detail, but I am very, very much in
favor of the feature in general… I have wished for this more than once,
and it certainly cost me more time working around it than it would have
cost to implement it.
Thanks,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@2ndquadrant.com> wrote:
On 2013-06-08 21:45:24 +0100, Simon Riggs wrote:
ALTER TABLE foo
ALTER CONSTRAINT fktable_fk_fkey DEFERRED INITIALLY IMMEDIATE;
I haven't looked at the patch in detail, but I am very, very much in
favor of the feature in general… I have wished for this more than once
+1
--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@2ndquadrant.com> writes:
I haven't looked at the patch in detail, but I am very, very much in
favor of the feature in general… I have wished for this more than once,
+1
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jun 10, 2013 at 11:06 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr>wrote:
Andres Freund <andres@2ndquadrant.com> writes:
I haven't looked at the patch in detail, but I am very, very much in
favor of the feature in general… I have wished for this more than once,+1
+1. It will be useful.
--
Michael
At 2013-06-08 21:45:24 +0100, simon@2ndQuadrant.com wrote:
ALTER TABLE foo
ALTER CONSTRAINT fktable_fk_fkey DEFERRED INITIALLY IMMEDIATE;
I read the patch (looks good), applied it on HEAD (fine), ran make check
(fine), and played with it in a test database. It looks great, and from
previous responses it's something a lot of people have wished for.
I'm marking this ready for committer.
-- Abhijit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Jun 23, 2013 at 8:58 PM, Abhijit Menon-Sen <ams@2ndquadrant.com>wrote:
At 2013-06-08 21:45:24 +0100, simon@2ndQuadrant.com wrote:
ALTER TABLE foo
ALTER CONSTRAINT fktable_fk_fkey DEFERRED INITIALLY IMMEDIATE;I read the patch (looks good), applied it on HEAD (fine), ran make check
(fine), and played with it in a test database. It looks great, and from
previous responses it's something a lot of people have wished for.I'm marking this ready for committer.
After the commit, I'm now getting the compiler warning:
tablecmds.c: In function 'ATPrepCmd':
tablecmds.c:2953: warning: 'pass' may be used uninitialized in this function
case AT_AlterConstraint (line 3130) is the only case branch that does not
set pass.
Cheers,
Jeff
On 24 June 2013 21:42, Jeff Janes <jeff.janes@gmail.com> wrote:
On Sun, Jun 23, 2013 at 8:58 PM, Abhijit Menon-Sen <ams@2ndquadrant.com>wrote:
At 2013-06-08 21:45:24 +0100, simon@2ndQuadrant.com wrote:
ALTER TABLE foo
ALTER CONSTRAINT fktable_fk_fkey DEFERRED INITIALLY IMMEDIATE;I read the patch (looks good), applied it on HEAD (fine), ran make check
(fine), and played with it in a test database. It looks great, and from
previous responses it's something a lot of people have wished for.I'm marking this ready for committer.
After the commit, I'm now getting the compiler warning:
tablecmds.c: In function 'ATPrepCmd':
tablecmds.c:2953: warning: 'pass' may be used uninitialized in this
functioncase AT_AlterConstraint (line 3130) is the only case branch that does not
set pass.
The investigation is into why my current compiler didn't report that.
Thanks though.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
On 24 June 2013 22:17, Simon Riggs <simon@2ndquadrant.com> wrote:
On 24 June 2013 21:42, Jeff Janes <jeff.janes@gmail.com> wrote:
On Sun, Jun 23, 2013 at 8:58 PM, Abhijit Menon-Sen <ams@2ndquadrant.com>wrote:
At 2013-06-08 21:45:24 +0100, simon@2ndQuadrant.com wrote:
ALTER TABLE foo
ALTER CONSTRAINT fktable_fk_fkey DEFERRED INITIALLY IMMEDIATE;I read the patch (looks good), applied it on HEAD (fine), ran make check
(fine), and played with it in a test database. It looks great, and from
previous responses it's something a lot of people have wished for.I'm marking this ready for committer.
After the commit, I'm now getting the compiler warning:
tablecmds.c: In function 'ATPrepCmd':
tablecmds.c:2953: warning: 'pass' may be used uninitialized in this
functioncase AT_AlterConstraint (line 3130) is the only case branch that does not
set pass.The investigation is into why my current compiler didn't report that.
Thanks though.
Looks like that really is a deficiency in my tool chain on OSX, rather than
some bug/user error. Even at the very latest, very shiny version.
Latest versions of gcc trap the error, so I'll have to investigate an
alternative.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Simon Riggs escribi�:
Looks like that really is a deficiency in my tool chain on OSX, rather than
some bug/user error. Even at the very latest, very shiny version.Latest versions of gcc trap the error, so I'll have to investigate an
alternative.
Funnily enough, on Debian Wheezy with gcc 4.7.2 I don't get the warning,
and Andres with gcc 4.7.3 (from Debian unstable) does see it. (Of
course, the 4.8 version shipped with unstable also shows it.)
Clang similarly requires pretty new versions to show the warning.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers