#include "postgres.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/xact.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/spi.h"
#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
Go to the source code of this file.
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3) |
Definition at line 87 of file ri_triggers.c.
Referenced by RI_Initial_Check().
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2) |
Definition at line 88 of file ri_triggers.c.
#define RI_INIT_CONSTRAINTHASHSIZE 64 |
Definition at line 65 of file ri_triggers.c.
Referenced by ri_InitHashTables().
#define RI_INIT_QUERYHASHSIZE (RI_INIT_CONSTRAINTHASHSIZE * 4) |
Definition at line 66 of file ri_triggers.c.
Referenced by ri_InitHashTables().
#define RI_KEYS_ALL_NULL 0 |
Definition at line 68 of file ri_triggers.c.
Referenced by RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_fk_upd_check_required(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), ri_restrict_del(), and ri_restrict_upd().
#define RI_KEYS_NONE_NULL 2 |
Definition at line 70 of file ri_triggers.c.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_fk_upd_check_required(), RI_FKey_pk_upd_check_required(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), RI_Initial_Check(), ri_restrict_del(), and ri_restrict_upd().
#define RI_KEYS_SOME_NULL 1 |
Definition at line 69 of file ri_triggers.c.
Referenced by RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_fk_upd_check_required(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), ri_restrict_del(), and ri_restrict_upd().
#define RI_MAX_NUMKEYS INDEX_MAX_KEYS |
Definition at line 63 of file ri_triggers.c.
Referenced by RI_FKey_cascade_upd(), ri_LoadConstraintInfo(), and ri_PerformCheck().
#define RI_PLAN_CASCADE_DEL_DODELETE 3 |
Definition at line 78 of file ri_triggers.c.
Referenced by RI_FKey_cascade_del().
#define RI_PLAN_CASCADE_UPD_DOUPDATE 4 |
Definition at line 79 of file ri_triggers.c.
Referenced by RI_FKey_cascade_upd().
#define RI_PLAN_CHECK_LOOKUPPK 1 |
Definition at line 74 of file ri_triggers.c.
Referenced by RI_FKey_check(), RI_Initial_Check(), and ri_PerformCheck().
#define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2 |
Definition at line 75 of file ri_triggers.c.
Referenced by ri_Check_Pk_Match(), and ri_PerformCheck().
#define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK |
Definition at line 76 of file ri_triggers.c.
Referenced by ri_PerformCheck(), and ri_PlanCheck().
#define RI_PLAN_RESTRICT_DEL_CHECKREF 5 |
Definition at line 80 of file ri_triggers.c.
Referenced by ri_restrict_del().
#define RI_PLAN_RESTRICT_UPD_CHECKREF 6 |
Definition at line 81 of file ri_triggers.c.
Referenced by ri_restrict_upd().
#define RI_PLAN_SETDEFAULT_DEL_DOUPDATE 9 |
Definition at line 84 of file ri_triggers.c.
Referenced by RI_FKey_setdefault_del().
#define RI_PLAN_SETDEFAULT_UPD_DOUPDATE 10 |
Definition at line 85 of file ri_triggers.c.
Referenced by RI_FKey_setdefault_upd().
#define RI_PLAN_SETNULL_DEL_DOUPDATE 7 |
Definition at line 82 of file ri_triggers.c.
Referenced by RI_FKey_setnull_del().
#define RI_PLAN_SETNULL_UPD_DOUPDATE 8 |
Definition at line 83 of file ri_triggers.c.
Referenced by RI_FKey_setnull_upd().
#define RI_TRIGTYPE_DELETE 3 |
Definition at line 96 of file ri_triggers.c.
Referenced by ri_CheckTrigger(), RI_FKey_cascade_del(), RI_FKey_noaction_del(), RI_FKey_restrict_del(), RI_FKey_setdefault_del(), and RI_FKey_setnull_del().
#define RI_TRIGTYPE_INSERT 1 |
Definition at line 94 of file ri_triggers.c.
Referenced by ri_CheckTrigger(), and RI_FKey_check_ins().
#define RI_TRIGTYPE_UPDATE 2 |
Definition at line 95 of file ri_triggers.c.
Referenced by ri_CheckTrigger(), RI_FKey_cascade_upd(), RI_FKey_check_upd(), RI_FKey_noaction_upd(), RI_FKey_restrict_upd(), RI_FKey_setdefault_upd(), and RI_FKey_setnull_upd().
#define RIAttCollation | ( | rel, | ||
attnum | ||||
) | attnumCollationId(rel, attnum) |
Definition at line 92 of file ri_triggers.c.
Referenced by RI_Initial_Check().
#define RIAttName | ( | rel, | ||
attnum | ||||
) | NameStr(*attnumAttName(rel, attnum)) |
Definition at line 90 of file ri_triggers.c.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), RI_Initial_Check(), ri_restrict_del(), and ri_restrict_upd().
#define RIAttType | ( | rel, | ||
attnum | ||||
) | attnumTypeId(rel, attnum) |
Definition at line 91 of file ri_triggers.c.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), RI_Initial_Check(), ri_KeysEqual(), ri_restrict_del(), and ri_restrict_upd().
typedef struct RI_CompareHashEntry RI_CompareHashEntry |
typedef struct RI_CompareKey RI_CompareKey |
typedef struct RI_ConstraintInfo RI_ConstraintInfo |
typedef struct RI_QueryHashEntry RI_QueryHashEntry |
typedef struct RI_QueryKey RI_QueryKey |
Definition at line 2925 of file ri_triggers.c.
References Assert, hash_seq_init(), hash_seq_search(), NULL, RI_ConstraintInfo::oidHashValue, and RI_ConstraintInfo::valid.
Referenced by ri_InitHashTables().
{ HASH_SEQ_STATUS status; RI_ConstraintInfo *hentry; Assert(ri_constraint_cache != NULL); hash_seq_init(&status, ri_constraint_cache); while ((hentry = (RI_ConstraintInfo *) hash_seq_search(&status)) != NULL) { if (hashvalue == 0 || hentry->oidHashValue == hashvalue) hentry->valid = false; } }
static void quoteOneName | ( | char * | buffer, | |
const char * | name | |||
) | [static] |
Definition at line 2514 of file ri_triggers.c.
Referenced by quoteRelationName(), ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), ri_GenerateQualCollation(), RI_Initial_Check(), ri_restrict_del(), and ri_restrict_upd().
static void quoteRelationName | ( | char * | buffer, | |
Relation | rel | |||
) | [static] |
Definition at line 2534 of file ri_triggers.c.
References get_namespace_name(), quoteOneName(), RelationGetNamespace, and RelationGetRelationName.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), RI_Initial_Check(), ri_restrict_del(), and ri_restrict_upd().
{ quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel))); buffer += strlen(buffer); *buffer++ = '.'; quoteOneName(buffer, RelationGetRelationName(rel)); }
static void ri_add_cast_to | ( | StringInfo | buf, | |
Oid | typid | |||
) | [static] |
Definition at line 2595 of file ri_triggers.c.
References appendStringInfo(), elog, ERROR, get_namespace_name(), GETSTRUCT, HeapTupleIsValid, NameStr, ObjectIdGetDatum, quote_identifier(), ReleaseSysCache(), SearchSysCache1, and TYPEOID.
Referenced by ri_GenerateQual().
{ HeapTuple typetup; Form_pg_type typform; char *typname; char *nspname; typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); if (!HeapTupleIsValid(typetup)) elog(ERROR, "cache lookup failed for type %u", typid); typform = (Form_pg_type) GETSTRUCT(typetup); typname = NameStr(typform->typname); nspname = get_namespace_name(typform->typnamespace); appendStringInfo(buf, "::%s.%s", quote_identifier(nspname), quote_identifier(typname)); ReleaseSysCache(typetup); }
Definition at line 3483 of file ri_triggers.c.
References BoolGetDatum, RI_CompareHashEntry::cast_func_finfo, DatumGetBool, RI_CompareHashEntry::eq_opr_finfo, FmgrInfo::fn_oid, FunctionCall2, FunctionCall3, Int32GetDatum, OidIsValid, and ri_HashCompareOp().
Referenced by ri_KeysEqual().
{ RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid); /* Do we need to cast the values? */ if (OidIsValid(entry->cast_func_finfo.fn_oid)) { oldvalue = FunctionCall3(&entry->cast_func_finfo, oldvalue, Int32GetDatum(-1), /* typmod */ BoolGetDatum(false)); /* implicit coercion */ newvalue = FunctionCall3(&entry->cast_func_finfo, newvalue, Int32GetDatum(-1), /* typmod */ BoolGetDatum(false)); /* implicit coercion */ } /* * Apply the comparison operator. We assume it doesn't care about * collations. */ return DatumGetBool(FunctionCall2(&entry->eq_opr_finfo, oldvalue, newvalue)); }
static void ri_BuildQueryKey | ( | RI_QueryKey * | key, | |
const RI_ConstraintInfo * | riinfo, | |||
int32 | constr_queryno | |||
) | [static] |
Definition at line 2675 of file ri_triggers.c.
References RI_QueryKey::constr_id, RI_QueryKey::constr_queryno, and RI_ConstraintInfo::constraint_id.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), ri_restrict_del(), and ri_restrict_upd().
{ /* * We assume struct RI_QueryKey contains no padding bytes, else we'd * need to use memset to clear them. */ key->constr_id = riinfo->constraint_id; key->constr_queryno = constr_queryno; }
static bool ri_Check_Pk_Match | ( | Relation | pk_rel, | |
Relation | fk_rel, | |||
HeapTuple | old_row, | |||
const RI_ConstraintInfo * | riinfo | |||
) | [static] |
Definition at line 509 of file ri_triggers.c.
References appendStringInfo(), Assert, StringInfoData::data, elog, ERROR, i, initStringInfo(), RI_ConstraintInfo::nkeys, NULL, RI_ConstraintInfo::pk_attnums, RI_ConstraintInfo::pp_eq_oprs, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_NONE_NULL, ri_NullCheck(), ri_PerformCheck(), RI_PLAN_CHECK_LOOKUPPK_FROM_PK, ri_PlanCheck(), RIAttName, RIAttType, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, and SPI_OK_SELECT.
Referenced by ri_restrict_del(), and ri_restrict_upd().
{ SPIPlanPtr qplan; RI_QueryKey qkey; int i; bool result; /* Only called for non-null rows */ Assert(ri_NullCheck(old_row, riinfo, true) == RI_KEYS_NONE_NULL); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for checking PK table with values coming * from a PK row */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK_FROM_PK); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; char pkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * SELECT 1 FROM ONLY <pktable> x WHERE pkatt1 = $1 [AND ...] * FOR KEY SHARE OF x * The type id's for the $ parameters are those of the * PK attributes themselves. * ---------- */ initStringInfo(&querybuf); quoteRelationName(pkrelname, pk_rel); appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[i])); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&querybuf, querysep, attname, pk_type, riinfo->pp_eq_oprs[i], paramname, pk_type); querysep = "AND"; queryoids[i] = pk_type; } appendStringInfo(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it. */ result = ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* treat like update */ SPI_OK_SELECT); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); return result; }
static void ri_CheckTrigger | ( | FunctionCallInfo | fcinfo, | |
const char * | funcname, | |||
int | tgkind | |||
) | [static] |
Definition at line 2690 of file ri_triggers.c.
References CALLED_AS_TRIGGER, FunctionCallInfoData::context, ereport, errcode(), errmsg(), ERROR, RI_TRIGTYPE_DELETE, RI_TRIGTYPE_INSERT, RI_TRIGTYPE_UPDATE, TriggerData::tg_event, TRIGGER_FIRED_AFTER, TRIGGER_FIRED_BY_DELETE, TRIGGER_FIRED_BY_INSERT, TRIGGER_FIRED_BY_UPDATE, and TRIGGER_FIRED_FOR_ROW.
Referenced by RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check_ins(), RI_FKey_check_upd(), RI_FKey_noaction_del(), RI_FKey_noaction_upd(), RI_FKey_restrict_del(), RI_FKey_restrict_upd(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), and RI_FKey_setnull_upd().
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; if (!CALLED_AS_TRIGGER(fcinfo)) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), errmsg("function \"%s\" was not called by trigger manager", funcname))); /* * Check proper event */ if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), errmsg("function \"%s\" must be fired AFTER ROW", funcname))); switch (tgkind) { case RI_TRIGTYPE_INSERT: if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), errmsg("function \"%s\" must be fired for INSERT", funcname))); break; case RI_TRIGTYPE_UPDATE: if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), errmsg("function \"%s\" must be fired for UPDATE", funcname))); break; case RI_TRIGTYPE_DELETE: if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), errmsg("function \"%s\" must be fired for DELETE", funcname))); break; } }
static void ri_ExtractValues | ( | Relation | rel, | |
HeapTuple | tup, | |||
const RI_ConstraintInfo * | riinfo, | |||
bool | rel_is_pk, | |||
Datum * | vals, | |||
char * | nulls | |||
) | [static] |
Definition at line 3128 of file ri_triggers.c.
References RI_ConstraintInfo::fk_attnums, heap_getattr, i, RI_ConstraintInfo::nkeys, RI_ConstraintInfo::pk_attnums, and RelationData::rd_att.
Referenced by ri_PerformCheck().
{ TupleDesc tupdesc = rel->rd_att; const int16 *attnums; int i; bool isnull; if (rel_is_pk) attnums = riinfo->pk_attnums; else attnums = riinfo->fk_attnums; for (i = 0; i < riinfo->nkeys; i++) { vals[i] = heap_getattr(tup, attnums[i], tupdesc, &isnull); nulls[i] = isnull ? 'n' : ' '; } }
static const RI_ConstraintInfo * ri_FetchConstraintInfo | ( | Trigger * | trigger, | |
Relation | trig_rel, | |||
bool | rel_is_pk | |||
) | [static] |
Definition at line 2736 of file ri_triggers.c.
References elog, ereport, errcode(), errhint(), errmsg(), ERROR, RI_ConstraintInfo::fk_relid, OidIsValid, RI_ConstraintInfo::pk_relid, RelationGetRelationName, RelationGetRelid, ri_LoadConstraintInfo(), Trigger::tgconstraint, Trigger::tgconstrrelid, and Trigger::tgname.
Referenced by RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_fk_upd_check_required(), RI_FKey_pk_upd_check_required(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), RI_Initial_Check(), ri_restrict_del(), and ri_restrict_upd().
{ Oid constraintOid = trigger->tgconstraint; const RI_ConstraintInfo *riinfo; /* * Check that the FK constraint's OID is available; it might not be if * we've been invoked via an ordinary trigger or an old-style "constraint * trigger". */ if (!OidIsValid(constraintOid)) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"", trigger->tgname, RelationGetRelationName(trig_rel)), errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT."))); /* Find or create a hashtable entry for the constraint */ riinfo = ri_LoadConstraintInfo(constraintOid); /* Do some easy cross-checks against the trigger call data */ if (rel_is_pk) { if (riinfo->fk_relid != trigger->tgconstrrelid || riinfo->pk_relid != RelationGetRelid(trig_rel)) elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", trigger->tgname, RelationGetRelationName(trig_rel)); } else { if (riinfo->fk_relid != RelationGetRelid(trig_rel) || riinfo->pk_relid != trigger->tgconstrrelid) elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", trigger->tgname, RelationGetRelationName(trig_rel)); } return riinfo; }
static SPIPlanPtr ri_FetchPreparedPlan | ( | RI_QueryKey * | key | ) | [static] |
Definition at line 3335 of file ri_triggers.c.
References hash_search(), NULL, RI_QueryHashEntry::plan, ri_InitHashTables(), SPI_freeplan(), and SPI_plan_is_valid().
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), ri_restrict_del(), and ri_restrict_upd().
{ RI_QueryHashEntry *entry; SPIPlanPtr plan; /* * 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; /* * Check whether the plan is still valid. If it isn't, we don't want to * simply rely on plancache.c to regenerate it; rather we should start * from scratch and rebuild the query text too. This is to cover cases * such as table/column renames. We depend on the plancache machinery to * detect possible invalidations, though. * * CAUTION: this check is only trustworthy if the caller has already * locked both FK and PK rels. */ plan = entry->plan; if (plan && SPI_plan_is_valid(plan)) return plan; /* * Otherwise we might as well flush the cached plan now, to free a little * memory space before we make a new one. */ entry->plan = NULL; if (plan) SPI_freeplan(plan); return NULL; }
Datum RI_FKey_cascade_del | ( | PG_FUNCTION_ARGS | ) |
Definition at line 1030 of file ri_triggers.c.
References appendStringInfo(), StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), NULL, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_CheckTrigger(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_NullCheck(), ri_PerformCheck(), RI_PLAN_CASCADE_DEL_DODELETE, ri_PlanCheck(), RI_TRIGTYPE_DELETE, RIAttName, RIAttType, RowExclusiveLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_DELETE, SPI_OK_FINISH, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; int i; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE); /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 9) a) i): * MATCH SIMPLE/FULL * ... ON DELETE CASCADE * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a 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(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the cascaded delete */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_DEL_DODELETE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&querybuf, querysep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = "AND"; queryoids[i] = pk_type; } /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Build up the arguments from the key values * in the deleted PK tuple and delete the referencing rows */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* must detect new rows */ SPI_OK_DELETE); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL cascaded delete. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
Datum RI_FKey_cascade_upd | ( | PG_FUNCTION_ARGS | ) |
Definition at line 1186 of file ri_triggers.c.
References appendStringInfo(), appendStringInfoString(), StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), NULL, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_CheckTrigger(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_KeysEqual(), RI_MAX_NUMKEYS, ri_NullCheck(), ri_PerformCheck(), RI_PLAN_CASCADE_UPD_DOUPDATE, ri_PlanCheck(), RI_TRIGTYPE_UPDATE, RIAttName, RIAttType, RowExclusiveLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_UPDATE, TriggerData::tg_newtuple, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; int i; int j; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE); /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 10) a) i): * MATCH SIMPLE/FULL * ... ON UPDATE CASCADE * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a 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, riinfo, true)) { heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); } if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the cascaded update */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; StringInfoData qualbuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; const char *qualsep; Oid queryoids[RI_MAX_NUMKEYS * 2]; /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...] * WHERE $n = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. Note that we are assuming * there is an assignment cast from the PK to the FK type; * else the parser will fail. * ---------- */ initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); appendStringInfo(&querybuf, "%s %s = $%d", querysep, attname, i + 1); sprintf(paramname, "$%d", j + 1); ri_GenerateQual(&qualbuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = ","; qualsep = "AND"; queryoids[i] = pk_type; queryoids[j] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it to update the existing references. */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, new_row, true, /* must detect new rows */ SPI_OK_UPDATE); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL cascade update. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
static Datum RI_FKey_check | ( | TriggerData * | trigdata | ) | [static] |
Definition at line 251 of file ri_triggers.c.
References appendStringInfo(), Assert, RI_ConstraintInfo::confmatchtype, RI_ConstraintInfo::conname, StringInfoData::data, elog, ereport, errcode(), errdetail(), errmsg(), ERROR, errtableconstraint(), RI_ConstraintInfo::fk_attnums, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), HeapTupleSatisfiesVisibility, i, initStringInfo(), InvalidBuffer, NameStr, RI_ConstraintInfo::nkeys, NULL, RI_ConstraintInfo::pf_eq_oprs, RI_ConstraintInfo::pk_attnums, RI_ConstraintInfo::pk_relid, PointerGetDatum, quoteOneName(), quoteRelationName(), RelationGetRelationName, ri_BuildQueryKey(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_NullCheck(), ri_PerformCheck(), RI_PLAN_CHECK_LOOKUPPK, ri_PlanCheck(), RIAttName, RIAttType, RowShareLock, SnapshotSelf, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_SELECT, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, and TRIGGER_FIRED_BY_UPDATE.
Referenced by RI_FKey_check_ins(), and RI_FKey_check_upd().
{ const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; Buffer new_row_buf; RI_QueryKey qkey; SPIPlanPtr qplan; int i; /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, false); if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { new_row = trigdata->tg_newtuple; new_row_buf = trigdata->tg_newtuplebuf; } else { new_row = trigdata->tg_trigtuple; new_row_buf = trigdata->tg_trigtuplebuf; } /* * We should not even consider checking the row if it is no longer valid, * since it was either deleted (so the deferred check should be skipped) * or updated (in which case only the latest version of the row should be * checked). Test its liveness according to SnapshotSelf. * * NOTE: The normal coding rule is that one must acquire the buffer * content lock to call HeapTupleSatisfiesVisibility. We can skip that * here because we know that AfterTriggerExecute just fetched the tuple * successfully, so there cannot be a VACUUM compaction in progress on the * page (either heap_fetch would have waited for the VACUUM, or the * VACUUM's LockBufferForCleanup would be waiting for us to drop pin). And * since this is a row inserted by our open transaction, no one else can * be entitled to change its xmin/xmax. */ Assert(new_row_buf != InvalidBuffer); if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf)) return PointerGetDatum(NULL); /* * Get the relation descriptors of the FK and PK tables. * * pk_rel is opened in RowShareLock mode since that's what our eventual * SELECT FOR KEY SHARE will get on it. */ fk_rel = trigdata->tg_relation; pk_rel = heap_open(riinfo->pk_relid, RowShareLock); if (riinfo->confmatchtype == FKCONSTR_MATCH_PARTIAL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); switch (ri_NullCheck(new_row, riinfo, false)) { case RI_KEYS_ALL_NULL: /* * No further check needed - an all-NULL key passes every type of * foreign key constraint. */ 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 (riinfo->confmatchtype) { case FKCONSTR_MATCH_FULL: /* * Not allowed - MATCH FULL says either all or none of the * attributes can be NULLs */ ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(fk_rel), NameStr(riinfo->conname)), errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), errtableconstraint(fk_rel, NameStr(riinfo->conname)))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); case FKCONSTR_MATCH_SIMPLE: /* * MATCH SIMPLE - if ANY column is null, the key passes * the constraint. */ heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); case FKCONSTR_MATCH_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) */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } case RI_KEYS_NONE_NULL: /* * Have a full qualified key - continue below for all three kinds * of MATCH. */ break; } if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the real check */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; char pkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * SELECT 1 FROM ONLY <pktable> x WHERE pkatt1 = $1 [AND ...] * FOR KEY SHARE OF x * The type id's for the $ parameters are those of the * corresponding FK attributes. * ---------- */ initStringInfo(&querybuf); quoteRelationName(pkrelname, pk_rel); appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[i])); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&querybuf, querysep, attname, pk_type, riinfo->pf_eq_oprs[i], paramname, fk_type); querysep = "AND"; queryoids[i] = fk_type; } appendStringInfo(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * Now check that foreign key exists in PK table */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, NULL, new_row, false, SPI_OK_SELECT); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); }
Datum RI_FKey_check_ins | ( | PG_FUNCTION_ARGS | ) |
Definition at line 462 of file ri_triggers.c.
References ri_CheckTrigger(), RI_FKey_check(), and RI_TRIGTYPE_INSERT.
Referenced by validateForeignKeyConstraint().
{ /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT); /* * Share code with UPDATE case. */ return RI_FKey_check((TriggerData *) fcinfo->context); }
Datum RI_FKey_check_upd | ( | PG_FUNCTION_ARGS | ) |
Definition at line 483 of file ri_triggers.c.
References ri_CheckTrigger(), RI_FKey_check(), and RI_TRIGTYPE_UPDATE.
{ /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE); /* * Share code with INSERT case. */ return RI_FKey_check((TriggerData *) fcinfo->context); }
bool RI_FKey_fk_upd_check_required | ( | Trigger * | trigger, | |
Relation | fk_rel, | |||
HeapTuple | old_row, | |||
HeapTuple | new_row | |||
) |
Definition at line 2140 of file ri_triggers.c.
References RI_ConstraintInfo::confmatchtype, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, HeapTupleHeaderGetXmin, ri_FetchConstraintInfo(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_KeysEqual(), ri_NullCheck(), HeapTupleData::t_data, and TransactionIdIsCurrentTransactionId().
Referenced by AfterTriggerSaveEvent().
{ const RI_ConstraintInfo *riinfo; /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); switch (riinfo->confmatchtype) { case FKCONSTR_MATCH_SIMPLE: /* * If any new key value is NULL, the row must satisfy the * constraint, so no check is needed. */ if (ri_NullCheck(new_row, riinfo, false) != RI_KEYS_NONE_NULL) return false; /* * If the original row was inserted by our own transaction, we * must fire the trigger whether or not the keys are equal. This * is because our UPDATE will invalidate the INSERT so that the * INSERT RI trigger will not do anything; so we had better do the * UPDATE check. (We could skip this if we knew the INSERT * trigger already fired, but there is no easy way to know that.) */ if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) return true; /* If all old and new key values are equal, no check is needed */ if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false)) return false; /* Else we need to fire the trigger. */ return true; case FKCONSTR_MATCH_FULL: /* * If all new key values are NULL, the row must satisfy the * constraint, so no check is needed. On the other hand, if only * some of them are NULL, the row must fail the constraint. We * must not throw error here, because the row might get * invalidated before the constraint is to be checked, but we * should queue the event to apply the check later. */ switch (ri_NullCheck(new_row, riinfo, false)) { case RI_KEYS_ALL_NULL: return false; case RI_KEYS_SOME_NULL: return true; case RI_KEYS_NONE_NULL: break; /* continue with the check */ } /* * If the original row was inserted by our own transaction, we * must fire the trigger whether or not the keys are equal. This * is because our UPDATE will invalidate the INSERT so that the * INSERT RI trigger will not do anything; so we had better do the * UPDATE check. (We could skip this if we knew the INSERT * trigger already fired, but there is no easy way to know that.) */ if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) return true; /* If all old and new key values are equal, no check is needed */ if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false)) return false; /* Else we need to fire the trigger. */ return true; /* Handle MATCH PARTIAL check. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); break; default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return false; }
Datum RI_FKey_noaction_del | ( | PG_FUNCTION_ARGS | ) |
Definition at line 597 of file ri_triggers.c.
References ri_CheckTrigger(), ri_restrict_del(), and RI_TRIGTYPE_DELETE.
Referenced by RI_FKey_setdefault_del().
{ /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE); /* * Share code with RESTRICT case. */ return ri_restrict_del((TriggerData *) fcinfo->context, true); }
Datum RI_FKey_noaction_upd | ( | PG_FUNCTION_ARGS | ) |
Definition at line 809 of file ri_triggers.c.
References ri_CheckTrigger(), ri_restrict_upd(), and RI_TRIGTYPE_UPDATE.
Referenced by RI_FKey_setdefault_upd().
{ /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE); /* * Share code with RESTRICT case. */ return ri_restrict_upd((TriggerData *) fcinfo->context, true); }
bool RI_FKey_pk_upd_check_required | ( | Trigger * | trigger, | |
Relation | pk_rel, | |||
HeapTuple | old_row, | |||
HeapTuple | new_row | |||
) |
Definition at line 2083 of file ri_triggers.c.
References RI_ConstraintInfo::confmatchtype, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, ri_FetchConstraintInfo(), RI_KEYS_NONE_NULL, ri_KeysEqual(), and ri_NullCheck().
Referenced by AfterTriggerSaveEvent().
{ const RI_ConstraintInfo *riinfo; /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true); switch (riinfo->confmatchtype) { case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: /* * If any old key value is NULL, the row could not have been * referenced by an FK row, so no check is needed. */ if (ri_NullCheck(old_row, riinfo, true) != RI_KEYS_NONE_NULL) return false; /* If all old and new key values are equal, no check is needed */ if (ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true)) return false; /* Else we need to fire the trigger. */ return true; /* Handle MATCH PARTIAL check. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); break; default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return false; }
Datum RI_FKey_restrict_del | ( | PG_FUNCTION_ARGS | ) |
Definition at line 622 of file ri_triggers.c.
References ri_CheckTrigger(), ri_restrict_del(), and RI_TRIGTYPE_DELETE.
{ /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE); /* * Share code with NO ACTION case. */ return ri_restrict_del((TriggerData *) fcinfo->context, false); }
Datum RI_FKey_restrict_upd | ( | PG_FUNCTION_ARGS | ) |
Definition at line 834 of file ri_triggers.c.
References ri_CheckTrigger(), ri_restrict_upd(), and RI_TRIGTYPE_UPDATE.
{ /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE); /* * Share code with NO ACTION case. */ return ri_restrict_upd((TriggerData *) fcinfo->context, false); }
Datum RI_FKey_setdefault_del | ( | PG_FUNCTION_ARGS | ) |
Definition at line 1708 of file ri_triggers.c.
References appendStringInfo(), appendStringInfoString(), StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), NULL, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_CheckTrigger(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), RI_FKey_noaction_del(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_NullCheck(), ri_PerformCheck(), RI_PLAN_SETDEFAULT_DEL_DOUPDATE, ri_PlanCheck(), RI_TRIGTYPE_DELETE, RIAttName, RIAttType, RowExclusiveLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_UPDATE, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE); /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 9) a) iii): * MATCH SIMPLE/FULL * ... ON DELETE SET DEFAULT * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a 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(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the set default delete * operation */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_DEL_DOUPDATE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; StringInfoData qualbuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; const char *qualsep; Oid queryoids[RI_MAX_NUMKEYS]; int i; /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...] * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); appendStringInfo(&querybuf, "%s %s = DEFAULT", querysep, attname); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&qualbuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = ","; qualsep = "AND"; queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it to update the existing references. */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowExclusiveLock); /* * If we just deleted the PK row whose key was equal to the FK * columns' default values, and a referencing row exists in the FK * table, we would have updated that row to the same values it * already had --- and RI_FKey_fk_upd_check_required would hence * believe no check is necessary. So we need to do another lookup * now and in case a reference still exists, abort the operation. * That is already implemented in the NO ACTION trigger, so just * run it. (This recheck is only needed in the SET DEFAULT case, * since CASCADE would remove such rows, while SET NULL is certain * to result in rows that satisfy the FK constraint.) */ RI_FKey_noaction_del(fcinfo); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL set default delete. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
Datum RI_FKey_setdefault_upd | ( | PG_FUNCTION_ARGS | ) |
Definition at line 1888 of file ri_triggers.c.
References appendStringInfo(), appendStringInfoString(), StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), NULL, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_CheckTrigger(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), RI_FKey_noaction_upd(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_KeysEqual(), ri_NullCheck(), ri_PerformCheck(), RI_PLAN_SETDEFAULT_UPD_DOUPDATE, ri_PlanCheck(), RI_TRIGTYPE_UPDATE, RIAttName, RIAttType, RowExclusiveLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_UPDATE, TriggerData::tg_newtuple, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE); /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 10) a) iii): * MATCH SIMPLE/FULL * ... ON UPDATE SET DEFAULT * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a 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, riinfo, true)) { heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); } if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the set default update * operation */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETDEFAULT_UPD_DOUPDATE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; StringInfoData qualbuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; const char *qualsep; Oid queryoids[RI_MAX_NUMKEYS]; int i; /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...] * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); appendStringInfo(&querybuf, "%s %s = DEFAULT", querysep, attname); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&qualbuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = ","; qualsep = "AND"; queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it to update the existing references. */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowExclusiveLock); /* * If we just updated the PK row whose key was equal to the FK * columns' default values, and a referencing row exists in the FK * table, we would have updated that row to the same values it * already had --- and RI_FKey_fk_upd_check_required would hence * believe no check is necessary. So we need to do another lookup * now and in case a reference still exists, abort the operation. * That is already implemented in the NO ACTION trigger, so just * run it. (This recheck is only needed in the SET DEFAULT case, * since CASCADE must change the FK key values, while SET NULL is * certain to result in rows that satisfy the FK constraint.) */ RI_FKey_noaction_upd(fcinfo); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL set default update. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
Datum RI_FKey_setnull_del | ( | PG_FUNCTION_ARGS | ) |
Definition at line 1367 of file ri_triggers.c.
References appendStringInfo(), appendStringInfoString(), StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), NULL, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_CheckTrigger(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_NullCheck(), ri_PerformCheck(), RI_PLAN_SETNULL_DEL_DOUPDATE, ri_PlanCheck(), RI_TRIGTYPE_DELETE, RIAttName, RIAttType, RowExclusiveLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_UPDATE, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; int i; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE); /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 9) a) ii): * MATCH SIMPLE/FULL * ... ON DELETE SET NULL * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a 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(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the set null delete operation */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_DEL_DOUPDATE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; StringInfoData qualbuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; const char *qualsep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...] * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); appendStringInfo(&querybuf, "%s %s = NULL", querysep, attname); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&qualbuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = ","; qualsep = "AND"; queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it to check for existing references. */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL set null delete. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
Datum RI_FKey_setnull_upd | ( | PG_FUNCTION_ARGS | ) |
Definition at line 1532 of file ri_triggers.c.
References appendStringInfo(), appendStringInfoString(), StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), NULL, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_CheckTrigger(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_KeysEqual(), ri_NullCheck(), ri_PerformCheck(), RI_PLAN_SETNULL_UPD_DOUPDATE, ri_PlanCheck(), RI_TRIGTYPE_UPDATE, RIAttName, RIAttType, RowExclusiveLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_UPDATE, TriggerData::tg_newtuple, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; int i; /* * Check that this is a valid trigger call on the right time and event. */ ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE); /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 10) a) ii): * MATCH SIMPLE/FULL * ... ON UPDATE SET NULL * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a 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, riinfo, true)) { heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); } if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the set null update operation */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_SETNULL_UPD_DOUPDATE); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; StringInfoData qualbuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; const char *qualsep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...] * WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); initStringInfo(&qualbuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); querysep = ""; qualsep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); appendStringInfo(&querybuf, "%s %s = NULL", querysep, attname); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&qualbuf, qualsep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = ","; qualsep = "AND"; queryoids[i] = pk_type; } appendStringInfoString(&querybuf, qualbuf.data); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it to update the existing references. */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* must detect new rows */ SPI_OK_UPDATE); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowExclusiveLock); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL set null update. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
int RI_FKey_trigger_type | ( | Oid | tgfoid | ) |
Definition at line 3611 of file ri_triggers.c.
Referenced by AfterTriggerSaveEvent(), and CreateTrigger().
{ switch (tgfoid) { case F_RI_FKEY_CASCADE_DEL: case F_RI_FKEY_CASCADE_UPD: case F_RI_FKEY_RESTRICT_DEL: case F_RI_FKEY_RESTRICT_UPD: case F_RI_FKEY_SETNULL_DEL: case F_RI_FKEY_SETNULL_UPD: case F_RI_FKEY_SETDEFAULT_DEL: case F_RI_FKEY_SETDEFAULT_UPD: case F_RI_FKEY_NOACTION_DEL: case F_RI_FKEY_NOACTION_UPD: return RI_TRIGGER_PK; case F_RI_FKEY_CHECK_INS: case F_RI_FKEY_CHECK_UPD: return RI_TRIGGER_FK; } return RI_TRIGGER_NONE; }
static void ri_GenerateQual | ( | StringInfo | buf, | |
const char * | sep, | |||
const char * | leftop, | |||
Oid | leftoptype, | |||
Oid | opoid, | |||
const char * | rightop, | |||
Oid | rightoptype | |||
) | [static] |
Definition at line 2554 of file ri_triggers.c.
References appendStringInfo(), appendStringInfoString(), Assert, elog, ERROR, get_namespace_name(), GETSTRUCT, HeapTupleIsValid, NameStr, ObjectIdGetDatum, OPEROID, quote_identifier(), ReleaseSysCache(), ri_add_cast_to(), and SearchSysCache1.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), RI_Initial_Check(), ri_restrict_del(), and ri_restrict_upd().
{ HeapTuple opertup; Form_pg_operator operform; char *oprname; char *nspname; opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid)); if (!HeapTupleIsValid(opertup)) elog(ERROR, "cache lookup failed for operator %u", opoid); operform = (Form_pg_operator) GETSTRUCT(opertup); Assert(operform->oprkind == 'b'); oprname = NameStr(operform->oprname); nspname = get_namespace_name(operform->oprnamespace); appendStringInfo(buf, " %s %s", sep, leftop); if (leftoptype != operform->oprleft) ri_add_cast_to(buf, operform->oprleft); appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname)); appendStringInfoString(buf, oprname); appendStringInfo(buf, ") %s", rightop); if (rightoptype != operform->oprright) ri_add_cast_to(buf, operform->oprright); ReleaseSysCache(opertup); }
static void ri_GenerateQualCollation | ( | StringInfo | buf, | |
Oid | collation | |||
) | [static] |
Definition at line 2634 of file ri_triggers.c.
References appendStringInfo(), COLLOID, elog, ERROR, get_namespace_name(), GETSTRUCT, HeapTupleIsValid, NameStr, ObjectIdGetDatum, OidIsValid, quoteOneName(), ReleaseSysCache(), and SearchSysCache1.
Referenced by RI_Initial_Check().
{ HeapTuple tp; Form_pg_collation colltup; char *collname; char onename[MAX_QUOTED_NAME_LEN]; /* Nothing to do if it's a noncollatable data type */ if (!OidIsValid(collation)) return; tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation)); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for collation %u", collation); colltup = (Form_pg_collation) GETSTRUCT(tp); collname = NameStr(colltup->collname); /* * We qualify the name always, for simplicity and to ensure the query is * not search-path-dependent. */ quoteOneName(onename, get_namespace_name(colltup->collnamespace)); appendStringInfo(buf, " COLLATE %s", onename); quoteOneName(onename, collname); appendStringInfo(buf, ".%s", onename); ReleaseSysCache(tp); }
static RI_CompareHashEntry * ri_HashCompareOp | ( | Oid | eq_opr, | |
Oid | typeid | |||
) | [static] |
Definition at line 3517 of file ri_triggers.c.
References Assert, RI_CompareHashEntry::cast_func_finfo, COERCION_IMPLICIT, COERCION_PATH_FUNC, COERCION_PATH_RELABELTYPE, elog, RI_CompareKey::eq_opr, RI_CompareHashEntry::eq_opr_finfo, ERROR, find_coercion_pathway(), fmgr_info_cxt(), FmgrInfo::fn_oid, format_type_be(), get_opcode(), hash_search(), IsBinaryCoercible(), IsPolymorphicType, OidIsValid, op_input_types(), ri_InitHashTables(), TopMemoryContext, RI_CompareKey::typeid, and RI_CompareHashEntry::valid.
Referenced by ri_AttributesEqual().
{ RI_CompareKey key; RI_CompareHashEntry *entry; bool found; /* * On the first call initialize the hashtable */ if (!ri_compare_cache) ri_InitHashTables(); /* * Find or create a hash entry. Note we're assuming RI_CompareKey * contains no struct padding. */ key.eq_opr = eq_opr; key.typeid = typeid; entry = (RI_CompareHashEntry *) hash_search(ri_compare_cache, (void *) &key, HASH_ENTER, &found); if (!found) entry->valid = false; /* * If not already initialized, do so. Since we'll keep this hash entry * for the life of the backend, put any subsidiary info for the function * cache structs into TopMemoryContext. */ if (!entry->valid) { Oid lefttype, righttype, castfunc; CoercionPathType pathtype; /* We always need to know how to call the equality operator */ fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo, TopMemoryContext); /* * If we chose to use a cast from FK to PK type, we may have to apply * the cast function to get to the operator's input type. * * XXX eventually it would be good to support array-coercion cases * here and in ri_AttributesEqual(). At the moment there is no point * because cases involving nonidentical array types will be rejected * at constraint creation time. * * XXX perhaps also consider supporting CoerceViaIO? No need at the * moment since that will never be generated for implicit coercions. */ op_input_types(eq_opr, &lefttype, &righttype); Assert(lefttype == righttype); if (typeid == lefttype) castfunc = InvalidOid; /* simplest case */ else { pathtype = find_coercion_pathway(lefttype, typeid, COERCION_IMPLICIT, &castfunc); if (pathtype != COERCION_PATH_FUNC && pathtype != COERCION_PATH_RELABELTYPE) { /* * The declared input type of the eq_opr might be a * polymorphic type such as ANYARRAY or ANYENUM, or other * special cases such as RECORD; find_coercion_pathway * currently doesn't subsume these special cases. */ if (!IsPolymorphicType(lefttype) && !IsBinaryCoercible(typeid, lefttype)) elog(ERROR, "no conversion function from %s to %s", format_type_be(typeid), format_type_be(lefttype)); } } if (OidIsValid(castfunc)) fmgr_info_cxt(castfunc, &entry->cast_func_finfo, TopMemoryContext); else entry->cast_func_finfo.fn_oid = InvalidOid; entry->valid = true; } return entry; }
static void ri_HashPreparedPlan | ( | RI_QueryKey * | key, | |
SPIPlanPtr | plan | |||
) | [static] |
Definition at line 3388 of file ri_triggers.c.
References Assert, hash_search(), NULL, RI_QueryHashEntry::plan, and ri_InitHashTables().
Referenced by ri_PlanCheck().
{ RI_QueryHashEntry *entry; bool found; /* * On the first call initialize the hashtable */ if (!ri_query_cache) ri_InitHashTables(); /* * Add the new plan. We might be overwriting an entry previously found * invalid by ri_FetchPreparedPlan. */ entry = (RI_QueryHashEntry *) hash_search(ri_query_cache, (void *) key, HASH_ENTER, &found); Assert(!found || entry->plan == NULL); entry->plan = plan; }
static void ri_InitHashTables | ( | void | ) | [static] |
Definition at line 3292 of file ri_triggers.c.
References CacheRegisterSyscacheCallback(), CONSTROID, HASHCTL::entrysize, HASHCTL::hash, hash_create(), HASH_ELEM, HASH_FUNCTION, InvalidateConstraintCacheCallBack(), HASHCTL::keysize, RI_INIT_CONSTRAINTHASHSIZE, and RI_INIT_QUERYHASHSIZE.
Referenced by ri_FetchPreparedPlan(), ri_HashCompareOp(), ri_HashPreparedPlan(), and ri_LoadConstraintInfo().
{ HASHCTL ctl; memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(Oid); ctl.entrysize = sizeof(RI_ConstraintInfo); ctl.hash = oid_hash; ri_constraint_cache = hash_create("RI constraint cache", RI_INIT_CONSTRAINTHASHSIZE, &ctl, HASH_ELEM | HASH_FUNCTION); /* Arrange to flush cache on pg_constraint changes */ CacheRegisterSyscacheCallback(CONSTROID, InvalidateConstraintCacheCallBack, (Datum) 0); 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); memset(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(RI_CompareKey); ctl.entrysize = sizeof(RI_CompareHashEntry); ctl.hash = tag_hash; ri_compare_cache = hash_create("RI compare cache", RI_INIT_QUERYHASHSIZE, &ctl, HASH_ELEM | HASH_FUNCTION); }
Definition at line 2251 of file ri_triggers.c.
References appendStringInfo(), AtEOXact_GUC(), bms_add_member(), RI_ConstraintInfo::confmatchtype, RI_ConstraintInfo::conname, StringInfoData::data, elog, ereport, errcode(), errdetail(), errmsg(), ERROR, errtableconstraint(), ExecCheckRTPerms(), RI_ConstraintInfo::fk_attnums, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, GetLatestSnapshot(), GUC_ACTION_SAVE, i, initStringInfo(), InvalidSnapshot, list_make2, maintenance_work_mem, makeNode, MAX_QUOTED_NAME_LEN, NameStr, NewGUCNestLevel(), RI_ConstraintInfo::nkeys, NULL, RI_ConstraintInfo::pf_eq_oprs, PGC_S_SESSION, PGC_USERSET, RI_ConstraintInfo::pk_attnums, quoteOneName(), quoteRelationName(), RelationData::rd_rel, RelationGetRelationName, RelationGetRelid, RangeTblEntry::relid, RangeTblEntry::relkind, RangeTblEntry::requiredPerms, ri_FetchConstraintInfo(), ri_GenerateQual(), ri_GenerateQualCollation(), RI_KEYS_NONE_NULL, ri_NullCheck(), RI_PLAN_CHECK_LOOKUPPK, ri_ReportViolation(), RIAttCollation, RIAttName, RIAttType, RangeTblEntry::rtekind, RangeTblEntry::selectedCols, set_config_option(), snprintf(), SPI_connect(), SPI_execute_snapshot(), SPI_finish(), SPI_OK_FINISH, SPI_OK_SELECT, SPI_prepare(), SPI_processed, SPI_result, SPI_tuptable, SPITupleTable::tupdesc, and SPITupleTable::vals.
Referenced by validateForeignKeyConstraint().
{ const RI_ConstraintInfo *riinfo; StringInfoData querybuf; char pkrelname[MAX_QUOTED_REL_NAME_LEN]; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char pkattname[MAX_QUOTED_NAME_LEN + 3]; char fkattname[MAX_QUOTED_NAME_LEN + 3]; RangeTblEntry *pkrte; RangeTblEntry *fkrte; const char *sep; int i; int save_nestlevel; char workmembuf[32]; int spi_result; SPIPlanPtr qplan; /* Fetch constraint info. */ riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); /* * Check to make sure current user has enough permissions to do the test * query. (If not, caller can fall back to the trigger method, which * works because it changes user IDs on the fly.) * * XXX are there any other show-stopper conditions to check? */ pkrte = makeNode(RangeTblEntry); pkrte->rtekind = RTE_RELATION; pkrte->relid = RelationGetRelid(pk_rel); pkrte->relkind = pk_rel->rd_rel->relkind; pkrte->requiredPerms = ACL_SELECT; fkrte = makeNode(RangeTblEntry); fkrte->rtekind = RTE_RELATION; fkrte->relid = RelationGetRelid(fk_rel); fkrte->relkind = fk_rel->rd_rel->relkind; fkrte->requiredPerms = ACL_SELECT; for (i = 0; i < riinfo->nkeys; i++) { int attno; attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber; pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno); attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber; fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno); } if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false)) return false; /*---------- * The query string built is: * SELECT fk.keycols FROM ONLY relname fk * LEFT OUTER JOIN ONLY pkrelname pk * ON (pk.pkkeycol1=fk.keycol1 [AND ...]) * WHERE pk.pkkeycol1 IS NULL AND * For MATCH SIMPLE: * (fk.keycol1 IS NOT NULL [AND ...]) * For MATCH FULL: * (fk.keycol1 IS NOT NULL [OR ...]) * * We attach COLLATE clauses to the operators when comparing columns * that have different collations. *---------- */ initStringInfo(&querybuf); appendStringInfo(&querybuf, "SELECT "); sep = ""; for (i = 0; i < riinfo->nkeys; i++) { quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i])); appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname); sep = ", "; } quoteRelationName(pkrelname, pk_rel); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON", fkrelname, pkrelname); strcpy(pkattname, "pk."); strcpy(fkattname, "fk."); sep = "("; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); quoteOneName(pkattname + 3, RIAttName(pk_rel, riinfo->pk_attnums[i])); quoteOneName(fkattname + 3, RIAttName(fk_rel, riinfo->fk_attnums[i])); ri_GenerateQual(&querybuf, sep, pkattname, pk_type, riinfo->pf_eq_oprs[i], fkattname, fk_type); if (pk_coll != fk_coll) ri_GenerateQualCollation(&querybuf, pk_coll); sep = "AND"; } /* * It's sufficient to test any one pk attribute for null to detect a join * failure. */ quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0])); appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname); sep = ""; for (i = 0; i < riinfo->nkeys; i++) { quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i])); appendStringInfo(&querybuf, "%sfk.%s IS NOT NULL", sep, fkattname); switch (riinfo->confmatchtype) { case FKCONSTR_MATCH_SIMPLE: sep = " AND "; break; case FKCONSTR_MATCH_FULL: sep = " OR "; break; case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); break; default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } } appendStringInfo(&querybuf, ")"); /* * Temporarily increase work_mem so that the check query can be executed * more efficiently. It seems okay to do this because the query is simple * enough to not use a multiple of work_mem, and one typically would not * have many large foreign-key validations happening concurrently. So * this seems to meet the criteria for being considered a "maintenance" * operation, and accordingly we use maintenance_work_mem. * * We use the equivalent of a function SET option to allow the setting to * persist for exactly the duration of the check query. guc.c also takes * care of undoing the setting on error. */ save_nestlevel = NewGUCNestLevel(); snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem); (void) set_config_option("work_mem", workmembuf, PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Generate the plan. We don't need to cache it, and there are no * arguments to the plan. */ qplan = SPI_prepare(querybuf.data, 0, NULL); if (qplan == NULL) elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querybuf.data); /* * Run the plan. For safety we force a current snapshot to be used. (In * transaction-snapshot mode, this arguably violates transaction isolation * rules, but we really haven't got much choice.) We don't need to * register the snapshot, because SPI_execute_snapshot will see to it. We * need at most one tuple returned, so pass limit = 1. */ spi_result = SPI_execute_snapshot(qplan, NULL, NULL, GetLatestSnapshot(), InvalidSnapshot, true, false, 1); /* Check result */ if (spi_result != SPI_OK_SELECT) elog(ERROR, "SPI_execute_snapshot returned %d", spi_result); /* Did we find a tuple violating the constraint? */ if (SPI_processed > 0) { HeapTuple tuple = SPI_tuptable->vals[0]; TupleDesc tupdesc = SPI_tuptable->tupdesc; RI_ConstraintInfo fake_riinfo; /* * The columns to look at in the result tuple are 1..N, not whatever * they are in the fk_rel. Hack up riinfo so that the subroutines * called here will behave properly. * * In addition to this, we have to pass the correct tupdesc to * ri_ReportViolation, overriding its normal habit of using the pk_rel * or fk_rel's tupdesc. */ memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo)); for (i = 0; i < fake_riinfo.nkeys; i++) fake_riinfo.fk_attnums[i] = i + 1; /* * If it's MATCH FULL, and there are any nulls in the FK keys, * complain about that rather than the lack of a match. MATCH FULL * disallows partially-null FK rows. */ if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL && ri_NullCheck(tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(fk_rel), NameStr(fake_riinfo.conname)), errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), errtableconstraint(fk_rel, NameStr(fake_riinfo.conname)))); /* * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK * query, which isn't true, but will cause it to use * fake_riinfo.fk_attnums as we need. */ ri_ReportViolation(&fake_riinfo, pk_rel, fk_rel, tuple, tupdesc, RI_PLAN_CHECK_LOOKUPPK, false); } if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); /* * Restore work_mem. */ AtEOXact_GUC(true, save_nestlevel); return true; }
static bool ri_KeysEqual | ( | Relation | rel, | |
HeapTuple | oldtup, | |||
HeapTuple | newtup, | |||
const RI_ConstraintInfo * | riinfo, | |||
bool | rel_is_pk | |||
) | [static] |
Definition at line 3423 of file ri_triggers.c.
References RI_ConstraintInfo::ff_eq_oprs, RI_ConstraintInfo::fk_attnums, heap_getattr, i, RI_ConstraintInfo::nkeys, RI_ConstraintInfo::pk_attnums, RI_ConstraintInfo::pp_eq_oprs, RelationGetDescr, ri_AttributesEqual(), and RIAttType.
Referenced by RI_FKey_cascade_upd(), RI_FKey_fk_upd_check_required(), RI_FKey_pk_upd_check_required(), RI_FKey_setdefault_upd(), RI_FKey_setnull_upd(), and ri_restrict_upd().
{ TupleDesc tupdesc = RelationGetDescr(rel); const int16 *attnums; const Oid *eq_oprs; int i; if (rel_is_pk) { attnums = riinfo->pk_attnums; eq_oprs = riinfo->pp_eq_oprs; } else { attnums = riinfo->fk_attnums; eq_oprs = riinfo->ff_eq_oprs; } for (i = 0; i < riinfo->nkeys; i++) { Datum oldvalue; Datum newvalue; bool isnull; /* * Get one attribute's oldvalue. If it is NULL - they're not equal. */ oldvalue = heap_getattr(oldtup, attnums[i], tupdesc, &isnull); if (isnull) return false; /* * Get one attribute's newvalue. If it is NULL - they're not equal. */ newvalue = heap_getattr(newtup, attnums[i], tupdesc, &isnull); if (isnull) return false; /* * Compare them with the appropriate equality operator. */ if (!ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]), oldvalue, newvalue)) return false; } return true; }
static const RI_ConstraintInfo * ri_LoadConstraintInfo | ( | Oid | constraintOid | ) | [static] |
Definition at line 2779 of file ri_triggers.c.
References Anum_pg_constraint_conffeqop, Anum_pg_constraint_confkey, Anum_pg_constraint_conkey, Anum_pg_constraint_conpfeqop, Anum_pg_constraint_conppeqop, ARR_DATA_PTR, ARR_DIMS, ARR_ELEMTYPE, ARR_HASNULL, ARR_NDIM, Assert, RI_ConstraintInfo::confdeltype, RI_ConstraintInfo::confmatchtype, RI_ConstraintInfo::confupdtype, RI_ConstraintInfo::conname, CONSTRAINT_FOREIGN, RI_ConstraintInfo::constraint_id, CONSTROID, DatumGetArrayTypeP, DatumGetPointer, elog, ERROR, RI_ConstraintInfo::ff_eq_oprs, RI_ConstraintInfo::fk_attnums, RI_ConstraintInfo::fk_relid, GETSTRUCT, GetSysCacheHashValue1, hash_search(), HeapTupleIsValid, INT2OID, RI_ConstraintInfo::nkeys, ObjectIdGetDatum, RI_ConstraintInfo::oidHashValue, OIDOID, RI_ConstraintInfo::pf_eq_oprs, pfree(), RI_ConstraintInfo::pk_attnums, RI_ConstraintInfo::pk_relid, RI_ConstraintInfo::pp_eq_oprs, ReleaseSysCache(), ri_InitHashTables(), RI_MAX_NUMKEYS, SearchSysCache1, SysCacheGetAttr(), and RI_ConstraintInfo::valid.
Referenced by ri_FetchConstraintInfo().
{ RI_ConstraintInfo *riinfo; bool found; HeapTuple tup; Form_pg_constraint conForm; Datum adatum; bool isNull; ArrayType *arr; int numkeys; /* * On the first call initialize the hashtable */ if (!ri_constraint_cache) ri_InitHashTables(); /* * Find or create a hash entry. If we find a valid one, just return it. */ riinfo = (RI_ConstraintInfo *) hash_search(ri_constraint_cache, (void *) &constraintOid, HASH_ENTER, &found); if (!found) riinfo->valid = false; else if (riinfo->valid) return riinfo; /* * Fetch the pg_constraint row so we can fill in the entry. */ tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid)); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for constraint %u", constraintOid); conForm = (Form_pg_constraint) GETSTRUCT(tup); if (conForm->contype != CONSTRAINT_FOREIGN) /* should not happen */ elog(ERROR, "constraint %u is not a foreign key constraint", constraintOid); /* And extract data */ Assert(riinfo->constraint_id == constraintOid); riinfo->oidHashValue = GetSysCacheHashValue1(CONSTROID, ObjectIdGetDatum(constraintOid)); memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData)); riinfo->pk_relid = conForm->confrelid; riinfo->fk_relid = conForm->conrelid; riinfo->confupdtype = conForm->confupdtype; riinfo->confdeltype = conForm->confdeltype; riinfo->confmatchtype = conForm->confmatchtype; /* * We expect the arrays to be 1-D arrays of the right types; verify that. * We don't need to use deconstruct_array() since the array data is just * going to look like a C array of values. */ adatum = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conkey, &isNull); if (isNull) elog(ERROR, "null conkey for constraint %u", constraintOid); arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != INT2OID) elog(ERROR, "conkey is not a 1-D smallint array"); numkeys = ARR_DIMS(arr)[0]; if (numkeys <= 0 || numkeys > RI_MAX_NUMKEYS) elog(ERROR, "foreign key constraint cannot have %d columns", numkeys); riinfo->nkeys = numkeys; memcpy(riinfo->fk_attnums, ARR_DATA_PTR(arr), numkeys * sizeof(int16)); if ((Pointer) arr != DatumGetPointer(adatum)) pfree(arr); /* free de-toasted copy, if any */ adatum = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_confkey, &isNull); if (isNull) elog(ERROR, "null confkey for constraint %u", constraintOid); arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numkeys || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != INT2OID) elog(ERROR, "confkey is not a 1-D smallint array"); memcpy(riinfo->pk_attnums, ARR_DATA_PTR(arr), numkeys * sizeof(int16)); if ((Pointer) arr != DatumGetPointer(adatum)) pfree(arr); /* free de-toasted copy, if any */ adatum = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conpfeqop, &isNull); if (isNull) elog(ERROR, "null conpfeqop for constraint %u", constraintOid); arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ /* see TryReuseForeignKey if you change the test below */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numkeys || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "conpfeqop is not a 1-D Oid array"); memcpy(riinfo->pf_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid)); if ((Pointer) arr != DatumGetPointer(adatum)) pfree(arr); /* free de-toasted copy, if any */ adatum = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conppeqop, &isNull); if (isNull) elog(ERROR, "null conppeqop for constraint %u", constraintOid); arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numkeys || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "conppeqop is not a 1-D Oid array"); memcpy(riinfo->pp_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid)); if ((Pointer) arr != DatumGetPointer(adatum)) pfree(arr); /* free de-toasted copy, if any */ adatum = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conffeqop, &isNull); if (isNull) elog(ERROR, "null conffeqop for constraint %u", constraintOid); arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ if (ARR_NDIM(arr) != 1 || ARR_DIMS(arr)[0] != numkeys || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "conffeqop is not a 1-D Oid array"); memcpy(riinfo->ff_eq_oprs, ARR_DATA_PTR(arr), numkeys * sizeof(Oid)); if ((Pointer) arr != DatumGetPointer(adatum)) pfree(arr); /* free de-toasted copy, if any */ ReleaseSysCache(tup); riinfo->valid = true; return riinfo; }
static int ri_NullCheck | ( | HeapTuple | tup, | |
const RI_ConstraintInfo * | riinfo, | |||
bool | rel_is_pk | |||
) | [static] |
Definition at line 3254 of file ri_triggers.c.
References RI_ConstraintInfo::fk_attnums, heap_attisnull(), i, RI_ConstraintInfo::nkeys, and RI_ConstraintInfo::pk_attnums.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_fk_upd_check_required(), RI_FKey_pk_upd_check_required(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), RI_Initial_Check(), ri_restrict_del(), and ri_restrict_upd().
{ const int16 *attnums; int i; bool allnull = true; bool nonenull = true; if (rel_is_pk) attnums = riinfo->pk_attnums; else attnums = riinfo->fk_attnums; for (i = 0; i < riinfo->nkeys; i++) { if (heap_attisnull(tup, attnums[i])) nonenull = false; else allnull = false; } if (allnull) return RI_KEYS_ALL_NULL; if (nonenull) return RI_KEYS_NONE_NULL; return RI_KEYS_SOME_NULL; }
static bool ri_PerformCheck | ( | const RI_ConstraintInfo * | riinfo, | |
RI_QueryKey * | qkey, | |||
SPIPlanPtr | qplan, | |||
Relation | fk_rel, | |||
Relation | pk_rel, | |||
HeapTuple | old_tuple, | |||
HeapTuple | new_tuple, | |||
bool | detectNewRows, | |||
int | expect_OK | |||
) | [static] |
Definition at line 2994 of file ri_triggers.c.
References CommandCounterIncrement(), RI_QueryKey::constr_queryno, elog, ERROR, GetLatestSnapshot(), GetTransactionSnapshot(), GetUserIdAndSecContext(), IsolationUsesXactSnapshot, RI_ConstraintInfo::nkeys, NULL, RelationGetForm, ri_ExtractValues(), RI_MAX_NUMKEYS, RI_PLAN_CHECK_LOOKUPPK, RI_PLAN_CHECK_LOOKUPPK_FROM_PK, RI_PLAN_LAST_ON_PK, ri_ReportViolation(), SECURITY_LOCAL_USERID_CHANGE, SetUserIdAndSecContext(), SPI_execute_snapshot(), SPI_OK_SELECT, and SPI_processed.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), ri_restrict_del(), and ri_restrict_upd().
{ Relation query_rel, source_rel; bool source_is_pk; Snapshot test_snapshot; Snapshot crosscheck_snapshot; int limit; int spi_result; Oid save_userid; int save_sec_context; Datum vals[RI_MAX_NUMKEYS * 2]; char nulls[RI_MAX_NUMKEYS * 2]; /* * Use the query type code to determine whether the query is run against * the PK or FK table; we'll do the check as that table's owner */ if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK) query_rel = pk_rel; else query_rel = fk_rel; /* * The values for the query are taken from the table on which the trigger * is called - it is normally the other one with respect to query_rel. * An exception is ri_Check_Pk_Match(), which uses the PK table for both * (and sets queryno to RI_PLAN_CHECK_LOOKUPPK_FROM_PK). We might * eventually need some less klugy way to determine this. */ if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK) { source_rel = fk_rel; source_is_pk = false; } else { source_rel = pk_rel; source_is_pk = true; } /* Extract the parameters to be passed into the query */ if (new_tuple) { ri_ExtractValues(source_rel, new_tuple, riinfo, source_is_pk, vals, nulls); if (old_tuple) ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk, vals + riinfo->nkeys, nulls + riinfo->nkeys); } else { ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk, vals, nulls); } /* * In READ COMMITTED mode, we just need to use an up-to-date regular * snapshot, and we will see all rows that could be interesting. But in * transaction-snapshot mode, we can't change the transaction snapshot. If * the caller passes detectNewRows == false then it's okay to do the query * with the transaction snapshot; otherwise we use a current snapshot, and * tell the executor to error out if it finds any rows under the current * snapshot that wouldn't be visible per the transaction snapshot. Note * that SPI_execute_snapshot will register the snapshots, so we don't need * to bother here. */ if (IsolationUsesXactSnapshot() && detectNewRows) { CommandCounterIncrement(); /* be sure all my own work is visible */ test_snapshot = GetLatestSnapshot(); crosscheck_snapshot = GetTransactionSnapshot(); } else { /* the default SPI behavior is okay */ test_snapshot = InvalidSnapshot; crosscheck_snapshot = InvalidSnapshot; } /* * If this is a select query (e.g., for a 'no action' or 'restrict' * trigger), we only need to see if there is a single row in the table, * matching the key. Otherwise, limit = 0 - because we want the query to * affect ALL the matching rows. */ limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0; /* Switch to proper UID to perform check as */ GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* Finally we can run the query. */ spi_result = SPI_execute_snapshot(qplan, vals, nulls, test_snapshot, crosscheck_snapshot, false, false, limit); /* Restore UID and security context */ SetUserIdAndSecContext(save_userid, save_sec_context); /* Check result */ if (spi_result < 0) elog(ERROR, "SPI_execute_snapshot returned %d", spi_result); if (expect_OK >= 0 && spi_result != expect_OK) ri_ReportViolation(riinfo, pk_rel, fk_rel, new_tuple ? new_tuple : old_tuple, NULL, qkey->constr_queryno, true); /* XXX wouldn't it be clearer to do this part at the caller? */ if (qkey->constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK && expect_OK == SPI_OK_SELECT && (SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK)) ri_ReportViolation(riinfo, pk_rel, fk_rel, new_tuple ? new_tuple : old_tuple, NULL, qkey->constr_queryno, false); return SPI_processed != 0; }
static SPIPlanPtr ri_PlanCheck | ( | const char * | querystr, | |
int | nargs, | |||
Oid * | argtypes, | |||
RI_QueryKey * | qkey, | |||
Relation | fk_rel, | |||
Relation | pk_rel, | |||
bool | cache_plan | |||
) | [static] |
Definition at line 2948 of file ri_triggers.c.
References RI_QueryKey::constr_queryno, elog, ERROR, GetUserIdAndSecContext(), NULL, RelationGetForm, ri_HashPreparedPlan(), RI_PLAN_LAST_ON_PK, SECURITY_LOCAL_USERID_CHANGE, SetUserIdAndSecContext(), SPI_keepplan(), SPI_prepare(), and SPI_result.
Referenced by ri_Check_Pk_Match(), RI_FKey_cascade_del(), RI_FKey_cascade_upd(), RI_FKey_check(), RI_FKey_setdefault_del(), RI_FKey_setdefault_upd(), RI_FKey_setnull_del(), RI_FKey_setnull_upd(), ri_restrict_del(), and ri_restrict_upd().
{ SPIPlanPtr qplan; Relation query_rel; Oid save_userid; int save_sec_context; /* * Use the query type code to determine whether the query is run against * the PK or FK table; we'll do the check as that table's owner */ if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK) query_rel = pk_rel; else query_rel = fk_rel; /* Switch to proper UID to perform check as */ GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, save_sec_context | SECURITY_LOCAL_USERID_CHANGE); /* Create the plan */ qplan = SPI_prepare(querystr, nargs, argtypes); if (qplan == NULL) elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr); /* Restore UID and security context */ SetUserIdAndSecContext(save_userid, save_sec_context); /* Save the plan if requested */ if (cache_plan) { SPI_keepplan(qplan); ri_HashPreparedPlan(qkey, qplan); } return qplan; }
static void ri_ReportViolation | ( | const RI_ConstraintInfo * | riinfo, | |
Relation | pk_rel, | |||
Relation | fk_rel, | |||
HeapTuple | violator, | |||
TupleDesc | tupdesc, | |||
int | queryno, | |||
bool | spi_err | |||
) | [static] |
Definition at line 3160 of file ri_triggers.c.
References appendStringInfoString(), RI_ConstraintInfo::conname, StringInfoData::data, ereport, errcode(), errdetail(), errhint(), errmsg(), ERROR, errtableconstraint(), RI_ConstraintInfo::fk_attnums, initStringInfo(), name, NameStr, RI_ConstraintInfo::nkeys, NULL, RI_ConstraintInfo::pk_attnums, RelationData::rd_att, RelationGetRelationName, SPI_fname(), SPI_getvalue(), and val.
Referenced by RI_Initial_Check(), and ri_PerformCheck().
{ StringInfoData key_names; StringInfoData key_values; bool onfk; const int16 *attnums; int idx; if (spi_err) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("referential integrity query on \"%s\" from constraint \"%s\" on \"%s\" gave unexpected result", RelationGetRelationName(pk_rel), NameStr(riinfo->conname), RelationGetRelationName(fk_rel)), errhint("This is most likely due to a rule having rewritten the query."))); /* * Determine which relation to complain about. If tupdesc wasn't passed * by caller, assume the violator tuple came from there. */ onfk = (queryno == RI_PLAN_CHECK_LOOKUPPK); if (onfk) { attnums = riinfo->fk_attnums; if (tupdesc == NULL) tupdesc = fk_rel->rd_att; } else { attnums = riinfo->pk_attnums; if (tupdesc == NULL) tupdesc = pk_rel->rd_att; } /* Get printable versions of the keys involved */ initStringInfo(&key_names); initStringInfo(&key_values); for (idx = 0; idx < riinfo->nkeys; idx++) { int fnum = attnums[idx]; char *name, *val; name = SPI_fname(tupdesc, fnum); val = SPI_getvalue(violator, tupdesc, fnum); if (!val) val = "null"; if (idx > 0) { appendStringInfoString(&key_names, ", "); appendStringInfoString(&key_values, ", "); } appendStringInfoString(&key_names, name); appendStringInfoString(&key_values, val); } if (onfk) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(fk_rel), NameStr(riinfo->conname)), errdetail("Key (%s)=(%s) is not present in table \"%s\".", key_names.data, key_values.data, RelationGetRelationName(pk_rel)), errtableconstraint(fk_rel, NameStr(riinfo->conname)))); else ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"", RelationGetRelationName(pk_rel), NameStr(riinfo->conname), RelationGetRelationName(fk_rel)), errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", key_names.data, key_values.data, RelationGetRelationName(fk_rel)), errtableconstraint(fk_rel, NameStr(riinfo->conname)))); }
static Datum ri_restrict_del | ( | TriggerData * | trigdata, | |
bool | is_no_action | |||
) | [static] |
Definition at line 642 of file ri_triggers.c.
References appendStringInfo(), RI_ConstraintInfo::confmatchtype, StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, RI_ConstraintInfo::fk_attnums, RI_ConstraintInfo::fk_relid, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), RI_ConstraintInfo::nkeys, NULL, RI_ConstraintInfo::pf_eq_oprs, RI_ConstraintInfo::pk_attnums, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_Check_Pk_Match(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_NullCheck(), ri_PerformCheck(), RI_PLAN_RESTRICT_DEL_CHECKREF, ri_PlanCheck(), RIAttName, RIAttType, RowShareLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_SELECT, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
Referenced by RI_FKey_noaction_del(), and RI_FKey_restrict_del().
{ const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; int i; /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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 KEY SHARE will get on it. */ fk_rel = heap_open(riinfo->fk_relid, RowShareLock); pk_rel = trigdata->tg_relation; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 9) a) iv): * MATCH SIMPLE/FULL * ... ON DELETE RESTRICT * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a NULL */ heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); case RI_KEYS_NONE_NULL: /* * Have a full qualified key - continue below */ break; } /* * If another PK row now exists providing the old key values, * we should not do anything. However, this check should only be * made in the NO ACTION case; in RESTRICT cases we don't wish to * allow another row to be substituted. */ if (is_no_action && ri_Check_Pk_Match(pk_rel, fk_rel, old_row, riinfo)) { heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); } if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the restrict delete lookup */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * SELECT 1 FROM ONLY <fktable> x WHERE $1 = fkatt1 [AND ...] * FOR KEY SHARE OF x * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", fkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&querybuf, querysep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = "AND"; queryoids[i] = pk_type; } appendStringInfo(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it to check for existing references. */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* must detect new rows */ SPI_OK_SELECT); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL restrict delete. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
static Datum ri_restrict_upd | ( | TriggerData * | trigdata, | |
bool | is_no_action | |||
) | [static] |
Definition at line 854 of file ri_triggers.c.
References appendStringInfo(), RI_ConstraintInfo::confmatchtype, StringInfoData::data, elog, ereport, errcode(), errmsg(), ERROR, RI_ConstraintInfo::fk_attnums, RI_ConstraintInfo::fk_relid, FKCONSTR_MATCH_FULL, FKCONSTR_MATCH_PARTIAL, FKCONSTR_MATCH_SIMPLE, heap_close, heap_open(), i, initStringInfo(), RI_ConstraintInfo::nkeys, NULL, RI_ConstraintInfo::pf_eq_oprs, RI_ConstraintInfo::pk_attnums, PointerGetDatum, quoteOneName(), quoteRelationName(), ri_BuildQueryKey(), ri_Check_Pk_Match(), ri_FetchConstraintInfo(), ri_FetchPreparedPlan(), ri_GenerateQual(), RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL, RI_KEYS_SOME_NULL, ri_KeysEqual(), ri_NullCheck(), ri_PerformCheck(), RI_PLAN_RESTRICT_UPD_CHECKREF, ri_PlanCheck(), RIAttName, RIAttType, RowShareLock, SPI_connect(), SPI_finish(), SPI_OK_CONNECT, SPI_OK_FINISH, SPI_OK_SELECT, TriggerData::tg_newtuple, TriggerData::tg_relation, TriggerData::tg_trigger, and TriggerData::tg_trigtuple.
Referenced by RI_FKey_noaction_upd(), and RI_FKey_restrict_upd().
{ const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; HeapTuple new_row; HeapTuple old_row; RI_QueryKey qkey; SPIPlanPtr qplan; int i; /* * Get arguments. */ riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, trigdata->tg_relation, true); /* * 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 KEY SHARE will get on it. */ fk_rel = heap_open(riinfo->fk_relid, RowShareLock); pk_rel = trigdata->tg_relation; new_row = trigdata->tg_newtuple; old_row = trigdata->tg_trigtuple; switch (riinfo->confmatchtype) { /* ---------- * SQL:2008 15.17 <Execution of referential actions> * General rules 10) a) iv): * MATCH SIMPLE/FULL * ... ON UPDATE RESTRICT * ---------- */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: switch (ri_NullCheck(old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: /* * No check needed - there cannot be any reference to old * key if it contains a 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, riinfo, true)) { heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); } /* * If another PK row now exists providing the old key values, * we should not do anything. However, this check should only be * made in the NO ACTION case; in RESTRICT cases we don't wish to * allow another row to be substituted. */ if (is_no_action && ri_Check_Pk_Match(pk_rel, fk_rel, old_row, riinfo)) { heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); } if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the restrict update lookup */ ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; char fkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; char paramname[16]; const char *querysep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * The type id's for the $ parameters are those of the * corresponding PK attributes. * ---------- */ initStringInfo(&querybuf); quoteRelationName(fkrelname, fk_rel); appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", fkrelname); querysep = "WHERE"; for (i = 0; i < riinfo->nkeys; i++) { Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[i])); sprintf(paramname, "$%d", i + 1); ri_GenerateQual(&querybuf, querysep, paramname, pk_type, riinfo->pf_eq_oprs[i], attname, fk_type); querysep = "AND"; queryoids[i] = pk_type; } appendStringInfo(&querybuf, " FOR KEY SHARE OF x"); /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, &qkey, fk_rel, pk_rel, true); } /* * We have a plan now. Run it to check for existing references. */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, old_row, NULL, true, /* must detect new rows */ SPI_OK_SELECT); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); /* * Handle MATCH PARTIAL restrict update. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); return PointerGetDatum(NULL); default: elog(ERROR, "unrecognized confmatchtype: %d", riinfo->confmatchtype); break; } /* Never reached */ return PointerGetDatum(NULL); }
HTAB* ri_compare_cache = NULL [static] |
Definition at line 185 of file ri_triggers.c.
HTAB* ri_constraint_cache = NULL [static] |
Definition at line 183 of file ri_triggers.c.
HTAB* ri_query_cache = NULL [static] |
Definition at line 184 of file ri_triggers.c.