summaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/ri_triggers.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/ri_triggers.c')
-rw-r--r--src/backend/utils/adt/ri_triggers.c3381
1 files changed, 0 insertions, 3381 deletions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
deleted file mode 100644
index e0b465ec983..00000000000
--- a/src/backend/utils/adt/ri_triggers.c
+++ /dev/null
@@ -1,3381 +0,0 @@
-/* ----------
- * ri_triggers.c
- *
- * Generic trigger procedures for referential integrity constraint
- * checks.
- *
- * Note about memory management: the private hashtables kept here live
- * across query and transaction boundaries, in fact they live as long as
- * the backend does. This works because the hashtable structures
- * themselves are allocated by dynahash.c in its permanent DynaHashCxt,
- * and the parse/plan node trees they point to are copied into
- * TopMemoryContext using SPI_saveplan(). This is pretty ugly, since there
- * is no way to free a no-longer-needed plan tree, but then again we don't
- * yet have any bookkeeping that would allow us to detect that a plan isn't
- * needed anymore. Improve it someday.
- *
- *
- * Portions Copyright (c) 2000-2001, PostgreSQL Global Development Group
- * Copyright 1999 Jan Wieck
- *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.37 2002/04/16 23:08:11 tgl Exp $
- *
- * ----------
- */
-
-
-/* ----------
- * Internal TODO:
- *
- * Add MATCH PARTIAL logic.
- * ----------
- */
-
-#include "postgres.h"
-
-#include "catalog/pg_operator.h"
-#include "commands/trigger.h"
-#include "executor/spi_priv.h"
-#include "parser/parse_oper.h"
-#include "utils/lsyscache.h"
-#include "miscadmin.h"
-
-
-/* ----------
- * Local definitions
- * ----------
- */
-
-#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_CHECK_LOOKUPPK_NOCOLS 1
-#define RI_PLAN_CHECK_LOOKUPPK 2
-#define RI_PLAN_CASCADE_DEL_DODELETE 1
-#define RI_PLAN_CASCADE_UPD_DOUPDATE 1
-#define RI_PLAN_NOACTION_DEL_CHECKREF 1
-#define RI_PLAN_NOACTION_UPD_CHECKREF 1
-#define RI_PLAN_RESTRICT_DEL_CHECKREF 1
-#define RI_PLAN_RESTRICT_UPD_CHECKREF 1
-#define RI_PLAN_SETNULL_DEL_DOUPDATE 1
-#define RI_PLAN_SETNULL_UPD_DOUPDATE 1
-
-#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
-#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
-
-
-/* ----------
- * 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;
- FmgrInfo oprfmgrinfo;
-} RI_OpreqHashEntry;
-
-
-
-/* ----------
- * Local data
- * ----------
- */
-static HTAB *ri_query_cache = (HTAB *) NULL;
-static HTAB *ri_opreq_cache = (HTAB *) NULL;
-
-
-/* ----------
- * Local function prototypes
- * ----------
- */
-static void quoteOneName(char *buffer, const char *name);
-static void quoteRelationName(char *buffer, Relation rel);
-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_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx);
-static bool ri_OneKeyEqual(Relation rel, int column, 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 existence (combined for INSERT and UPDATE).
- * ----------
- */
-static Datum
-RI_FKey_check(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- 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;
- int match_type;
- Oid save_uid;
-
- save_uid = GetUserId();
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- 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.
- *
- * pk_rel is opened in RowShareLock mode since that's what our
- * eventual SELECT FOR UPDATE will get on it.
- */
- fk_rel = trigdata->tg_relation;
- pk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
- 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;
- }
-
- /*
- * We should not even consider checking the row if it is no longer
- * valid since it was either deleted (doesn't matter) or updated
- * (in which case it'll be checked with its final values).
- */
- if (new_row) {
- if (!HeapTupleSatisfiesItself(new_row->t_data)) {
- heap_close(pk_rel, RowShareLock);
- return PointerGetDatum(NULL);
- }
- }
-
- /* ----------
- * 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)
- *
- * Note: The special case that no columns are given cannot
- * occur up to now in Postgres, it's just there for
- * future enhancements.
- * ----------
- */
- if (tgnargs == 4)
- {
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_CHECK_LOOKUPPK_NOCOLS,
- fk_rel, pk_rel,
- tgnargs, tgargs);
-
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100];
- char pkrelname[MAX_QUOTED_REL_NAME_LEN];
-
- /* ---------
- * The query string built is
- * SELECT 1 FROM ONLY <pktable>
- * ----------
- */
- quoteRelationName(pkrelname, pk_rel);
- sprintf(querystr, "SELECT 1 FROM ONLY %s x FOR UPDATE OF x",
- pkrelname);
-
- /*
- * Prepare, save and remember the new plan.
- */
- qplan = SPI_prepare(querystr, 0, NULL);
- qplan = SPI_saveplan(qplan);
- ri_HashPreparedPlan(&qkey, qplan);
- }
-
- /*
- * Execute the plan
- */
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
-
- SetUserId(save_uid);
-
- if (SPI_processed == 0)
- elog(ERROR, "%s referential integrity violation - "
- "no rows found in %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel));
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
-
- heap_close(pk_rel, RowShareLock);
-
- return PointerGetDatum(NULL);
-
- }
-
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
- if (match_type == RI_MATCH_TYPE_PARTIAL)
- {
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_CHECK_LOOKUPPK, 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.
- *
- * This is true for MATCH FULL, MATCH PARTIAL, and MATCH
- * <unspecified>
- */
- heap_close(pk_rel, RowShareLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_SOME_NULL:
-
- /*
- * This is the only case that differs between the three kinds
- * of MATCH.
- */
- switch (match_type)
- {
- case RI_MATCH_TYPE_FULL:
-
- /*
- * 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]);
- heap_close(pk_rel, RowShareLock);
- return PointerGetDatum(NULL);
-
- case RI_MATCH_TYPE_UNSPECIFIED:
-
- /*
- * MATCH <unspecified> - if ANY column is null, we
- * have a match.
- */
- heap_close(pk_rel, RowShareLock);
- return PointerGetDatum(NULL);
-
- case RI_MATCH_TYPE_PARTIAL:
-
- /*
- * MATCH PARTIAL - all non-null columns must match.
- * (not implemented, can be done by modifying the
- * query below to only include non-null columns, or by
- * writing a special version here)
- */
- elog(ERROR, "MATCH PARTIAL not yet implemented");
- heap_close(pk_rel, RowShareLock);
- return PointerGetDatum(NULL);
- }
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below for all three
- * kinds of MATCH.
- */
- break;
- }
-
- /*
- * Note: We cannot avoid the check on UPDATE, even if old and new key
- * are the same. Otherwise, someone could DELETE the PK that consists
- * of the DEFAULT values, and if there are any references, a ON DELETE
- * SET DEFAULT action would update the references to exactly these
- * values but we wouldn't see that weired case (this is the only place
- * to see it).
- */
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
- /*
- * Fetch or prepare a saved plan for the real check
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char pkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * SELECT 1 FROM ONLY <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 '='.
- * ----------
- */
- quoteRelationName(pkrelname, pk_rel);
- sprintf(querystr, "SELECT 1 FROM ONLY %s x", pkrelname);
- querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
- sprintf(querystr + strlen(querystr), " %s %s = $%d",
- querysep, attname, i+1);
- querysep = "AND";
- queryoids[i] = SPI_gettypeid(fk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_FK_IDX]);
- }
- strcat(querystr, " FOR UPDATE OF x");
-
- /*
- * 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++)
- {
- /*
- * We can implement MATCH PARTIAL by excluding this column from
- * the query if it is null. Simple! Unfortunately, the
- * referential actions aren't so I've not bothered to do so for
- * the moment.
- */
-
- 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[i] = '\0';
-
- /*
- * Now check that foreign key exists in PK table
- */
-
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
-
- SetUserId(save_uid);
-
- if (SPI_processed == 0)
- elog(ERROR, "%s referential integrity violation - "
- "key referenced from %s not found in %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(fk_rel),
- RelationGetRelationName(pk_rel));
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
-
- heap_close(pk_rel, RowShareLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #1 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_check_ins -
- *
- * Check foreign key existence at insert event on FK table.
- * ----------
- */
-Datum
-RI_FKey_check_ins(PG_FUNCTION_ARGS)
-{
- return RI_FKey_check(fcinfo);
-}
-
-
-/* ----------
- * RI_FKey_check_upd -
- *
- * Check foreign key existence at update event on FK table.
- * ----------
- */
-Datum
-RI_FKey_check_upd(PG_FUNCTION_ARGS)
-{
- return RI_FKey_check(fcinfo);
-}
-
-
-/* ----------
- * RI_FKey_noaction_del -
- *
- * Give an error and roll back the current transaction if the
- * delete has resulted in a violation of the given referential
- * integrity constraint.
- * ----------
- */
-Datum
-RI_FKey_noaction_del(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- 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;
- Oid save_uid;
-
- save_uid = GetUserId();
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_noaction_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_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_noaction_del()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_noaction_del()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the old
- * tuple.
- *
- * fk_rel is opened in RowShareLock mode since that's what our
- * eventual SELECT FOR UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
- 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) iv):
- * 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,
- RI_PLAN_NOACTION_DEL_CHECKREF,
- 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, RowShareLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_del()");
-
- /*
- * Fetch or prepare a saved plan for the restrict delete
- * lookup if foreign references exist
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * SELECT 1 FROM ONLY <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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
- querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), " %s %s = $%d",
- querysep, attname, i+1);
- querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, " FOR UPDATE OF x");
-
- /*
- * 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[i] = '\0';
-
- /*
- * Now check for existing references
- */
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_noaction_del()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_del()");
-
- heap_close(fk_rel, RowShareLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL restrict delete.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #2 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_noaction_upd -
- *
- * Give an error and roll back the current transaction if the
- * update has resulted in a violation of the given referential
- * integrity constraint.
- * ----------
- */
-Datum
-RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple new_row;
- HeapTuple old_row;
- RI_QueryKey qkey;
- void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
- Oid save_uid;
-
- save_uid = GetUserId();
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_noaction_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_noaction_upd() must be fired for 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_noaction_upd()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_noaction_upd()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the new
- * and old tuple.
- *
- * fk_rel is opened in RowShareLock mode since that's what our
- * eventual SELECT FOR UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
- pk_rel = trigdata->tg_relation;
- new_row = trigdata->tg_newtuple;
- old_row = trigdata->tg_trigtuple;
-
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 6) a) iv):
- * 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,
- RI_PLAN_NOACTION_UPD_CHECKREF,
- 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, RowShareLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- /*
- * No need to check anything if old and new keys are equal
- */
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
- {
- heap_close(fk_rel, RowShareLock);
- return PointerGetDatum(NULL);
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_upd()");
-
- /*
- * Fetch or prepare a saved plan for the noaction update
- * lookup if foreign references exist
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * SELECT 1 FROM ONLY <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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
- querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), " %s %s = $%d",
- querysep, attname, i+1);
- querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, " FOR UPDATE OF x");
-
- /*
- * 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 updated PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now check for existing references
- */
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_noaction_upd()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_upd()");
-
- heap_close(fk_rel, RowShareLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL noaction update.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #3 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_cascade_del -
- *
- * Cascaded delete foreign key references at delete event on PK table.
- * ----------
- */
-Datum
-RI_FKey_cascade_del(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- 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;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- 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 PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the old
- * tuple.
- *
- * fk_rel is opened in RowExclusiveLock mode since that's what our
- * eventual DELETE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
- pk_rel = trigdata->tg_relation;
- old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- 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,
- RI_PLAN_CASCADE_DEL_DODELETE,
- 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, RowExclusiveLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_del()");
-
- /*
- * Fetch or prepare a saved plan for the cascaded delete
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * DELETE FROM ONLY <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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "DELETE FROM ONLY %s", fkrelname);
- querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), " %s %s = $%d",
- querysep, attname, i+1);
- 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[i] = '\0';
-
- /*
- * Now delete constraint
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, del_values, del_nulls, 0) != SPI_OK_DELETE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_del()");
-
- SetUserId(save_uid);
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_del()");
-
- heap_close(fk_rel, RowExclusiveLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL cascaded delete.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #4 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_cascade_upd -
- *
- * Cascaded update/delete foreign key references at update event on PK table.
- * ----------
- */
-Datum
-RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple new_row;
- HeapTuple old_row;
- RI_QueryKey qkey;
- void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS * 2];
- char upd_nulls[RI_MAX_NUMKEYS * 2 + 1];
- bool isnull;
- int i;
- int j;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_cascade_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_cascade_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_cascade_upd() must be fired for 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_cascade_upd()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_cascade_upd()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the new
- * and old tuple.
- *
- * fk_rel is opened in RowExclusiveLock mode since that's what our
- * eventual UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
- pk_rel = trigdata->tg_relation;
- new_row = trigdata->tg_newtuple;
- old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 7) a) i):
- * MATCH <unspecified> or MATCH FULL
- * ... ON UPDATE CASCADE
- * ----------
- */
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_CASCADE_UPD_DOUPDATE,
- 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 update - MATCH FULL means there cannot be any
- * reference to old key if it contains NULL
- */
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- /*
- * No need to do anything if old and new keys are equal
- */
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
- {
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_upd()");
-
- /*
- * Fetch or prepare a saved plan for the cascaded update of
- * foreign references
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- const char *qualsep;
- Oid queryoids[RI_MAX_NUMKEYS * 2];
-
- /* ----------
- * The query string built is
- * UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
- * WHERE fkatt1 = $n [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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
- querysep = "";
- qualsep = "WHERE";
- for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), "%s %s = $%d",
- querysep, attname, i+1);
- sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
- qualsep, attname, j+1);
- querysep = ",";
- qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- queryoids[j] = queryoids[i];
- }
- strcat(querystr, qualstr);
-
- /*
- * Prepare, save and remember the new plan.
- */
- qplan = SPI_prepare(querystr, qkey.nkeypairs * 2, 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 updated PK tuple.
- */
- for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
- {
- upd_values[i] = SPI_getbinval(new_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
-
- upd_values[j] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[j] = 'n';
- else
- upd_nulls[j] = ' ';
- }
- upd_nulls[j] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_upd()");
-
- SetUserId(save_uid);
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_upd()");
-
- heap_close(fk_rel, RowExclusiveLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL cascade update.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #5 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_restrict_del -
- *
- * Restrict delete from PK table to rows unreferenced by foreign key.
- *
- * SQL3 intends that this referential action occur BEFORE the
- * update is performed, rather than after. This appears to be
- * the only difference between "NO ACTION" and "RESTRICT".
- *
- * For now, however, we treat "RESTRICT" and "NO ACTION" as
- * equivalent.
- * ----------
- */
-Datum
-RI_FKey_restrict_del(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- 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;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_restrict_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_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_restrict_del()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_restrict_del()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the old
- * tuple.
- *
- * fk_rel is opened in RowShareLock mode since that's what our
- * eventual SELECT FOR UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
- pk_rel = trigdata->tg_relation;
- old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 6) a) iv):
- * 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,
- RI_PLAN_RESTRICT_DEL_CHECKREF,
- 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, RowShareLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_del()");
-
- /*
- * Fetch or prepare a saved plan for the restrict delete
- * lookup if foreign references exist
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * SELECT 1 FROM ONLY <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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
- querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), " %s %s = $%d",
- querysep, attname, i+1);
- querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, " FOR UPDATE OF x");
-
- /*
- * 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[i] = '\0';
-
- /*
- * Now check for existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_del()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_del()");
-
- heap_close(fk_rel, RowShareLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL restrict delete.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #6 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_restrict_upd -
- *
- * Restrict update of PK to rows unreferenced by foreign key.
- *
- * SQL3 intends that this referential action occur BEFORE the
- * update is performed, rather than after. This appears to be
- * the only difference between "NO ACTION" and "RESTRICT".
- *
- * For now, however, we treat "RESTRICT" and "NO ACTION" as
- * equivalent.
- * ----------
- */
-Datum
-RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple new_row;
- HeapTuple old_row;
- RI_QueryKey qkey;
- void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_restrict_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_restrict_upd() must be fired for 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_restrict_upd()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_restrict_upd()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the new
- * and old tuple.
- *
- * fk_rel is opened in RowShareLock mode since that's what our
- * eventual SELECT FOR UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
- pk_rel = trigdata->tg_relation;
- new_row = trigdata->tg_newtuple;
- old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 6) a) iv):
- * 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,
- RI_PLAN_RESTRICT_UPD_CHECKREF,
- 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, RowShareLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- /*
- * No need to check anything if old and new keys are equal
- */
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
- {
- heap_close(fk_rel, RowShareLock);
- return PointerGetDatum(NULL);
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_upd()");
-
- /*
- * Fetch or prepare a saved plan for the restrict update
- * lookup if foreign references exist
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * SELECT 1 FROM ONLY <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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "SELECT 1 FROM ONLY %s x", fkrelname);
- querysep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), " %s %s = $%d",
- querysep, attname, i+1);
- querysep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, " FOR UPDATE OF x");
-
- /*
- * 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 updated PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now check for existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- SetUserId(RelationGetForm(pk_rel)->relowner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 1) != SPI_OK_SELECT)
- elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_upd()");
-
- SetUserId(save_uid);
-
- if (SPI_processed > 0)
- elog(ERROR, "%s referential integrity violation - "
- "key in %s still referenced from %s",
- tgargs[RI_CONSTRAINT_NAME_ARGNO],
- RelationGetRelationName(pk_rel),
- RelationGetRelationName(fk_rel));
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_upd()");
-
- heap_close(fk_rel, RowShareLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL restrict update.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #7 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_setnull_del -
- *
- * Set foreign key references to NULL values at delete event on PK table.
- * ----------
- */
-Datum
-RI_FKey_setnull_del(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple old_row;
- RI_QueryKey qkey;
- void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setnull_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_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_setnull_del()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_setnull_del()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the old
- * tuple.
- *
- * fk_rel is opened in RowExclusiveLock mode since that's what our
- * eventual UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
- pk_rel = trigdata->tg_relation;
- old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 6) a) ii):
- * MATCH <UNSPECIFIED> or MATCH FULL
- * ... ON DELETE SET NULL
- * ----------
- */
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_DEL_DOUPDATE,
- 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 update - MATCH FULL means there cannot be any
- * reference to old key if it contains NULL
- */
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_del()");
-
- /*
- * Fetch or prepare a saved plan for the set null delete
- * operation
- */
- if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- const char *qualsep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
- * 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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
- querysep = "";
- qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), "%s %s = NULL",
- querysep, attname);
- sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i+1);
- querysep = ",";
- qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, qualstr);
-
- /*
- * 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 updated PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setnull_del()");
-
- SetUserId(save_uid);
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_del()");
-
- heap_close(fk_rel, RowExclusiveLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL set null delete.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #8 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_setnull_upd -
- *
- * Set foreign key references to NULL at update event on PK table.
- * ----------
- */
-Datum
-RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple new_row;
- HeapTuple old_row;
- RI_QueryKey qkey;
- void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
- int match_type;
- bool use_cached_query;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setnull_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setnull_upd() must be fired for 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_setnull_upd()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_setnull_upd()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the old
- * tuple.
- *
- * fk_rel is opened in RowExclusiveLock mode since that's what our
- * eventual UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
- pk_rel = trigdata->tg_relation;
- new_row = trigdata->tg_newtuple;
- old_row = trigdata->tg_trigtuple;
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- switch (match_type)
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 7) a) ii) 2):
- * MATCH FULL
- * ... ON UPDATE SET NULL
- * ----------
- */
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_UPD_DOUPDATE,
- 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 update - MATCH FULL means there cannot be any
- * reference to old key if it contains NULL
- */
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- /*
- * No need to do anything if old and new keys are equal
- */
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
- {
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_upd()");
-
- /*
- * "MATCH <unspecified>" only changes columns corresponding to
- * the referenced columns that have changed in pk_rel. This
- * means the "SET attrn=NULL [, attrn=NULL]" string will be
- * change as well. In this case, we need to build a temporary
- * plan rather than use our cached plan, unless the update
- * happens to change all columns in the key. Fortunately, for
- * the most common case of a single-column foreign key, this
- * will be true.
- *
- * In case you're wondering, the inequality check works because
- * we know that the old key value has no NULLs (see above).
- */
-
- use_cached_query = match_type == RI_MATCH_TYPE_FULL ||
- ri_AllKeysUnequal(pk_rel, old_row, new_row,
- &qkey, RI_KEYPAIR_PK_IDX);
-
- /*
- * Fetch or prepare a saved plan for the set null update
- * operation if possible, or build a temporary plan if not.
- */
- if (!use_cached_query ||
- (qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- const char *qualsep;
- Oid queryoids[RI_MAX_NUMKEYS];
-
- /* ----------
- * The query string built is
- * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
- * 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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
- querysep = "";
- qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- /*
- * MATCH <unspecified> - only change columns
- * corresponding to changed columns in pk_rel's key
- */
- if (match_type == RI_MATCH_TYPE_FULL ||
- !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
- {
- sprintf(querystr + strlen(querystr), "%s %s = NULL",
- querysep, attname);
- querysep = ",";
- }
- sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i+1);
- qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, qualstr);
-
- /*
- * Prepare the new plan.
- */
- qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids);
-
- /*
- * Save and remember the plan if we're building the
- * "standard" plan.
- */
- if (use_cached_query)
- {
- 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 updated PK tuple.
- */
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setnull_upd()");
-
- SetUserId(save_uid);
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_upd()");
-
- heap_close(fk_rel, RowExclusiveLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL set null update.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #9 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_setdefault_del -
- *
- * Set foreign key references to defaults at delete event on PK table.
- * ----------
- */
-Datum
-RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple old_row;
- RI_QueryKey qkey;
- void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setdefault_del() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_del() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_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_setdefault_del()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_setdefault_del()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the old
- * tuple.
- *
- * fk_rel is opened in RowExclusiveLock mode since that's what our
- * eventual UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
- pk_rel = trigdata->tg_relation;
- old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 6) a) iii):
- * MATCH <UNSPECIFIED> or MATCH FULL
- * ... ON DELETE SET DEFAULT
- * ----------
- */
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_DEL_DOUPDATE,
- 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 update - MATCH FULL means there cannot be any
- * reference to old key if it contains NULL
- */
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_del()");
-
- /*
- * Prepare a plan for the set default delete operation.
- * Unfortunately we need to do it on every invocation because
- * the default value could potentially change between calls.
- */
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- const char *qualsep;
- Oid queryoids[RI_MAX_NUMKEYS];
- Plan *spi_plan;
- AttrDefault *defval;
- TargetEntry *spi_qptle;
- int i,
- j;
-
- /* ----------
- * The query string built is
- * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
- * 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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
- querysep = "";
- qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- sprintf(querystr + strlen(querystr), "%s %s = NULL",
- querysep, attname);
- sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i+1);
- querysep = ",";
- qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, qualstr);
-
- /*
- * Prepare the plan
- */
- qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids);
-
- /* ----------
- * Here now follows very ugly code depending on internals
- * of the SPI manager.
- *
- * EVIL EVIL EVIL (but must be - Jan)
- *
- * We replace the CONST NULL targetlist expressions
- * in the generated plan by (any) default values found
- * in the tuple constructor.
- * ----------
- */
- spi_plan = (Plan *) lfirst(((_SPI_plan *) qplan)->ptlist);
- if (fk_rel->rd_att->constr != NULL)
- defval = fk_rel->rd_att->constr->defval;
- else
- defval = NULL;
- for (i = 0; i < qkey.nkeypairs && defval != NULL; i++)
- {
- /*
- * For each key attribute lookup the tuple constructor
- * for a corresponding default value
- */
- for (j = 0; j < fk_rel->rd_att->constr->num_defval; j++)
- {
- if (defval[j].adnum ==
- qkey.keypair[i][RI_KEYPAIR_FK_IDX])
- {
- /*
- * That's the one - push the expression from
- * defval.adbin into the plan's targetlist
- */
- spi_qptle = (TargetEntry *)
- nth(defval[j].adnum - 1,
- spi_plan->targetlist);
- spi_qptle->expr = stringToNode(defval[j].adbin);
-
- break;
- }
- }
- }
- }
-
- /*
- * 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++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setdefault_del()");
-
- SetUserId(save_uid);
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_del()");
-
- heap_close(fk_rel, RowExclusiveLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL set null delete.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #10 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_setdefault_upd -
- *
- * Set foreign key references to defaults at update event on PK table.
- * ----------
- */
-Datum
-RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
-{
- TriggerData *trigdata = (TriggerData *) fcinfo->context;
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple new_row;
- HeapTuple old_row;
- RI_QueryKey qkey;
- void *qplan;
- Datum upd_values[RI_MAX_NUMKEYS];
- char upd_nulls[RI_MAX_NUMKEYS + 1];
- bool isnull;
- int i;
- int match_type;
- Oid save_uid;
- Oid fk_owner;
-
- ReferentialIntegritySnapshotOverride = true;
-
- /*
- * Check that this is a valid trigger call on the right time and
- * event.
- */
- if (!CALLED_AS_TRIGGER(fcinfo))
- elog(ERROR, "RI_FKey_setdefault_upd() not fired by trigger manager");
- if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
- !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_upd() must be fired AFTER ROW");
- if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
- elog(ERROR, "RI_FKey_setdefault_upd() must be fired for 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_setdefault_upd()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_setdefault_upd()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return PointerGetDatum(NULL);
-
- /*
- * Get the relation descriptors of the FK and PK tables and the old
- * tuple.
- *
- * fk_rel is opened in RowExclusiveLock mode since that's what our
- * eventual UPDATE will get on it.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
- pk_rel = trigdata->tg_relation;
- new_row = trigdata->tg_newtuple;
- old_row = trigdata->tg_trigtuple;
- fk_owner = RelationGetForm(fk_rel)->relowner;
-
- match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
- switch (match_type)
- {
- /* ----------
- * SQL3 11.9 <referential constraint definition>
- * Gereral rules 7) a) iii):
- * MATCH <UNSPECIFIED> or MATCH FULL
- * ... ON UPDATE SET DEFAULT
- * ----------
- */
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- RI_PLAN_SETNULL_DEL_DOUPDATE,
- 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 update - MATCH FULL means there cannot be any
- * reference to old key if it contains NULL
- */
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
-
- case RI_KEYS_NONE_NULL:
-
- /*
- * Have a full qualified key - continue below
- */
- break;
- }
-
- /*
- * No need to do anything if old and new keys are equal
- */
- if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX))
- {
- heap_close(fk_rel, RowExclusiveLock);
- return PointerGetDatum(NULL);
- }
-
- if (SPI_connect() != SPI_OK_CONNECT)
- elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_upd()");
-
- /*
- * Prepare a plan for the set default delete operation.
- * Unfortunately we need to do it on every invocation because
- * the default value could potentially change between calls.
- */
- {
- char querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
- (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
- char qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
- char fkrelname[MAX_QUOTED_REL_NAME_LEN];
- char attname[MAX_QUOTED_NAME_LEN];
- const char *querysep;
- const char *qualsep;
- Oid queryoids[RI_MAX_NUMKEYS];
- Plan *spi_plan;
- AttrDefault *defval;
- TargetEntry *spi_qptle;
- int i,
- j;
-
- /* ----------
- * The query string built is
- * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
- * 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 '='.
- * ----------
- */
- quoteRelationName(fkrelname, fk_rel);
- sprintf(querystr, "UPDATE ONLY %s SET", fkrelname);
- qualstr[0] = '\0';
- querysep = "";
- qualsep = "WHERE";
- for (i = 0; i < qkey.nkeypairs; i++)
- {
- quoteOneName(attname,
- tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
- /*
- * MATCH <unspecified> - only change columns
- * corresponding to changed columns in pk_rel's key
- */
- if (match_type == RI_MATCH_TYPE_FULL ||
- !ri_OneKeyEqual(pk_rel, i, old_row,
- new_row, &qkey, RI_KEYPAIR_PK_IDX))
- {
- sprintf(querystr + strlen(querystr), "%s %s = NULL",
- querysep, attname);
- querysep = ",";
- }
- sprintf(qualstr + strlen(qualstr), " %s %s = $%d",
- qualsep, attname, i+1);
- qualsep = "AND";
- queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
- }
- strcat(querystr, qualstr);
-
- /*
- * Prepare the plan
- */
- qplan = SPI_prepare(querystr, qkey.nkeypairs, queryoids);
-
- /*
- * Now replace the CONST NULL targetlist expressions in
- * the generated plan by (any) default values found in the
- * tuple constructor.
- */
- spi_plan = (Plan *) lfirst(((_SPI_plan *) qplan)->ptlist);
- if (fk_rel->rd_att->constr != NULL)
- defval = fk_rel->rd_att->constr->defval;
- else
- defval = NULL;
- for (i = 0; i < qkey.nkeypairs && defval != NULL; i++)
- {
- /*
- * MATCH <unspecified> - only change columns
- * corresponding to changed columns in pk_rel's key.
- * This conditional must match the one in the loop
- * above that built the SET attrn=NULL list.
- */
- if (match_type == RI_MATCH_TYPE_FULL ||
- !ri_OneKeyEqual(pk_rel, i, old_row,
- new_row, &qkey, RI_KEYPAIR_PK_IDX))
- {
- /*
- * For each key attribute lookup the tuple
- * constructor for a corresponding default value
- */
- for (j = 0; j < fk_rel->rd_att->constr->num_defval; j++)
- {
- if (defval[j].adnum ==
- qkey.keypair[i][RI_KEYPAIR_FK_IDX])
- {
- /*
- * That's the one - push the expression
- * from defval.adbin into the plan's
- * targetlist
- */
- spi_qptle = (TargetEntry *)
- nth(defval[j].adnum - 1,
- spi_plan->targetlist);
- spi_qptle->expr = stringToNode(defval[j].adbin);
-
- break;
- }
- }
- }
- }
- }
-
- /*
- * 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++)
- {
- upd_values[i] = SPI_getbinval(old_row,
- pk_rel->rd_att,
- qkey.keypair[i][RI_KEYPAIR_PK_IDX],
- &isnull);
- if (isnull)
- upd_nulls[i] = 'n';
- else
- upd_nulls[i] = ' ';
- }
- upd_nulls[i] = '\0';
-
- /*
- * Now update the existing references
- */
- save_uid = GetUserId();
- SetUserId(fk_owner);
-
- if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
- elog(ERROR, "SPI_execp() failed in RI_FKey_setdefault_upd()");
-
- SetUserId(save_uid);
-
- if (SPI_finish() != SPI_OK_FINISH)
- elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_upd()");
-
- heap_close(fk_rel, RowExclusiveLock);
-
- return PointerGetDatum(NULL);
-
- /*
- * Handle MATCH PARTIAL set null delete.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- return PointerGetDatum(NULL);
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #11 in ri_triggers.c");
- return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_keyequal_upd -
- *
- * Check if we have a key change on update.
- *
- * This is not a real trigger procedure. It is used by the deferred
- * trigger queue manager to detect "triggered data change violation".
- * ----------
- */
-bool
-RI_FKey_keyequal_upd(TriggerData *trigdata)
-{
- int tgnargs;
- char **tgargs;
- Relation fk_rel;
- Relation pk_rel;
- HeapTuple new_row;
- HeapTuple old_row;
- RI_QueryKey qkey;
-
- /*
- * Check for the correct # of call arguments
- */
- tgnargs = trigdata->tg_trigger->tgnargs;
- tgargs = trigdata->tg_trigger->tgargs;
- if (tgnargs < 4 || (tgnargs % 2) != 0)
- elog(ERROR, "wrong # of arguments in call to RI_FKey_keyequal_upd()");
- if (tgnargs > RI_MAX_ARGUMENTS)
- elog(ERROR, "too many keys (%d max) in call to RI_FKey_keyequal_upd()",
- RI_MAX_NUMKEYS);
-
- /*
- * Nothing to do if no column names to compare given
- */
- if (tgnargs == 4)
- return true;
-
- /*
- * Get the relation descriptors of the FK and PK tables and the new
- * and old tuple.
- *
- * Use minimal locking for fk_rel here.
- */
- fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, AccessShareLock);
- pk_rel = trigdata->tg_relation;
- new_row = trigdata->tg_newtuple;
- old_row = trigdata->tg_trigtuple;
-
- switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
- {
- /*
- * MATCH <UNSPECIFIED>
- */
- case RI_MATCH_TYPE_UNSPECIFIED:
- case RI_MATCH_TYPE_FULL:
- ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
- 0,
- fk_rel, pk_rel,
- tgnargs, tgargs);
-
- heap_close(fk_rel, AccessShareLock);
-
- /*
- * Return if key's are equal
- */
- return ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
- RI_KEYPAIR_PK_IDX);
-
- /*
- * Handle MATCH PARTIAL set null delete.
- */
- case RI_MATCH_TYPE_PARTIAL:
- elog(ERROR, "MATCH PARTIAL not yet supported");
- break;
- }
-
- /*
- * Never reached
- */
- elog(ERROR, "internal error #12 in ri_triggers.c");
- return false;
-}
-
-
-
-
-
-/* ----------
- * Local functions below
- * ----------
- */
-
-
-/*
- * quoteOneName --- safely quote a single SQL name
- *
- * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
- */
-static void
-quoteOneName(char *buffer, const char *name)
-{
- /* Rather than trying to be smart, just always quote it. */
- *buffer++ = '"';
- while (*name)
- {
- if (*name == '"')
- *buffer++ = '"';
- *buffer++ = *name++;
- }
- *buffer++ = '"';
- *buffer = '\0';
-}
-
-/*
- * quoteRelationName --- safely quote a fully qualified relation name
- *
- * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
- */
-static void
-quoteRelationName(char *buffer, Relation rel)
-{
- quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel)));
- buffer += strlen(buffer);
- *buffer++ = '.';
- quoteOneName(buffer, RelationGetRelationName(rel));
-}
-
-
-/* ----------
- * ri_DetermineMatchType -
- *
- * Convert the MATCH TYPE string into a switchable int
- * ----------
- */
-static int
-ri_DetermineMatchType(char *str)
-{
- if (strcmp(str, "UNSPECIFIED") == 0)
- return RI_MATCH_TYPE_UNSPECIFIED;
- if (strcmp(str, "FULL") == 0)
- return RI_MATCH_TYPE_FULL;
- if (strcmp(str, "PARTIAL") == 0)
- 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],
- RelationGetRelationName(fk_rel),
- 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],
- RelationGetRelationName(pk_rel),
- argv[j + 1]);
- key->keypair[i][RI_KEYPAIR_PK_IDX] = fno;
- }
-}
-
-
-/* ----------
- * 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.entrysize = sizeof(RI_QueryHashEntry);
- ctl.hash = tag_hash;
- ri_query_cache = hash_create("RI query cache", RI_INIT_QUERYHASHSIZE,
- &ctl, HASH_ELEM | HASH_FUNCTION);
-
- ctl.keysize = sizeof(Oid);
- ctl.entrysize = sizeof(RI_OpreqHashEntry);
- ctl.hash = tag_hash;
- ri_opreq_cache = hash_create("RI OpReq cache", 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;
-
- /*
- * 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,
- (void *) key,
- HASH_FIND, NULL);
- if (entry == NULL)
- 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,
- (void *) key,
- HASH_ENTER, &found);
- if (entry == NULL)
- elog(ERROR, "out of memory for 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_AllKeysUnequal -
- *
- * Check if all key values in OLD and NEW are not equal.
- * ----------
- */
-static bool
-ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx)
-{
- int i;
- Oid typeid;
- Datum oldvalue;
- Datum newvalue;
- bool isnull;
- bool keys_unequal;
-
- keys_unequal = true;
- for (i = 0; keys_unequal && 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)
- continue;
-
- /*
- * 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)
- continue;
-
- /*
- * 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))
- continue;
- keys_unequal = false;
- }
-
- return keys_unequal;
-}
-
-
-/* ----------
- * ri_OneKeyEqual -
- *
- * Check if one key value in OLD and NEW is equal.
- *
- * ri_KeysEqual could call this but would run a bit slower. For
- * now, let's duplicate the code.
- * ----------
- */
-static bool
-ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
- RI_QueryKey *key, int pairidx)
-{
- Oid typeid;
- Datum oldvalue;
- Datum newvalue;
- bool isnull;
-
- /*
- * Get one attributes oldvalue. If it is NULL - they're not equal.
- */
- oldvalue = SPI_getbinval(oldtup, rel->rd_att,
- key->keypair[column][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[column][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[column][pairidx]);
- if (!ri_AttributesEqual(typeid, oldvalue, newvalue))
- return false;
-
- return true;
-}
-
-
-/* ----------
- * ri_AttributesEqual -
- *
- * Call the type specific '=' operator comparison function
- * for two values.
- *
- * NB: we have already checked that neither value is null.
- * ----------
- */
-static bool
-ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue)
-{
- RI_OpreqHashEntry *entry;
- bool found;
-
- /*
- * On the first call initialize the hashtable
- */
- if (!ri_opreq_cache)
- ri_InitHashTables();
-
- /*
- * Try to find the '=' operator for this type in our cache
- */
- entry = (RI_OpreqHashEntry *) hash_search(ri_opreq_cache,
- (void *) &typeid,
- HASH_FIND, NULL);
-
- /*
- * If not found, lookup the operator, then do the function manager
- * lookup, and remember that info.
- */
- if (!entry)
- {
- Oid opr_proc;
- FmgrInfo finfo;
-
- opr_proc = compatible_oper_funcid(makeList1(makeString("=")),
- typeid, typeid, true);
- if (!OidIsValid(opr_proc))
- elog(ERROR,
- "ri_AttributesEqual(): cannot find '=' operator for type %u",
- typeid);
-
- /*
- * Since fmgr_info could fail, call it *before* creating the
- * hashtable entry --- otherwise we could elog leaving an
- * incomplete entry in the hashtable. Also, because this will be
- * a permanent table entry, we must make sure any subsidiary
- * structures of the fmgr record are kept in TopMemoryContext.
- */
- fmgr_info_cxt(opr_proc, &finfo, TopMemoryContext);
-
- entry = (RI_OpreqHashEntry *) hash_search(ri_opreq_cache,
- (void *) &typeid,
- HASH_ENTER, &found);
- if (entry == NULL)
- elog(ERROR, "out of memory for RI operator cache");
-
- entry->typeid = typeid;
- memcpy(&(entry->oprfmgrinfo), &finfo, sizeof(FmgrInfo));
- }
-
- /*
- * Call the type specific '=' function
- */
- return DatumGetBool(FunctionCall2(&(entry->oprfmgrinfo),
- oldvalue, newvalue));
-}