From 34eb4f0a32fb908bf91ca8694954e5713efe045b Mon Sep 17 00:00:00 2001 From: Jan Wieck Date: Fri, 8 Oct 1999 12:00:08 +0000 Subject: [PATCH] First real FOREIGN KEY constraint trigger functionality. Implemented now: FOREIGN KEY ... REFERENCES ... MATCH FULL FOREIGN KEY ... MATCH FULL ... ON DELETE CASCADE Jan --- src/backend/utils/adt/ri_triggers.c | 954 +++++++++++++++++++++++++++- 1 file changed, 942 insertions(+), 12 deletions(-) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 13f17076e4..6f69479ba3 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -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 $ * * ---------- */ @@ -15,12 +15,403 @@ #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 - @@ -31,10 +422,7 @@ 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; +} + +