Avoid primary key lookup (and lock) if foreign key does not change
authorJan Wieck
Mon, 7 Apr 2003 20:30:38 +0000 (20:30 +0000)
committerJan Wieck
Mon, 7 Apr 2003 20:30:38 +0000 (20:30 +0000)
on UPDATE.

This get's rid of the long standing annoyance that updating a row
that has foreign keys locks all the referenced rows even if the
foreign key values do not change.

The trick is to actually do a check identical to NO ACTION after an
eventually done UPDATE in the SET DEFAULT case. Since a SET DEFAULT
operation should have moved referencing rows to a new "home", a following
NO ACTION check can only fail if the column defaults of the referencing
table resulted in the key we actually deleted. Thanks to Stephan.

Jan

src/backend/utils/adt/ri_triggers.c
src/test/regress/expected/foreign_key.out

index bc7c723ea2513bad17fa0569eebb26030a25ec65..2ebf92213be2d5b8b5dd9b91db6c1d2435b84568 100644 (file)
@@ -17,7 +17,7 @@
  *
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.48 2003/03/27 19:25:40 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.49 2003/04/07 20:30:38 wieck Exp $
  *
  * ----------
  */
@@ -395,13 +395,19 @@ RI_FKey_check(PG_FUNCTION_ARGS)
    }
 
    /*
-    * Note: We cannot avoid the check on UPDATE, even if old and new key
-    * are the same. Otherwise, someone could DELETE the PK that consists
-    * of the DEFAULT values, and if there are any references, a ON DELETE
-    * SET DEFAULT action would update the references to exactly these
-    * values but we wouldn't see that weird case (this is the only place
-    * to see it).
+    * No need to check anything if old and new references are the
+    * same on UPDATE.
     */
+   if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+   {
+       if (ri_KeysEqual(fk_rel, old_row, new_row, &qkey,
+                        RI_KEYPAIR_FK_IDX))
+       {
+           heap_close(pk_rel, RowShareLock);
+           return PointerGetDatum(NULL);
+       }
+   }
+
    if (SPI_connect() != SPI_OK_CONNECT)
        elog(ERROR, "SPI_connect() failed in RI_FKey_check()");
 
@@ -2397,6 +2403,16 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 
            heap_close(fk_rel, RowExclusiveLock);
 
+           /*
+            * In the case we delete the row who's key is equal to the
+            * default values AND a referencing row in the foreign key
+            * table exists, we would just have updated it to the same
+            * values. We need to do another lookup now and in case a
+            * reference exists, abort the operation. That is already
+            * implemented in the NO ACTION trigger.
+            */
+           RI_FKey_noaction_del(fcinfo);
+
            return PointerGetDatum(NULL);
 
            /*
@@ -2635,6 +2651,16 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 
            heap_close(fk_rel, RowExclusiveLock);
 
+           /*
+            * In the case we updated the row who's key was equal to the
+            * default values AND a referencing row in the foreign key
+            * table exists, we would just have updated it to the same
+            * values. We need to do another lookup now and in case a
+            * reference exists, abort the operation. That is already
+            * implemented in the NO ACTION trigger.
+            */
+           RI_FKey_noaction_upd(fcinfo);
+
            return PointerGetDatum(NULL);
 
            /*
index 7a0c47a96850a27a87104b8298b94543adaa1744..580762f93bdb33b54296f71f5acdc8a2bab8e323 100644 (file)
@@ -882,7 +882,7 @@ delete from pktable where base1=2;
 ERROR:  $1 referential integrity violation - key (base1,ptest1)=(2,2) in pktable still referenced from pktable
 -- fails (1,1) is being referenced (twice)
 update pktable set base1=3 where base1=1;
-ERROR:  $1 referential integrity violation - key (base2,ptest2)=(1,1) referenced from pktable not found in pktable
+ERROR:  $1 referential integrity violation - key (base1,ptest1)=(1,1) in pktable still referenced from pktable
 -- this sequence of two deletes will work, since after the first there will be no (2,*) references
 delete from pktable where base2=2;
 delete from pktable where base1=2;