First real FOREIGN KEY constraint trigger functionality.
authorJan Wieck
Fri, 8 Oct 1999 12:00:08 +0000 (12:00 +0000)
committerJan Wieck
Fri, 8 Oct 1999 12:00:08 +0000 (12:00 +0000)
Implemented now:

    FOREIGN KEY ... REFERENCES ... MATCH FULL
FOREIGN KEY ... MATCH FULL ... ON DELETE CASCADE

Jan

src/backend/utils/adt/ri_triggers.c

index 13f17076e43fc7b0d810fae740a2f12c493eab30..6f69479ba35093ee08f428a66e8dad28b41201e9 100644 (file)
@@ -6,7 +6,7 @@
  *
  * 1999 Jan Wieck
  *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.1 1999/09/30 14:54:22 wieck Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.2 1999/10/08 12:00:08 wieck Exp $
  *
  * ----------
  */
 #include "fmgr.h"
 
 #include "access/heapam.h"
-#include "catalog/pg_proc.h"
-#include "catalog/pg_type.h"
+#include "catalog/pg_operator.h"
+#include "catalog/catname.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
 #include "utils/builtins.h"
+#include "utils/mcxt.h"
 #include "utils/syscache.h"
+#include "lib/hasht.h"
+
+
+/* ----------
+ * Local definitions
+ * ----------
+ */
+#define RI_CONSTRAINT_NAME_ARGNO       0
+#define RI_FK_RELNAME_ARGNO                1
+#define RI_PK_RELNAME_ARGNO                2
+#define RI_MATCH_TYPE_ARGNO                3
+#define RI_FIRST_ATTNAME_ARGNO         4
+
+#define RI_MAX_NUMKEYS                 16
+#define RI_MAX_ARGUMENTS       (RI_FIRST_ATTNAME_ARGNO + (RI_MAX_NUMKEYS * 2))
+#define RI_KEYPAIR_FK_IDX              0
+#define RI_KEYPAIR_PK_IDX              1
+
+#define RI_INIT_QUERYHASHSIZE          128
+#define RI_INIT_OPREQHASHSIZE          128
+
+#define RI_MATCH_TYPE_UNSPECIFIED      0
+#define RI_MATCH_TYPE_FULL             1
+#define RI_MATCH_TYPE_PARTIAL          2
+
+#define RI_KEYS_ALL_NULL               0
+#define RI_KEYS_SOME_NULL              1
+#define RI_KEYS_NONE_NULL              2
+
+
+#define RI_PLAN_TYPE_CHECK_FULL            0
+#define RI_PLAN_TYPE_CASCADE_DEL_FULL  1
+
+
+/* ----------
+ * RI_QueryKey
+ *
+ * The key identifying a prepared SPI plan in our private hashtable
+ * ----------
+ */
+typedef struct RI_QueryKey {
+   int32               constr_type;
+   Oid                 constr_id;
+   int32               constr_queryno;
+   Oid                 fk_relid;
+   Oid                 pk_relid;
+   int32               nkeypairs;
+   int16               keypair[RI_MAX_NUMKEYS][2];
+} RI_QueryKey;
+
+
+/* ----------
+ * RI_QueryHashEntry
+ * ----------
+ */
+typedef struct RI_QueryHashEntry {
+   RI_QueryKey         key;
+   void               *plan;
+} RI_QueryHashEntry;
+
+
+typedef struct RI_OpreqHashEntry {
+   Oid                 typeid;
+   Oid                 oprfnid;
+   FmgrInfo            oprfmgrinfo;
+} RI_OpreqHashEntry;
+
+
+
+/* ----------
+ * Local data
+ * ----------
+ */
+static HTAB               *ri_query_cache = (HTAB *)NULL;
+static HTAB               *ri_opreq_cache = (HTAB *)NULL;
+
+
+/* ----------
+ * Local function prototypes
+ * ----------
+ */
+static int ri_DetermineMatchType(char *str);
+static int ri_NullCheck(Relation rel, HeapTuple tup, 
+                           RI_QueryKey *key, int pairidx);
+static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id,
+                           int32 constr_queryno,
+                           Relation fk_rel, Relation pk_rel,
+                           int argc, char **argv);
+static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, 
+                           RI_QueryKey *key, int pairidx);
+static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue);
+
+static void ri_InitHashTables(void);
+static void *ri_FetchPreparedPlan(RI_QueryKey *key);
+static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
+
+
+
+/* ----------
+ * RI_FKey_check -
+ *
+ * Check foreign key existance (combined for INSERT and UPDATE).
+ * ----------
+ */
+static HeapTuple
+RI_FKey_check (FmgrInfo *proinfo)
+{
+   TriggerData        *trigdata;
+   int                 tgnargs;
+   char              **tgargs;
+   Relation            fk_rel;
+   Relation            pk_rel;
+   HeapTuple           new_row;
+   HeapTuple           old_row;
+   RI_QueryKey         qkey;
+   void               *qplan;
+   Datum               check_values[RI_MAX_NUMKEYS];
+   char                check_nulls[RI_MAX_NUMKEYS + 1];
+   bool                isnull;
+   int                 i;
+
+   trigdata = CurrentTriggerData;
+   CurrentTriggerData  = NULL;
+
+   /* ----------
+    * Check that this is a valid trigger call on the right time and event.
+    * ----------
+    */
+   if (trigdata == NULL)
+       elog(ERROR, "RI_FKey_check() not fired by trigger manager");
+   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || 
+               !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+       elog(ERROR, "RI_FKey_check() must be fired AFTER ROW");
+   if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) &&
+               !TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+       elog(ERROR, "RI_FKey_check() must be fired for INSERT or UPDATE");
+
+   /* ----------
+    * Check for the correct # of call arguments 
+    * ----------
+    */
+   tgnargs = trigdata->tg_trigger->tgnargs;
+   tgargs  = trigdata->tg_trigger->tgargs;
+   if (tgnargs < 4 || (tgnargs % 2) != 0)
+       elog(ERROR, "wrong # of arguments in call to RI_FKey_check()");
+   if (tgnargs > RI_MAX_ARGUMENTS)
+       elog(ERROR, "too many keys (%d max) in call to RI_FKey_check()",
+                       RI_MAX_NUMKEYS);
+
+   /* ----------
+    * Get the relation descriptors of the FK and PK tables and
+    * the new tuple.
+    * ----------
+    */
+   fk_rel  = trigdata->tg_relation;
+   pk_rel  = heap_openr(tgargs[RI_PK_RELNAME_ARGNO], NoLock);
+   if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+   {
+       old_row = trigdata->tg_trigtuple;
+       new_row = trigdata->tg_newtuple;
+   } else {
+       old_row = NULL;
+       new_row = trigdata->tg_trigtuple;
+   }
+
+   /* ----------
+    * SQL3 11.9 
+    *  Gereral rules 2) a):
+    *      If Rf and Rt are empty (no columns to compare given)
+    *      constraint is true if 0 < (SELECT COUNT(*) FROM T)
+    * ----------
+    */
+   if (tgnargs == 4) {
+       ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, 1,
+                               fk_rel, pk_rel,
+                               tgnargs, tgargs);
+
+       if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+       {
+           char        querystr[8192];
+
+           /* ----------
+            * The query string built is
+            *    SELECT oid FROM 
+            * ----------
+            */
+           sprintf(querystr, "SELECT oid FROM \"%s\"", 
+                               tgargs[RI_PK_RELNAME_ARGNO]);
+
+           /* ----------
+            * Prepare, save and remember the new plan.
+            * ----------
+            */
+           qplan = SPI_prepare(querystr, 0, NULL);
+           qplan = SPI_saveplan(qplan);
+           ri_HashPreparedPlan(&qkey, qplan);
+       }
+       heap_close(pk_rel, NoLock);
+
+       /* ----------
+        * Execute the plan
+        * ----------
+        */
+       if (SPI_connect() != SPI_OK_CONNECT)
+           elog(NOTICE, "SPI_connect() failed in RI_FKey_check()");
+
+       if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
+           elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
+       
+       if (SPI_processed == 0)
+           elog(ERROR, "%s referential integrity violation - "
+                       "no rows found in %s",
+                   tgargs[RI_CONSTRAINT_NAME_ARGNO],
+                   tgargs[RI_PK_RELNAME_ARGNO]);
+
+       if (SPI_finish() != SPI_OK_FINISH)
+           elog(NOTICE, "SPI_finish() failed in RI_FKey_check()");
+
+       return NULL;
+
+   }
+
+   switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+   {
+       /* ----------
+        * SQL3 11.9 
+        *  Gereral rules 2) b):
+        *       is not specified
+        * ----------
+        */
+       case RI_MATCH_TYPE_UNSPECIFIED:
+           elog(ERROR, "MATCH  not yet supported");
+           return NULL;
+
+       /* ----------
+        * SQL3 11.9 
+        *  Gereral rules 2) c):
+        *      MATCH PARTIAL
+        * ----------
+        */
+       case RI_MATCH_TYPE_PARTIAL:
+           elog(ERROR, "MATCH PARTIAL not yet supported");
+           return NULL;
+
+       /* ----------
+        * SQL3 11.9 
+        *  Gereral rules 2) d):
+        *      MATCH FULL
+        * ----------
+        */
+       case RI_MATCH_TYPE_FULL:
+           ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, 2,
+                                               fk_rel, pk_rel,
+                                               tgnargs, tgargs);
+
+           switch (ri_NullCheck(fk_rel, new_row, &qkey, RI_KEYPAIR_FK_IDX))
+           {
+               case RI_KEYS_ALL_NULL:
+                   /* ----------
+                    * No check - if NULLs are allowed at all is
+                    * already checked by NOT NULL constraint.
+                    * ----------
+                    */
+                   heap_close(pk_rel, NoLock);
+                   return NULL;
+                   
+               case RI_KEYS_SOME_NULL:
+                   /* ----------
+                    * Not allowed - MATCH FULL says either all or none
+                    * of the attributes can be NULLs
+                    * ----------
+                    */
+                   elog(ERROR, "%s referential integrity violation - "
+                               "MATCH FULL doesn't allow mixing of NULL "
+                               "and NON-NULL key values",
+                               tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+                   break;
+
+               case RI_KEYS_NONE_NULL:
+                   /* ----------
+                    * Have a full qualified key - continue below
+                    * ----------
+                    */
+                   break;
+           }
+           heap_close(pk_rel, NoLock);
+
+           /* ----------
+            * If we're called on UPDATE, check if there was a change
+            * in the foreign key at all.
+            * ----------
+            */
+           if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+           {
+               if (ri_KeysEqual(fk_rel, old_row, new_row, &qkey,
+                                                       RI_KEYPAIR_FK_IDX))
+                   return NULL;
+           }
+
+           if (SPI_connect() != SPI_OK_CONNECT)
+               elog(NOTICE, "SPI_connect() failed in RI_FKey_check()");
+
+           /* ----------
+            * Fetch or prepare a saved plan for the real check
+            * ----------
+            */
+           if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+           {
+               char        buf[256];
+               char        querystr[8192];
+               char        *querysep;
+               Oid         queryoids[RI_MAX_NUMKEYS];
+
+               /* ----------
+                * The query string built is
+                *    SELECT oid FROM  WHERE pkatt1 = $1 [AND ...]
+                * The type id's for the $ parameters are those of the
+                * corresponding FK attributes. Thus, SPI_prepare could
+                * eventually fail if the parser cannot identify some way
+                * how to compare these two types by '='.
+                * ----------
+                */
+               sprintf(querystr, "SELECT oid FROM \"%s\"", 
+                                   tgargs[RI_PK_RELNAME_ARGNO]);
+               querysep = "WHERE";
+               for (i = 0; i < qkey.nkeypairs; i++)
+               {
+                   sprintf(buf, " %s \"%s\" = $%d", querysep, 
+                                       tgargs[5 + i * 2], i + 1);
+                   strcat(querystr, buf);
+                   querysep = "AND";
+                   queryoids[i] = SPI_gettypeid(fk_rel->rd_att,
+                                   qkey.keypair[i][RI_KEYPAIR_FK_IDX]);
+               }
+
+               /* ----------
+                * Prepare, save and remember the new plan.
+                * ----------
+                */
+               qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids);
+               qplan = SPI_saveplan(qplan);
+               ri_HashPreparedPlan(&qkey, qplan);
+           }
+
+           /* ----------
+            * We have a plan now. Build up the arguments for SPI_execp()
+            * from the key values in the new FK tuple.
+            * ----------
+            */
+           for (i = 0; i < qkey.nkeypairs; i++)
+           {
+               check_values[i] = SPI_getbinval(new_row,
+                                   fk_rel->rd_att,
+                                   qkey.keypair[i][RI_KEYPAIR_FK_IDX],
+                                   &isnull);
+               if (isnull) 
+                   check_nulls[i] = 'n';
+               else
+                   check_nulls[i] = ' ';
+           }
+           check_nulls[RI_MAX_NUMKEYS] = '\0';
+
+           /* ----------
+            * Now check that foreign key exists in PK table
+            * ----------
+            */
+           if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
+               elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
+           
+           if (SPI_processed == 0)
+               elog(ERROR, "%s referential integrity violation - "
+                           "key referenced from %s not found in %s",
+                       tgargs[RI_CONSTRAINT_NAME_ARGNO],
+                       tgargs[RI_FK_RELNAME_ARGNO],
+                       tgargs[RI_PK_RELNAME_ARGNO]);
+
+           if (SPI_finish() != SPI_OK_FINISH)
+               elog(NOTICE, "SPI_finish() failed in RI_FKey_check()");
+
+           return NULL;
+   }
+
+   /* ----------
+    * Never reached
+    * ----------
+    */
+   elog(ERROR, "internal error #1 in ri_triggers.c");
+   return NULL;
+}
+
 
 /* ----------
  * RI_FKey_check_ins -
 HeapTuple
 RI_FKey_check_ins (FmgrInfo *proinfo)
 {
-   CurrentTriggerData  = NULL;
-
-   elog(NOTICE, "RI_FKey_check_ins() called\n");
-   return NULL;
+   return RI_FKey_check(proinfo);
 }
 
 
@@ -47,10 +435,7 @@ RI_FKey_check_ins (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_check_upd (FmgrInfo *proinfo)
 {
-   CurrentTriggerData  = NULL;
-
-   elog(NOTICE, "RI_FKey_check_upd() called\n");
-   return NULL;
+   return RI_FKey_check(proinfo);
 }
 
 
@@ -63,9 +448,187 @@ RI_FKey_check_upd (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_cascade_del (FmgrInfo *proinfo)
 {
+   TriggerData        *trigdata;
+   int                 tgnargs;
+   char              **tgargs;
+   Relation            fk_rel;
+   Relation            pk_rel;
+   HeapTuple           old_row;
+   RI_QueryKey         qkey;
+   void               *qplan;
+   Datum               del_values[RI_MAX_NUMKEYS];
+   char                del_nulls[RI_MAX_NUMKEYS + 1];
+   bool                isnull;
+   int                 i;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
-   elog(NOTICE, "RI_FKey_cascade_del() called\n");
+   /* ----------
+    * Check that this is a valid trigger call on the right time and event.
+    * ----------
+    */
+   if (trigdata == NULL)
+       elog(ERROR, "RI_FKey_cascade_del() not fired by trigger manager");
+   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || 
+               !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+       elog(ERROR, "RI_FKey_cascade_del() must be fired AFTER ROW");
+   if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+       elog(ERROR, "RI_FKey_cascade_del() must be fired for DELETE");
+
+   /* ----------
+    * Check for the correct # of call arguments 
+    * ----------
+    */
+   tgnargs = trigdata->tg_trigger->tgnargs;
+   tgargs  = trigdata->tg_trigger->tgargs;
+   if (tgnargs < 4 || (tgnargs % 2) != 0)
+       elog(ERROR, "wrong # of arguments in call to RI_FKey_cascade_del()");
+   if (tgnargs > RI_MAX_ARGUMENTS)
+       elog(ERROR, "too many keys (%d max) in call to RI_FKey_cascade_del()",
+                       RI_MAX_NUMKEYS);
+
+   /* ----------
+    * Nothing to do if no column names to compare given
+    * ----------
+    */
+   if (tgnargs == 4)
+       return NULL;
+
+   /* ----------
+    * Get the relation descriptors of the FK and PK tables and
+    * the old tuple.
+    * ----------
+    */
+   fk_rel  = heap_openr(tgargs[RI_FK_RELNAME_ARGNO], NoLock);
+   pk_rel  = trigdata->tg_relation;
+   old_row = trigdata->tg_trigtuple;
+
+   switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+   {
+       /* ----------
+        * SQL3 11.9 
+        *  Gereral rules 6) a) i):
+        *      MATCH  or MATCH FULL
+        *          ... ON DELETE CASCADE
+        * ----------
+        */
+       case RI_MATCH_TYPE_UNSPECIFIED:
+       case RI_MATCH_TYPE_FULL:
+           ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, 1,
+                                               fk_rel, pk_rel,
+                                               tgnargs, tgargs);
+
+           switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
+           {
+               case RI_KEYS_ALL_NULL:
+               case RI_KEYS_SOME_NULL:
+                   /* ----------
+                    * No check - MATCH FULL means there cannot be any
+                    * reference to old key if it contains NULL
+                    * ----------
+                    */
+                   heap_close(fk_rel, NoLock);
+                   return NULL;
+                   
+               case RI_KEYS_NONE_NULL:
+                   /* ----------
+                    * Have a full qualified key - continue below
+                    * ----------
+                    */
+                   break;
+           }
+           heap_close(fk_rel, NoLock);
+
+           if (SPI_connect() != SPI_OK_CONNECT)
+               elog(NOTICE, "SPI_connect() failed in RI_FKey_check()");
+
+           /* ----------
+            * Fetch or prepare a saved plan for the cascaded delete
+            * ----------
+            */
+           if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+           {
+               char        buf[256];
+               char        querystr[8192];
+               char        *querysep;
+               Oid         queryoids[RI_MAX_NUMKEYS];
+
+               /* ----------
+                * The query string built is
+                *    DELETE FROM  WHERE fkatt1 = $1 [AND ...]
+                * The type id's for the $ parameters are those of the
+                * corresponding PK attributes. Thus, SPI_prepare could
+                * eventually fail if the parser cannot identify some way
+                * how to compare these two types by '='.
+                * ----------
+                */
+               sprintf(querystr, "DELETE FROM \"%s\"", 
+                                   tgargs[RI_FK_RELNAME_ARGNO]);
+               querysep = "WHERE";
+               for (i = 0; i < qkey.nkeypairs; i++)
+               {
+                   sprintf(buf, " %s \"%s\" = $%d", querysep, 
+                                       tgargs[4 + i * 2], i + 1);
+                   strcat(querystr, buf);
+                   querysep = "AND";
+                   queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
+                                   qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+               }
+
+               /* ----------
+                * Prepare, save and remember the new plan.
+                * ----------
+                */
+               qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids);
+               qplan = SPI_saveplan(qplan);
+               ri_HashPreparedPlan(&qkey, qplan);
+           }
+
+           /* ----------
+            * We have a plan now. Build up the arguments for SPI_execp()
+            * from the key values in the deleted PK tuple.
+            * ----------
+            */
+           for (i = 0; i < qkey.nkeypairs; i++)
+           {
+               del_values[i] = SPI_getbinval(old_row,
+                                   pk_rel->rd_att,
+                                   qkey.keypair[i][RI_KEYPAIR_PK_IDX],
+                                   &isnull);
+               if (isnull) 
+                   del_nulls[i] = 'n';
+               else
+                   del_nulls[i] = ' ';
+           }
+           del_nulls[RI_MAX_NUMKEYS] = '\0';
+
+           /* ----------
+            * Now delete constraint
+            * ----------
+            */
+           if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_DELETE)
+               elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_del()");
+           
+           if (SPI_finish() != SPI_OK_FINISH)
+               elog(NOTICE, "SPI_finish() failed in RI_FKey_cascade_del()");
+
+           return NULL;
+
+       /* ----------
+        * Handle MATCH PARTIAL cascaded delete.
+        * ----------
+        */
+       case RI_MATCH_TYPE_PARTIAL:
+           elog(ERROR, "MATCH PARTIAL not yet supported");
+           return NULL;
+   }
+
+   /* ----------
+    * Never reached
+    * ----------
+    */
+   elog(ERROR, "internal error #2 in ri_triggers.c");
    return NULL;
 }
 
@@ -79,6 +642,9 @@ RI_FKey_cascade_del (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_cascade_upd (FmgrInfo *proinfo)
 {
+   TriggerData         *trigdata;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
    elog(NOTICE, "RI_FKey_cascade_upd() called\n");
@@ -95,6 +661,9 @@ RI_FKey_cascade_upd (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_restrict_del (FmgrInfo *proinfo)
 {
+   TriggerData         *trigdata;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
    elog(NOTICE, "RI_FKey_restrict_del() called\n");
@@ -111,6 +680,9 @@ RI_FKey_restrict_del (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_restrict_upd (FmgrInfo *proinfo)
 {
+   TriggerData         *trigdata;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
    elog(NOTICE, "RI_FKey_restrict_upd() called\n");
@@ -127,6 +699,9 @@ RI_FKey_restrict_upd (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_setnull_del (FmgrInfo *proinfo)
 {
+   TriggerData         *trigdata;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
    elog(NOTICE, "RI_FKey_setnull_del() called\n");
@@ -143,6 +718,9 @@ RI_FKey_setnull_del (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_setnull_upd (FmgrInfo *proinfo)
 {
+   TriggerData         *trigdata;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
    elog(NOTICE, "RI_FKey_setnull_upd() called\n");
@@ -159,6 +737,9 @@ RI_FKey_setnull_upd (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_setdefault_del (FmgrInfo *proinfo)
 {
+   TriggerData         *trigdata;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
    elog(NOTICE, "RI_FKey_setdefault_del() called\n");
@@ -175,6 +756,9 @@ RI_FKey_setdefault_del (FmgrInfo *proinfo)
 HeapTuple
 RI_FKey_setdefault_upd (FmgrInfo *proinfo)
 {
+   TriggerData         *trigdata;
+
+   trigdata = CurrentTriggerData;
    CurrentTriggerData  = NULL;
 
    elog(NOTICE, "RI_FKey_setdefault_upd() called\n");
@@ -182,3 +766,349 @@ RI_FKey_setdefault_upd (FmgrInfo *proinfo)
 }
 
 
+
+
+
+/* ----------
+ * Local functions below
+ * ----------
+ */
+
+
+
+
+
+/* ----------
+ * ri_DetermineMatchType -
+ *
+ * Convert the MATCH TYPE string into a switchable int
+ * ----------
+ */
+static int
+ri_DetermineMatchType(char *str)
+{
+   if (!strcmp(str, "UNSPECIFIED"))
+       return RI_MATCH_TYPE_UNSPECIFIED;
+   if (!strcmp(str, "FULL"))
+       return RI_MATCH_TYPE_FULL;
+   if (!strcmp(str, "PARTIAL"))
+       return RI_MATCH_TYPE_PARTIAL;
+
+   elog(ERROR, "unrecognized referential integrity MATCH type '%s'", str);
+   return 0;
+}
+
+
+/* ----------
+ * ri_BuildQueryKeyFull -
+ *
+ * Build up a new hashtable key for a prepared SPI plan of a
+ * constraint trigger of MATCH FULL. The key consists of:
+ *
+ *     constr_type is FULL
+ *     constr_id is the OID of the pg_trigger row that invoked us
+ *     constr_queryno is an internal number of the query inside the proc
+ *     fk_relid is the OID of referencing relation
+ *     pk_relid is the OID of referenced relation
+ *     nkeypairs is the number of keypairs
+ *     following are the attribute number keypairs of the trigger invocation
+ *
+ * At least for MATCH FULL this builds a unique key per plan.
+ * ----------
+ */
+static void 
+ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
+                           Relation fk_rel, Relation pk_rel,
+                           int argc, char **argv)
+{
+   int                 i;
+   int                 j;
+   int                 fno;
+
+   /* ----------
+    * 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;
+   key->fk_relid       = fk_rel->rd_id;
+   key->pk_relid       = pk_rel->rd_id;
+   key->nkeypairs      = (argc - RI_FIRST_ATTNAME_ARGNO) / 2;
+
+   /* ----------
+    * Lookup the attribute numbers of the arguments to the trigger call
+    * and fill in the keypairs.
+    * ----------
+    */
+   for (i = 0, j = RI_FIRST_ATTNAME_ARGNO; j < argc; i++, j += 2)
+   {
+       fno = SPI_fnumber(fk_rel->rd_att, argv[j]);
+       if (fno == SPI_ERROR_NOATTRIBUTE)
+           elog(ERROR, "constraint %s: table %s does not have an attribute %s",
+                   argv[RI_CONSTRAINT_NAME_ARGNO],
+                   argv[RI_FK_RELNAME_ARGNO],
+                   argv[j]);
+       key->keypair[i][RI_KEYPAIR_FK_IDX] = fno;
+
+       fno = SPI_fnumber(pk_rel->rd_att, argv[j + 1]);
+       if (fno == SPI_ERROR_NOATTRIBUTE)
+           elog(ERROR, "constraint %s: table %s does not have an attribute %s",
+                   argv[RI_CONSTRAINT_NAME_ARGNO],
+                   argv[RI_PK_RELNAME_ARGNO],
+                   argv[j + 1]);
+       key->keypair[i][RI_KEYPAIR_PK_IDX] = fno;
+   }
+
+   return;
+}
+
+
+/* ----------
+ * ri_NullCheck -
+ *
+ * Determine the NULL state of all key values in a tuple
+ *
+ * Returns one of RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL or RI_KEYS_SOME_NULL.
+ * ----------
+ */
+static int 
+ri_NullCheck(Relation rel, HeapTuple tup, RI_QueryKey *key, int pairidx)
+{
+   int             i;
+   bool            isnull;
+   bool            allnull  = true;
+   bool            nonenull = true;
+
+   for (i = 0; i < key->nkeypairs; i++)
+   {
+       isnull = false;
+       SPI_getbinval(tup, rel->rd_att, key->keypair[i][pairidx], &isnull);
+       if (isnull)
+           nonenull = false;
+       else
+           allnull = false;
+   }
+
+   if (allnull)
+       return RI_KEYS_ALL_NULL;
+
+   if (nonenull)
+       return RI_KEYS_NONE_NULL;
+
+   return RI_KEYS_SOME_NULL;
+}
+
+
+/* ----------
+ * ri_InitHashTables -
+ *
+ * Initialize our internal hash tables for prepared
+ * query plans and equal operators.
+ * ----------
+ */
+static void
+ri_InitHashTables(void)
+{
+   HASHCTL     ctl;
+
+   memset(&ctl, 0, sizeof(ctl));
+   ctl.keysize     = sizeof(RI_QueryKey);
+   ctl.datasize    = sizeof(void *);
+   ri_query_cache = hash_create(RI_INIT_QUERYHASHSIZE, &ctl, HASH_ELEM);
+
+   memset(&ctl, 0, sizeof(ctl));
+   ctl.keysize     = sizeof(Oid);
+   ctl.datasize    = sizeof(Oid) + sizeof(FmgrInfo);
+   ctl.hash        = tag_hash;
+   ri_opreq_cache = hash_create(RI_INIT_OPREQHASHSIZE, &ctl, 
+                                               HASH_ELEM | HASH_FUNCTION);
+}
+
+
+/* ----------
+ * ri_FetchPreparedPlan -
+ *
+ * Lookup for a query key in our private hash table of prepared
+ * and saved SPI execution plans. Return the plan if found or NULL.
+ * ----------
+ */
+static void *
+ri_FetchPreparedPlan(RI_QueryKey *key)
+{
+   RI_QueryHashEntry  *entry;
+   bool                found;
+
+   /* ----------
+    * On the first call initialize the hashtable
+    * ----------
+    */
+   if (!ri_query_cache)
+       ri_InitHashTables();
+
+   /* ----------
+    * Lookup for the key
+    * ----------
+    */
+   entry = (RI_QueryHashEntry *)hash_search(ri_query_cache, 
+                                       (char *)key, HASH_FIND, &found);
+   if (entry == NULL)
+       elog(FATAL, "error in RI plan cache");
+   if (!found)
+       return NULL;
+   return entry->plan;
+}
+
+
+/* ----------
+ * ri_HashPreparedPlan -
+ *
+ * Add another plan to our private SPI query plan hashtable.
+ * ----------
+ */
+static void
+ri_HashPreparedPlan(RI_QueryKey *key, void *plan)
+{
+   RI_QueryHashEntry  *entry;
+   bool                found;
+
+   /* ----------
+    * On the first call initialize the hashtable
+    * ----------
+    */
+   if (!ri_query_cache)
+       ri_InitHashTables();
+
+   /* ----------
+    * Add the new plan.
+    * ----------
+    */
+   entry = (RI_QueryHashEntry *)hash_search(ri_query_cache, 
+                                       (char *)key, HASH_ENTER, &found);
+   if (entry == NULL)
+       elog(FATAL, "can't insert into RI plan cache");
+   entry->plan = plan;
+}
+
+
+/* ----------
+ * ri_KeysEqual -
+ *
+ * Check if all key values in OLD and NEW are equal.
+ * ----------
+ */
+static bool
+ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, 
+                           RI_QueryKey *key, int pairidx)
+{
+   int             i;
+   Oid             typeid;
+   Datum           oldvalue;
+   Datum           newvalue;
+   bool            isnull;
+
+   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);
+       if (isnull)
+           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);
+       if (isnull)
+           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]);
+       if (!ri_AttributesEqual(typeid, oldvalue, newvalue))
+           return false;
+   }
+
+   return true;
+}
+
+
+/* ----------
+ * ri_AttributesEqual -
+ *
+ * Call the type specific '=' operator comparision function
+ * for two values.
+ * ----------
+ */
+static bool
+ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue)
+{
+   RI_OpreqHashEntry      *entry;
+   bool                    found;
+   Datum                   result;
+
+   /* ----------
+    * On the first call initialize the hashtable
+    * ----------
+    */
+   if (!ri_query_cache)
+       ri_InitHashTables();
+
+   /* ----------
+    * Try to find the '=' operator for this type in our cache
+    * ----------
+    */
+   entry = (RI_OpreqHashEntry *)hash_search(ri_opreq_cache,
+                                       (char *)&typeid, HASH_FIND, &found);
+   if (entry == NULL)
+       elog(FATAL, "error in RI operator cache");
+
+   /* ----------
+    * If not found, lookup the OPRNAME system cache for it
+    * and remember that info.
+    * ----------
+    */
+   if (!found)
+   {
+       HeapTuple           opr_tup;
+       Form_pg_operator    opr_struct;
+
+       opr_tup = SearchSysCacheTuple(OPRNAME,
+                           PointerGetDatum("="),
+                           ObjectIdGetDatum(typeid),
+                           ObjectIdGetDatum(typeid),
+                           CharGetDatum('b'));
+
+       if (!HeapTupleIsValid(opr_tup))
+           elog(ERROR, "ri_AttributesEqual(): cannot find '=' operator "
+                       "for type %d", typeid);
+       opr_struct = (Form_pg_operator) GETSTRUCT(opr_tup);
+
+       entry = (RI_OpreqHashEntry *)hash_search(ri_opreq_cache,
+                                       (char *)&typeid, HASH_ENTER, &found);
+       if (entry == NULL)
+           elog(FATAL, "can't insert into RI operator cache");
+
+       entry->oprfnid = opr_struct->oprcode;
+       memset(&(entry->oprfmgrinfo), 0, sizeof(FmgrInfo));
+   }
+
+   /* ----------
+    * Call the type specific '=' function
+    * ----------
+    */
+   fmgr_info(entry->oprfnid, &(entry->oprfmgrinfo));
+   result = (Datum)(*fmgr_faddr(&(entry->oprfmgrinfo)))(oldvalue, newvalue);
+   return (bool)result;
+}
+
+