]> granicus.if.org Git - postgresql/commitdiff
First real FOREIGN KEY constraint trigger functionality.
authorJan Wieck <JanWieck@Yahoo.com>
Fri, 8 Oct 1999 12:00:08 +0000 (12:00 +0000)
committerJan Wieck <JanWieck@Yahoo.com>
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 <referential constraint definition>
+        *      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 <pktable>
+                        * ----------
+                        */
+                       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 <referential constraint definition>
+                *      Gereral rules 2) b):
+                *              <match type> is not specified
+                * ----------
+                */
+               case RI_MATCH_TYPE_UNSPECIFIED:
+                       elog(ERROR, "MATCH <unspecified> not yet supported");
+                       return NULL;
+
+               /* ----------
+                * SQL3 11.9 <referential constraint definition>
+                *      Gereral rules 2) c):
+                *              MATCH PARTIAL
+                * ----------
+                */
+               case RI_MATCH_TYPE_PARTIAL:
+                       elog(ERROR, "MATCH PARTIAL not yet supported");
+                       return NULL;
+
+               /* ----------
+                * SQL3 11.9 <referential constraint definition>
+                *      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 <pktable> 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 <referential constraint definition>
+                *      Gereral rules 6) a) i):
+                *              MATCH <unspecified> 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 <fktable> 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;
+}
+
+