Thread: skip FK trigger on UPDATE

skip FK trigger on UPDATE

From
Neil Conway
Date:
This patch implements an idea discussed on -hackers recently: if an
UPDATE on a table with a foreign key does not modify any of the table's
foreign key columns, we can avoid enqueueing the foreign queue check
trigger.

I basically just moved the logic for the "are the keys equal?" test from
the FK trigger itself into the code that enqueues the trigger. I then
removed the keys-are-equal check from the FK trigger. I also had to
change (somewhat awkwardly) RI_FKey_keyequal_upd() to work for updates
on either the PK table or the FK table. I also removed the bogus
TriggerData argument from RI_FKey_keyequal_upd(), since AFAICS it is no
needed and merely adds confusion.

This patch does cause one change to the regression test output:

*** ./expected/foreign_key.out    Fri May 27 23:58:54 2005
--- ./results/foreign_key.out    Sat May 28 00:46:20 2005
***************
*** 911,918 ****
   DETAIL:  Key (base1,ptest1)=(2,2) is still referenced from table
"pktable".
   -- fails (1,1) is being referenced (twice)
   update pktable set base1=3 where base1=1;
! ERROR:  insert or update on table "pktable" violates foreign key
constraint "pktable_base2_fkey"
! DETAIL:  Key (base2,ptest2)=(1,1) is not present in table "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;
--- 911,918 ----
   DETAIL:  Key (base1,ptest1)=(2,2) is still referenced from table
"pktable".
   -- fails (1,1) is being referenced (twice)
   update pktable set base1=3 where base1=1;
! ERROR:  update or delete on "pktable" violates foreign key constraint
"pktable_base2_fkey" on "pktable"
! DETAIL:  Key (base1,ptest1)=(1,1) is still referenced from table
"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;

I found this a bit strange: on the one hand I think the new error
message is actually more sensible, but I'm not sure what caused the
change in behavior. I'll have more of a think about this tomorrow.

-Neil
Index: src/backend/commands/tablecmds.c
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/backend/commands/tablecmds.c,v
retrieving revision 1.157
diff -c -r1.157 tablecmds.c
*** src/backend/commands/tablecmds.c    10 May 2005 13:16:26 -0000    1.157
--- src/backend/commands/tablecmds.c    27 May 2005 13:58:54 -0000
***************
*** 1596,1603 ****
          case F_RI_FKEY_NOACTION_UPD:
              return RI_TRIGGER_PK;

!         case F_RI_FKEY_CHECK_INS:
!         case F_RI_FKEY_CHECK_UPD:
              return RI_TRIGGER_FK;
      }

--- 1596,1602 ----
          case F_RI_FKEY_NOACTION_UPD:
              return RI_TRIGGER_PK;

!         case F_RI_FKEY_CHECK:
              return RI_TRIGGER_FK;
      }

***************
*** 4305,4313 ****
          return;

      /*
!      * Scan through each tuple, calling RI_FKey_check_ins (insert trigger)
!      * as if that tuple had just been inserted.  If any of those fail, it
!      * should ereport(ERROR) and that's that.
       */
      MemSet(&trig, 0, sizeof(trig));
      trig.tgoid = InvalidOid;
--- 4304,4312 ----
          return;

      /*
!      * Scan through each tuple, calling RI_FKey_check (insert trigger)
!      * as if that tuple had just been inserted.  If any of those fail,
!      * it should ereport(ERROR) and that's that.
       */
      MemSet(&trig, 0, sizeof(trig));
      trig.tgoid = InvalidOid;
***************
*** 4359,4365 ****
          MemSet(&fcinfo, 0, sizeof(fcinfo));

          /*
!          * We assume RI_FKey_check_ins won't look at flinfo...
           */
          trigdata.type = T_TriggerData;
          trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
--- 4358,4364 ----
          MemSet(&fcinfo, 0, sizeof(fcinfo));

          /*
!          * We assume RI_FKey_check won't look at flinfo...
           */
          trigdata.type = T_TriggerData;
          trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
***************
*** 4372,4378 ****

          fcinfo.context = (Node *) &trigdata;

!         RI_FKey_check_ins(&fcinfo);
      }

      heap_endscan(scan);
--- 4371,4377 ----

          fcinfo.context = (Node *) &trigdata;

!         RI_FKey_check(&fcinfo);
      }

      heap_endscan(scan);
***************
*** 4420,4426 ****
      fk_trigger = makeNode(CreateTrigStmt);
      fk_trigger->trigname = fkconstraint->constr_name;
      fk_trigger->relation = myRel;
!     fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
      fk_trigger->before = false;
      fk_trigger->row = true;
      fk_trigger->actions[0] = 'i';
--- 4419,4425 ----
      fk_trigger = makeNode(CreateTrigStmt);
      fk_trigger->trigname = fkconstraint->constr_name;
      fk_trigger->relation = myRel;
!     fk_trigger->funcname = SystemFuncName("RI_FKey_check");
      fk_trigger->before = false;
      fk_trigger->row = true;
      fk_trigger->actions[0] = 'i';
Index: src/backend/commands/trigger.c
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/backend/commands/trigger.c,v
retrieving revision 1.188
diff -c -r1.188 trigger.c
*** src/backend/commands/trigger.c    6 May 2005 17:24:53 -0000    1.188
--- src/backend/commands/trigger.c    29 May 2005 13:10:25 -0000
***************
*** 111,117 ****
          bool        needconstrrelid = false;
          void       *elem = NULL;

!         if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_check_", 14) == 0)
          {
              /* A trigger on FK table. */
              needconstrrelid = true;
--- 111,117 ----
          bool        needconstrrelid = false;
          void       *elem = NULL;

!         if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_check", 13) == 0)
          {
              /* A trigger on FK table. */
              needconstrrelid = true;
***************
*** 2994,3009 ****
              continue;

          /*
!          * If it is an RI UPDATE trigger, and the referenced keys have
!          * not changed, short-circuit queuing of the event; there's no
!          * need to fire the trigger.
           */
          if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
          {
!             bool        is_ri_trigger;

              switch (trigger->tgfoid)
              {
                  case F_RI_FKEY_NOACTION_UPD:
                  case F_RI_FKEY_CASCADE_UPD:
                  case F_RI_FKEY_RESTRICT_UPD:
--- 2994,3010 ----
              continue;

          /*
!          * If this is an UPDATE of a PK table or FK table that does
!          * not change the PK or FK respectively, we can skip queuing
!          * the event: there is no need to fire the trigger.
           */
          if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
          {
!             bool        is_ri_trigger = false;

              switch (trigger->tgfoid)
              {
+                 case F_RI_FKEY_CHECK:
                  case F_RI_FKEY_NOACTION_UPD:
                  case F_RI_FKEY_CASCADE_UPD:
                  case F_RI_FKEY_RESTRICT_UPD:
***************
*** 3011,3043 ****
                  case F_RI_FKEY_SETDEFAULT_UPD:
                      is_ri_trigger = true;
                      break;
-
-                 default:
-                     is_ri_trigger = false;
-                     break;
              }

              if (is_ri_trigger)
              {
!                 TriggerData LocTriggerData;
!
!                 LocTriggerData.type = T_TriggerData;
!                 LocTriggerData.tg_event =
!                     TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW;
!                 LocTriggerData.tg_relation = rel;
!                 LocTriggerData.tg_trigtuple = oldtup;
!                 LocTriggerData.tg_newtuple = newtup;
!                 LocTriggerData.tg_trigger = trigger;
!                 /*
!                  * We do not currently know which buffers the passed tuples
!                  * are in, but it does not matter because RI_FKey_keyequal_upd
!                  * does not care.  We could expand the API of this function
!                  * if it becomes necessary to set these fields accurately.
!                  */
!                 LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
!                 LocTriggerData.tg_newtuplebuf = InvalidBuffer;
!
!                 if (RI_FKey_keyequal_upd(&LocTriggerData))
                  {
                      /* key unchanged, so skip queuing this event */
                      continue;
--- 3012,3035 ----
                  case F_RI_FKEY_SETDEFAULT_UPD:
                      is_ri_trigger = true;
                      break;
              }

+             /*
+              * There is one exception when updating FK tables: if the
+              * updated row was inserted by our own transaction and the
+              * FK is deferred, we still need to fire the trigger. This
+              * is because our UPDATE will invalidate the INSERT so the
+              * end-of-transaction INSERT RI trigger will not do
+              * anything, so we have to do the check for the UPDATE
+              * anyway.
+              */
+             if (trigger->tgfoid == F_RI_FKEY_CHECK &&
+                 HeapTupleHeaderGetXmin(oldtup->t_data) == GetCurrentTransactionId())
+                 is_ri_trigger = false;
+
              if (is_ri_trigger)
              {
!                 if (RI_FKey_keyequal_upd(trigger, rel, oldtup, newtup))
                  {
                      /* key unchanged, so skip queuing this event */
                      continue;
Index: src/backend/utils/adt/ri_triggers.c
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/backend/utils/adt/ri_triggers.c,v
retrieving revision 1.78
diff -c -r1.78 ri_triggers.c
*** src/backend/utils/adt/ri_triggers.c    29 May 2005 04:23:05 -0000    1.78
--- src/backend/utils/adt/ri_triggers.c    29 May 2005 13:22:09 -0000
***************
*** 38,47 ****
  #include "optimizer/planmain.h"
  #include "parser/parse_oper.h"
  #include "rewrite/rewriteHandler.h"
- #include "utils/lsyscache.h"
- #include "utils/typcache.h"
  #include "utils/acl.h"
  #include "utils/guc.h"
  #include "miscadmin.h"


--- 38,48 ----
  #include "optimizer/planmain.h"
  #include "parser/parse_oper.h"
  #include "rewrite/rewriteHandler.h"
  #include "utils/acl.h"
+ #include "utils/fmgroids.h"
  #include "utils/guc.h"
+ #include "utils/lsyscache.h"
+ #include "utils/typcache.h"
  #include "miscadmin.h"


***************
*** 176,182 ****
   *    Check foreign key existence (combined for INSERT and UPDATE).
   * ----------
   */
! static Datum
  RI_FKey_check(PG_FUNCTION_ARGS)
  {
      TriggerData *trigdata = (TriggerData *) fcinfo->context;
--- 177,183 ----
   *    Check foreign key existence (combined for INSERT and UPDATE).
   * ----------
   */
! Datum
  RI_FKey_check(PG_FUNCTION_ARGS)
  {
      TriggerData *trigdata = (TriggerData *) fcinfo->context;
***************
*** 375,396 ****
              break;
      }

-     /*
-      * No need to check anything if old and new references are the same on
-      * UPDATE.
-      */
-     if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-     {
-         if (HeapTupleHeaderGetXmin(old_row->t_data) !=
-             GetCurrentTransactionId() &&
-             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");

--- 376,381 ----
***************
*** 455,486 ****


  /* ----------
-  * RI_FKey_check_ins -
-  *
-  *    Check foreign key existence at insert event on FK table.
-  * ----------
-  */
- Datum
- RI_FKey_check_ins(PG_FUNCTION_ARGS)
- {
-     return RI_FKey_check(fcinfo);
- }
-
-
- /* ----------
-  * RI_FKey_check_upd -
-  *
-  *    Check foreign key existence at update event on FK table.
-  * ----------
-  */
- Datum
- RI_FKey_check_upd(PG_FUNCTION_ARGS)
- {
-     return RI_FKey_check(fcinfo);
- }
-
-
- /* ----------
   * ri_Check_Pk_Match
   *
   *    Check for matching value of old pk row in current state for
--- 440,445 ----
***************
*** 2005,2012 ****
                       * corresponding to changed columns in pk_rel's key
                       */
                      if (match_type == RI_MATCH_TYPE_FULL ||
!                       !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
!                                       RI_KEYPAIR_PK_IDX))
                      {
                          snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
                                   querysep, attname);
--- 1964,1971 ----
                       * corresponding to changed columns in pk_rel's key
                       */
                      if (match_type == RI_MATCH_TYPE_FULL ||
!                         !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
!                                         RI_KEYPAIR_PK_IDX))
                      {
                          snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
                                   querysep, attname);
***************
*** 2016,2022 ****
                               qualsep, attname, i + 1);
                      qualsep = "AND";
                      queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
!                                      qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
                  }
                  strcat(querystr, qualstr);

--- 1975,1981 ----
                               qualsep, attname, i + 1);
                      qualsep = "AND";
                      queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
!                                                  qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
                  }
                  strcat(querystr, qualstr);

***************
*** 2453,2480 ****
  /* ----------
   * RI_FKey_keyequal_upd -
   *
!  *    Check if we have a key change on update.
!  *
!  *    This is not a real trigger procedure. It is used by the AFTER
   *    trigger queue manager to detect "triggered data change violation".
   * ----------
   */
  bool
! RI_FKey_keyequal_upd(TriggerData *trigdata)
  {
      int            tgnargs;
      char      **tgargs;
      Relation    fk_rel;
      Relation    pk_rel;
-     HeapTuple    new_row;
-     HeapTuple    old_row;
      RI_QueryKey qkey;

      /*
       * Check for the correct # of call arguments
       */
!     tgnargs = trigdata->tg_trigger->tgnargs;
!     tgargs = trigdata->tg_trigger->tgargs;
      if (tgnargs < 4 ||
          tgnargs > RI_MAX_ARGUMENTS ||
          (tgnargs % 2) != 0)
--- 2412,2437 ----
  /* ----------
   * RI_FKey_keyequal_upd -
   *
!  *    Check if we have a key change on update. It is used by the AFTER
   *    trigger queue manager to detect "triggered data change violation".
   * ----------
   */
  bool
! RI_FKey_keyequal_upd(Trigger *trigger, Relation rel, HeapTuple old_row, HeapTuple new_row)
  {
      int            tgnargs;
      char      **tgargs;
      Relation    fk_rel;
      Relation    pk_rel;
      RI_QueryKey qkey;
+     bool        fk_update;
+     bool        result;

      /*
       * Check for the correct # of call arguments
       */
!     tgnargs = trigger->tgnargs;
!     tgargs = trigger->tgargs;
      if (tgnargs < 4 ||
          tgnargs > RI_MAX_ARGUMENTS ||
          (tgnargs % 2) != 0)
***************
*** 2490,2548 ****
          return true;

      /*
!      * Get the relation descriptors of the FK and PK tables and the new
!      * and old tuple.
!      *
!      * Use minimal locking for fk_rel here.
       */
!     if (!OidIsValid(trigdata->tg_trigger->tgconstrrelid))
          ereport(ERROR,
                  (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
!         errmsg("no target table given for trigger \"%s\" on table \"%s\"",
!                trigdata->tg_trigger->tgname,
!                RelationGetRelationName(trigdata->tg_relation)),
!                  errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD
CONSTRAINT.")));
!
!     fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, AccessShareLock);
!     pk_rel = trigdata->tg_relation;
!     new_row = trigdata->tg_newtuple;
!     old_row = trigdata->tg_trigtuple;
!
!     switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
      {
!             /*
!              * MATCH <UNSPECIFIED>
!              */
!         case RI_MATCH_TYPE_UNSPECIFIED:
!         case RI_MATCH_TYPE_FULL:
!             ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
!                                  RI_PLAN_KEYEQUAL_UPD,
!                                  fk_rel, pk_rel,
!                                  tgnargs, tgargs);
!
!             heap_close(fk_rel, AccessShareLock);

!             /*
!              * Return if key's are equal
!              */
!             return ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
!                                 RI_KEYPAIR_PK_IDX);

!             /*
!              * Handle MATCH PARTIAL set null delete.
!              */
!         case RI_MATCH_TYPE_PARTIAL:
!             ereport(ERROR,
!                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                      errmsg("MATCH PARTIAL not yet implemented")));
!             break;
!     }

      /*
!      * Never reached
       */
!     elog(ERROR, "invalid match_type");
!     return false;
  }


--- 2447,2499 ----
          return true;

      /*
!      * This can be invoked for both UPDATEs to a PK table or an FK
!      * table. "rel" always refers to the relation on which the UPDATE
!      * occurred.
       */
!     if (!OidIsValid(trigger->tgconstrrelid))
          ereport(ERROR,
                  (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
!                  errmsg("no target table given for trigger \"%s\" on table \"%s\"",
!                         trigger->tgname,
!                         RelationGetRelationName(rel)),
!                  errhint("Remove this referential integrity trigger and its mates, "
!                          "then do ALTER TABLE ADD CONSTRAINT.")));
!
!     if (trigger->tgfoid == F_RI_FKEY_CHECK)
!     {
!         /* Update on FK table */
!         fk_update = true;
!         fk_rel = rel;
!         pk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock);
!     }
!     else
      {
!         /* Update on PK table */
!         fk_update = false;
!         pk_rel = rel;
!         fk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock);
!     }

!     if (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]) == RI_MATCH_TYPE_PARTIAL)
!         ereport(ERROR,
!                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
!                  errmsg("MATCH PARTIAL not yet implemented")));

!     /* MATCH <UNSPECIFIED> and MATCH <FULL> */
!     ri_BuildQueryKeyFull(&qkey, trigger->tgoid, RI_PLAN_KEYEQUAL_UPD,
!                          fk_rel, pk_rel, tgnargs, tgargs);

      /*
!      * Return if keys are equal
       */
!     result = ri_KeysEqual(rel, old_row, new_row, &qkey,
!                           fk_update ? RI_KEYPAIR_FK_IDX : RI_KEYPAIR_PK_IDX);
!     if (fk_update)
!         heap_close(pk_rel, AccessShareLock);
!     else
!         heap_close(fk_rel, AccessShareLock);
!     return result;
  }


***************
*** 2871,2877 ****
      /*
       * Initialize the key and fill in type, oid's and number of keypairs
       */
!     memset((void *) key, 0, sizeof(RI_QueryKey));
      key->constr_type = RI_MATCH_TYPE_FULL;
      key->constr_id = constr_id;
      key->constr_queryno = constr_queryno;
--- 2822,2828 ----
      /*
       * Initialize the key and fill in type, oid's and number of keypairs
       */
!     memset(key, 0, sizeof(RI_QueryKey));
      key->constr_type = RI_MATCH_TYPE_FULL;
      key->constr_id = constr_id;
      key->constr_queryno = constr_queryno;
***************
*** 3489,3495 ****
      for (i = 0; i < key->nkeypairs; i++)
      {
          /*
!          * Get one attributes oldvalue. If it is NULL - they're not equal.
           */
          oldvalue = SPI_getbinval(oldtup, rel->rd_att,
                                   key->keypair[i][pairidx], &isnull);
--- 3440,3446 ----
      for (i = 0; i < key->nkeypairs; i++)
      {
          /*
!          * Get one attribute's oldvalue. If it is NULL - they're not equal.
           */
          oldvalue = SPI_getbinval(oldtup, rel->rd_att,
                                   key->keypair[i][pairidx], &isnull);
***************
*** 3497,3503 ****
              return false;

          /*
!          * Get one attributes oldvalue. If it is NULL - they're not equal.
           */
          newvalue = SPI_getbinval(newtup, rel->rd_att,
                                   key->keypair[i][pairidx], &isnull);
--- 3448,3454 ----
              return false;

          /*
!          * Get one attribute's oldvalue. If it is NULL - they're not equal.
           */
          newvalue = SPI_getbinval(newtup, rel->rd_att,
                                   key->keypair[i][pairidx], &isnull);
***************
*** 3505,3511 ****
              return false;

          /*
!          * Get the attributes type OID and call the '=' operator to
           * compare the values.
           */
          typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]);
--- 3456,3462 ----
              return false;

          /*
!          * Get the attribute's type OID and call the '=' operator to
           * compare the values.
           */
          typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]);
Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/include/catalog/pg_proc.h,v
retrieving revision 1.363
diff -c -r1.363 pg_proc.h
*** src/include/catalog/pg_proc.h    20 May 2005 01:29:55 -0000    1.363
--- src/include/catalog/pg_proc.h    27 May 2005 13:58:54 -0000
***************
*** 2261,2269 ****


  /* Generic referential integrity constraint triggers */
! DATA(insert OID = 1644 (  RI_FKey_check_ins        PGNSP PGUID 12 f f t f v 0 2279 "" _null_ _null_ _null_
RI_FKey_check_ins- _null_ )); 
! DESCR("referential integrity FOREIGN KEY ... REFERENCES");
! DATA(insert OID = 1645 (  RI_FKey_check_upd        PGNSP PGUID 12 f f t f v 0 2279 "" _null_ _null_ _null_
RI_FKey_check_upd- _null_ )); 
  DESCR("referential integrity FOREIGN KEY ... REFERENCES");
  DATA(insert OID = 1646 (  RI_FKey_cascade_del    PGNSP PGUID 12 f f t f v 0 2279 "" _null_ _null_ _null_
RI_FKey_cascade_del- _null_ )); 
  DESCR("referential integrity ON DELETE CASCADE");
--- 2261,2267 ----


  /* Generic referential integrity constraint triggers */
! DATA(insert OID = 1644 (  RI_FKey_check            PGNSP PGUID 12 f f t f v 0 2279 "" _null_ _null_ _null_
RI_FKey_check- _null_ )); 
  DESCR("referential integrity FOREIGN KEY ... REFERENCES");
  DATA(insert OID = 1646 (  RI_FKey_cascade_del    PGNSP PGUID 12 f f t f v 0 2279 "" _null_ _null_ _null_
RI_FKey_cascade_del- _null_ )); 
  DESCR("referential integrity ON DELETE CASCADE");
Index: src/include/commands/trigger.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/include/commands/trigger.h,v
retrieving revision 1.53
diff -c -r1.53 trigger.h
*** src/include/commands/trigger.h    11 Apr 2005 19:51:15 -0000    1.53
--- src/include/commands/trigger.h    27 May 2005 13:58:54 -0000
***************
*** 168,174 ****
  /*
   * in utils/adt/ri_triggers.c
   */
! extern bool RI_FKey_keyequal_upd(TriggerData *trigdata);
  extern bool RI_Initial_Check(FkConstraint *fkconstraint,
                   Relation rel,
                   Relation pkrel);
--- 168,175 ----
  /*
   * in utils/adt/ri_triggers.c
   */
! extern bool RI_FKey_keyequal_upd(Trigger *trigger, Relation rel,
!                                  HeapTuple old_row, HeapTuple new_row);
  extern bool RI_Initial_Check(FkConstraint *fkconstraint,
                   Relation rel,
                   Relation pkrel);
Index: src/include/utils/builtins.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/include/utils/builtins.h,v
retrieving revision 1.257
diff -c -r1.257 builtins.h
*** src/include/utils/builtins.h    27 May 2005 00:57:49 -0000    1.257
--- src/include/utils/builtins.h    27 May 2005 13:58:54 -0000
***************
*** 782,789 ****
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);

  /* ri_triggers.c */
! extern Datum RI_FKey_check_ins(PG_FUNCTION_ARGS);
! extern Datum RI_FKey_check_upd(PG_FUNCTION_ARGS);
  extern Datum RI_FKey_noaction_del(PG_FUNCTION_ARGS);
  extern Datum RI_FKey_noaction_upd(PG_FUNCTION_ARGS);
  extern Datum RI_FKey_cascade_del(PG_FUNCTION_ARGS);
--- 782,788 ----
  extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);

  /* ri_triggers.c */
! extern Datum RI_FKey_check(PG_FUNCTION_ARGS);
  extern Datum RI_FKey_noaction_del(PG_FUNCTION_ARGS);
  extern Datum RI_FKey_noaction_upd(PG_FUNCTION_ARGS);
  extern Datum RI_FKey_cascade_del(PG_FUNCTION_ARGS);
Index: src/test/regress/expected/foreign_key.out
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/expected/foreign_key.out,v
retrieving revision 1.38
diff -c -r1.38 foreign_key.out
*** src/test/regress/expected/foreign_key.out    13 Oct 2004 01:22:31 -0000    1.38
--- src/test/regress/expected/foreign_key.out    27 May 2005 13:58:54 -0000
***************
*** 911,918 ****
  DETAIL:  Key (base1,ptest1)=(2,2) is still referenced from table "pktable".
  -- fails (1,1) is being referenced (twice)
  update pktable set base1=3 where base1=1;
! ERROR:  update or delete on "pktable" violates foreign key constraint "pktable_base2_fkey" on "pktable"
! DETAIL:  Key (base1,ptest1)=(1,1) is still referenced from table "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;
--- 911,918 ----
  DETAIL:  Key (base1,ptest1)=(2,2) is still referenced from table "pktable".
  -- fails (1,1) is being referenced (twice)
  update pktable set base1=3 where base1=1;
! ERROR:  insert or update on table "pktable" violates foreign key constraint "pktable_base2_fkey"
! DETAIL:  Key (base2,ptest2)=(1,1) is not present in table "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;
***************
*** 1061,1066 ****
--- 1061,1068 ----
  COMMIT;
  ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
  DETAIL:  Key (fk)=(200) is not present in table "pktable".
+ DROP TABLE pktable, fktable CASCADE;
+ NOTICE:  drop cascades to constraint fktable_fk_fkey on table fktable
  -- test notice about expensive referential integrity checks,
  -- where the index cannot be used because of type incompatibilities.
  CREATE TEMP TABLE pktable (
***************
*** 1128,1130 ****
--- 1130,1174 ----
  DETAIL:  Key columns "x2" and "id1" are of different types: character varying and integer.
  WARNING:  foreign key constraint "fk_241_132" will require costly sequential scans
  DETAIL:  Key columns "x4" and "id3" are of different types: text and real.
+ DROP TABLE pktable, fktable CASCADE;
+ NOTICE:  drop cascades to constraint fk_241_132 on table fktable
+ NOTICE:  drop cascades to constraint fk_123_231 on table fktable
+ NOTICE:  drop cascades to constraint fk_253_213 on table fktable
+ NOTICE:  drop cascades to constraint fk_213_213 on table fktable
+ NOTICE:  drop cascades to constraint fk_123_123 on table fktable
+ NOTICE:  drop cascades to constraint fk_5_1 on table fktable
+ NOTICE:  drop cascades to constraint fk_3_1 on table fktable
+ NOTICE:  drop cascades to constraint fk_2_1 on table fktable
+ NOTICE:  drop cascades to constraint fktable_x1_fkey on table fktable
+ NOTICE:  drop cascades to constraint fk_4_2 on table fktable
+ NOTICE:  drop cascades to constraint fk_1_2 on table fktable
+ NOTICE:  drop cascades to constraint fktable_x2_fkey on table fktable
+ NOTICE:  drop cascades to constraint fk_1_3 on table fktable
+ NOTICE:  drop cascades to constraint fk_2_3 on table fktable
+ NOTICE:  drop cascades to constraint fktable_x3_fkey on table fktable
+ -- test a tricky case: we can elide firing the FK check trigger during
+ -- an UPDATE if the UPDATE did not change the foreign key
+ -- field. However, we can't do this if our transaction was the one that
+ -- created the updated row and the trigger is deferred, since our UPDATE
+ -- will have invalidated the original newly-inserted tuple, and therefore
+ -- cause the on-INSERT RI trigger not to be fired.
+ CREATE TEMP TABLE pktable (
+     id int primary key,
+     other int
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
+ CREATE TEMP TABLE fktable (
+     id int primary key,
+     fk int references pktable deferrable initially deferred
+ );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "fktable_pkey" for table "fktable"
+ INSERT INTO pktable VALUES (5, 10);
+ BEGIN;
+ -- doesn't match PK, but no error yet
+ INSERT INTO fktable VALUES (0, 20);
+ -- don't change FK
+ UPDATE fktable SET id = id + 1;
+ -- should catch error from initial INSERT
+ 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".
Index: src/test/regress/sql/foreign_key.sql
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/sql/foreign_key.sql,v
retrieving revision 1.15
diff -c -r1.15 foreign_key.sql
*** src/test/regress/sql/foreign_key.sql    4 Aug 2004 21:34:34 -0000    1.15
--- src/test/regress/sql/foreign_key.sql    27 May 2005 13:58:54 -0000
***************
*** 705,710 ****
--- 705,712 ----
  -- error here on commit
  COMMIT;

+ DROP TABLE pktable, fktable CASCADE;
+
  -- test notice about expensive referential integrity checks,
  -- where the index cannot be used because of type incompatibilities.

***************
*** 774,776 ****
--- 776,810 ----

  ALTER TABLE fktable ADD CONSTRAINT fk_241_132
  FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2);
+
+ DROP TABLE pktable, fktable CASCADE;
+
+ -- test a tricky case: we can elide firing the FK check trigger during
+ -- an UPDATE if the UPDATE did not change the foreign key
+ -- field. However, we can't do this if our transaction was the one that
+ -- created the updated row and the trigger is deferred, since our UPDATE
+ -- will have invalidated the original newly-inserted tuple, and therefore
+ -- cause the on-INSERT RI trigger not to be fired.
+
+ CREATE TEMP TABLE pktable (
+     id int primary key,
+     other int
+ );
+
+ CREATE TEMP TABLE fktable (
+     id int primary key,
+     fk int references pktable deferrable initially deferred
+ );
+
+ INSERT INTO pktable VALUES (5, 10);
+
+ BEGIN;
+
+ -- doesn't match PK, but no error yet
+ INSERT INTO fktable VALUES (0, 20);
+
+ -- don't change FK
+ UPDATE fktable SET id = id + 1;
+
+ -- should catch error from initial INSERT
+ COMMIT;

Re: skip FK trigger on UPDATE

From
Tom Lane
Date:
Neil Conway <neilc@samurai.com> writes:
> I basically just moved the logic for the "are the keys equal?" test from
> the FK trigger itself into the code that enqueues the trigger. I then
> removed the keys-are-equal check from the FK trigger. I also had to
> change (somewhat awkwardly) RI_FKey_keyequal_upd() to work for updates
> on either the PK table or the FK table. I also removed the bogus
> TriggerData argument from RI_FKey_keyequal_upd(), since AFAICS it is no
> needed and merely adds confusion.

It would probably be cleaner to have two keys-are-equal check routines
than to contort RI_FKey_keyequal_upd to work this way.

You seem to have also done a fair amount of unrelated hacking around.
What's the point of removing the distinction between check_ins and
check_upd functions?  I think that may confuse existing client code
that looks at the triggers, without really buying much.  A possibly
stronger argument is that if we find down the road that we need
separate functions again, we'll be in a bit of a sticky place; at
least if we need it to fix a bug without forcing initdb.

> This patch does cause one change to the regression test output:

That's discomforting --- you had better find out exactly why that
changed.

            regards, tom lane

Re: skip FK trigger on UPDATE

From
Neil Conway
Date:
Tom Lane wrote:
> You seem to have also done a fair amount of unrelated hacking around.
> What's the point of removing the distinction between check_ins and
> check_upd functions?

I talked about this in an earlier message to -hackers: check_upd was
actually unused (check_ins was used for both inserts and updates).

> I think that may confuse existing client code
> that looks at the triggers, without really buying much.  A possibly
> stronger argument is that if we find down the road that we need
> separate functions again, we'll be in a bit of a sticky place; at
> least if we need it to fix a bug without forcing initdb.

Hmm, I suppose -- if you prefer I can have check_ins called by the
INSERT trigger and check_upd called by the UPDATE trigger, which
probably makes more sense.

-Neil

Re: skip FK trigger on UPDATE

From
Tom Lane
Date:
Neil Conway <neilc@samurai.com> writes:
> Tom Lane wrote:
>> What's the point of removing the distinction between check_ins and
>> check_upd functions?

> I talked about this in an earlier message to -hackers: check_upd was
> actually unused (check_ins was used for both inserts and updates).

Hm, guess I missed (or forgot) that message.

> Hmm, I suppose -- if you prefer I can have check_ins called by the
> INSERT trigger and check_upd called by the UPDATE trigger, which
> probably makes more sense.

Yeah ... I thought it was doing that already.

            regards, tom lane

Re: skip FK trigger on UPDATE

From
Neil Conway
Date:
On Sun, 2005-05-29 at 21:06 -0400, Tom Lane wrote:
> Neil Conway <neilc@samurai.com> writes:
> > Hmm, I suppose -- if you prefer I can have check_ins called by the
> > INSERT trigger and check_upd called by the UPDATE trigger, which
> > probably makes more sense.
>
> Yeah ... I thought it was doing that already.

Attached are two patches: one that changes ADD FOREIGN KEY to create
separate ON INSERT and ON UPDATE triggers that invoke different trigger
functions, and a revised version of the FK UPDATE enqueuing patch.

BTW, the regression test failure was just stupidity on my part: I had
updated the "expected" results using the regression test output from
some intermediate version of the patch without checking it carefully
enough. The attached patch doesn't FK enqueuing patch doesn't cause any
unexpected regression test changes.

Barring any objections I'll apply both of these to HEAD today or
tomorrow.

-Neil


Attachment

Re: skip FK trigger on UPDATE

From
Tom Lane
Date:
Neil Conway <neilc@samurai.com> writes:
> Attached are two patches: one that changes ADD FOREIGN KEY to create
> separate ON INSERT and ON UPDATE triggers that invoke different trigger
> functions, and a revised version of the FK UPDATE enqueuing patch.

Looks OK to me.  Don't forget that the first of these should probably
include a catversion.h bump.

            regards, tom lane

Re: skip FK trigger on UPDATE

From
Neil Conway
Date:
On Sun, 2005-05-29 at 23:09 -0400, Tom Lane wrote:
> Looks OK to me.  Don't forget that the first of these should probably
> include a catversion.h bump.

Both patches applied to HEAD, catversion bumped. Thanks for the
comments, Tom.

-Neil