Hi all

Attached is a patch to implement ALTER TABLE ... ALTER CONSTRAINT ...
SET DEFERRABLE on UNIQUE or PRIMARY KEY constraints.

Currently only FOREIGN KEY constraints are supported. Others are rejected with:

constraint \"%s\" of relation \"%s\" is not a foreign key constraint

The patch also adds some regression tests for DEFERRABLE constraints.

The ALTER doesn't take effect in the session it's run in, which makes
me suspect I need to do additional cache invalidations - maybe the
index backing the constraint? Anyway, posted here as-is because I'm
out of time for now and it might be useful for someone who's looking
for info on this.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services
From c0a041f0ca5d884842820538b56d82472a701c3c Mon Sep 17 00:00:00 2001
From: Craig Ringer <cr...@2ndquadrant.com>
Date: Fri, 26 Jun 2015 11:59:30 +0800
Subject: [PATCH] Allow ALTER TABLE...ALTER CONSTRAINT on PK and UNIQUE

We presently support DEFERRABLE constraints on PRIMARY KEY and UNIQUE
constraints, but do not permit ALTER TABLE to modify their deferrable
state.

Fix that and add some regression test coverage.
---
 src/backend/commands/tablecmds.c     | 72 +++++++++++++++++++-----------------
 src/test/regress/sql/alter_table.sql | 37 ++++++++++++++++++
 2 files changed, 76 insertions(+), 33 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d394713..5875987 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6647,22 +6647,20 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 				 errmsg("constraint \"%s\" of relation \"%s\" does not exist",
 						cmdcon->conname, RelationGetRelationName(rel))));
 
-	if (currcon->contype != CONSTRAINT_FOREIGN)
+	if (currcon->contype != CONSTRAINT_FOREIGN &&
+		currcon->contype != CONSTRAINT_PRIMARY &&
+		currcon->contype != CONSTRAINT_UNIQUE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
+				 errmsg("constraint \"%s\" of relation \"%s\" must be FOREIGN KEY, PRIMARY KEY or UNIQUE",
 						cmdcon->conname, RelationGetRelationName(rel))));
 
 	if (currcon->condeferrable != cmdcon->deferrable ||
 		currcon->condeferred != cmdcon->initdeferred)
 	{
 		HeapTuple	copyTuple;
-		HeapTuple	tgtuple;
 		Form_pg_constraint copy_con;
 		List	   *otherrelids = NIL;
-		ScanKeyData tgkey;
-		SysScanDesc tgscan;
-		Relation	tgrel;
 		ListCell   *lc;
 
 		/*
@@ -6682,44 +6680,52 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 
 		/*
 		 * Now we need to update the multiple entries in pg_trigger that
-		 * implement the constraint.
+		 * implement the constraint if it's a foreign key constraint.
 		 */
-		tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
+		if (currcon->contype == CONSTRAINT_FOREIGN)
+		{
+			HeapTuple	tgtuple;
+			ScanKeyData tgkey;
+			SysScanDesc tgscan;
+			Relation	tgrel;
 
-		ScanKeyInit(&tgkey,
-					Anum_pg_trigger_tgconstraint,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(HeapTupleGetOid(contuple)));
+			tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
 
-		tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
-									NULL, 1, &tgkey);
+			ScanKeyInit(&tgkey,
+						Anum_pg_trigger_tgconstraint,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(HeapTupleGetOid(contuple)));
 
-		while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger copy_tg;
+			tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
+										NULL, 1, &tgkey);
 
-			copyTuple = heap_copytuple(tgtuple);
-			copy_tg = (Form_pg_trigger) GETSTRUCT(copyTuple);
+			while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
+			{
+				Form_pg_trigger copy_tg;
 
-			/* 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);
+				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);
+				/* 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);
 
-			InvokeObjectPostAlterHook(TriggerRelationId,
-									  HeapTupleGetOid(tgtuple), 0);
+				copy_tg->tgdeferrable = cmdcon->deferrable;
+				copy_tg->tginitdeferred = cmdcon->initdeferred;
+				simple_heap_update(tgrel, &copyTuple->t_self, copyTuple);
+				CatalogUpdateIndexes(tgrel, copyTuple);
 
-			heap_freetuple(copyTuple);
-		}
+				InvokeObjectPostAlterHook(TriggerRelationId,
+										  HeapTupleGetOid(tgtuple), 0);
+
+				heap_freetuple(copyTuple);
+			}
 
-		systable_endscan(tgscan);
+			systable_endscan(tgscan);
 
-		heap_close(tgrel, RowExclusiveLock);
+			heap_close(tgrel, RowExclusiveLock);
+		}
 
 		/*
 		 * Invalidate relcache so that others see the new attributes.  We must
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 29c1875..ec31597 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1688,3 +1688,40 @@ ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
 DROP TABLE logged3;
 DROP TABLE logged2;
 DROP TABLE logged1;
+-- Make sure that ALTERing a table to make a PK or UNIQUE constraint deferrable works
+CREATE TABLE pkuk(
+   pk INTEGER PRIMARY KEY,
+   uk INTEGER,
+   CONSTRAINT pkuk_unique_uk UNIQUE(uk)
+);
+INSERT INTO pkuk(pk, uk) VALUES (1,1), (2,2);
+ALTER TABLE pkuk ALTER CONSTRAINT pkuk_pkey DEFERRABLE;
+ALTER TABLE pkuk ALTER CONSTRAINT pkuk_unique_uk DEFERRABLE;
+\d pkuk
+BEGIN;
+INSERT INTO pkuk(pk,uk) VALUES (2,2);
+COMMIT;
+BEGIN;
+SET CONSTRAINTS ALL DEFERRED;
+INSERT INTO pkuk(pk,uk) VALUES (2,2);
+COMMIT;
+ALTER TABLE pkuk ALTER CONSTRAINT pkuk_pkey DEFERRABLE INITIALLY DEFERRED;
+ALTER TABLE pkuk ALTER CONSTRAINT pkuk_unique_uk DEFERRABLE INITIALLY DEFERRED;
+\d pkuk
+BEGIN;
+INSERT INTO pkuk(pk,uk) VALUES (2,2);
+COMMIT;
+BEGIN;
+SET CONSTRAINTS ALL IMMEDIATE;
+INSERT INTO pkuk(pk,uk) VALUES (2,2);
+COMMIT;
+ALTER TABLE pkuk ALTER CONSTRAINT pkuk_pkey DEFERRABLE INITIALLY IMMEDIATE;
+ALTER TABLE pkuk ALTER CONSTRAINT pkuk_unique_uk DEFERRABLE INITIALLY IMMEDIATE;
+\d pkuk
+BEGIN;
+INSERT INTO pkuk(pk,uk) VALUES (2,2);
+COMMIT;
+BEGIN;
+SET CONSTRAINTS ALL DEFERRED;
+INSERT INTO pkuk(pk,uk) VALUES (2,2);
+COMMIT;
-- 
2.1.0

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to