ri_triggers.c
来自「PostgreSQL7.4.6 for Linux」· C语言 代码 · 共 2,399 行 · 第 1/5 页
C
2,399 行
/* ---------- * ri_triggers.c * * Generic trigger procedures for referential integrity constraint * checks. * * Note about memory management: the private hashtables kept here live * across query and transaction boundaries, in fact they live as long as * the backend does. This works because the hashtable structures * themselves are allocated by dynahash.c in its permanent DynaHashCxt, * and the parse/plan node trees they point to are copied into * TopMemoryContext using SPI_saveplan(). This is pretty ugly, since there * is no way to free a no-longer-needed plan tree, but then again we don't * yet have any bookkeeping that would allow us to detect that a plan isn't * needed anymore. Improve it someday. * * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.63.2.1 2004/10/13 22:22:03 tgl Exp $ * * ---------- *//* ---------- * Internal TODO: * * Add MATCH PARTIAL logic. * ---------- */#include "postgres.h"#include "catalog/pg_operator.h"#include "commands/trigger.h"#include "executor/spi_priv.h"#include "optimizer/planmain.h"#include "parser/parse_oper.h"#include "rewrite/rewriteHandler.h"#include "utils/lsyscache.h"#include "utils/typcache.h"#include "utils/acl.h"#include "miscadmin.h"/* ---------- * Local definitions * ---------- */#define RI_INIT_QUERYHASHSIZE 128#define RI_MATCH_TYPE_UNSPECIFIED 0#define RI_MATCH_TYPE_FULL 1#define RI_MATCH_TYPE_PARTIAL 2#define RI_KEYS_ALL_NULL 0#define RI_KEYS_SOME_NULL 1#define RI_KEYS_NONE_NULL 2/* queryno values must be distinct for the convenience of ri_PerformCheck */#define RI_PLAN_CHECK_LOOKUPPK_NOCOLS 1#define RI_PLAN_CHECK_LOOKUPPK 2#define RI_PLAN_CASCADE_DEL_DODELETE 3#define RI_PLAN_CASCADE_UPD_DOUPDATE 4#define RI_PLAN_NOACTION_DEL_CHECKREF 5#define RI_PLAN_NOACTION_UPD_CHECKREF 6#define RI_PLAN_RESTRICT_DEL_CHECKREF 7#define RI_PLAN_RESTRICT_UPD_CHECKREF 8#define RI_PLAN_SETNULL_DEL_DOUPDATE 9#define RI_PLAN_SETNULL_UPD_DOUPDATE 10#define RI_PLAN_KEYEQUAL_UPD 11#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)#define RI_TRIGTYPE_INSERT 1#define RI_TRIGTYPE_UPDATE 2#define RI_TRIGTYPE_INUP 3#define RI_TRIGTYPE_DELETE 4/* ---------- * RI_QueryKey * * The key identifying a prepared SPI plan in our private hashtable * ---------- */typedef struct RI_QueryKey{ int32 constr_type; Oid constr_id; int32 constr_queryno; Oid fk_relid; Oid pk_relid; int32 nkeypairs; int16 keypair[RI_MAX_NUMKEYS][2];} RI_QueryKey;/* ---------- * RI_QueryHashEntry * ---------- */typedef struct RI_QueryHashEntry{ RI_QueryKey key; void *plan;} RI_QueryHashEntry;/* ---------- * Local data * ---------- */static HTAB *ri_query_cache = (HTAB *) NULL;/* ---------- * Local function prototypes * ---------- */static void quoteOneName(char *buffer, const char *name);static void quoteRelationName(char *buffer, Relation rel);static int ri_DetermineMatchType(char *str);static int ri_NullCheck(Relation rel, HeapTuple tup, RI_QueryKey *key, int pairidx);static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno, Relation fk_rel, Relation pk_rel, int argc, char **argv);static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id, int32 constr_queryno, Relation pk_rel, int argc, char **argv);static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, RI_QueryKey *key, int pairidx);static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup, RI_QueryKey *key, int pairidx);static bool ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup, RI_QueryKey *key, int pairidx);static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue);static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, HeapTuple old_row, Oid tgoid, int match_type, int tgnargs, char **tgargs);static void ri_InitHashTables(void);static void *ri_FetchPreparedPlan(RI_QueryKey *key);static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind);static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel, bool cache_plan);static bool ri_PerformCheck(RI_QueryKey *qkey, void *qplan, Relation fk_rel, Relation pk_rel, HeapTuple old_tuple, HeapTuple new_tuple, bool detectNewRows, int expect_OK, const char *constrname);static void ri_ExtractValues(RI_QueryKey *qkey, int key_idx, Relation rel, HeapTuple tuple, Datum *vals, char *nulls);static void ri_ReportViolation(RI_QueryKey *qkey, const char *constrname, Relation pk_rel, Relation fk_rel, HeapTuple violator, TupleDesc tupdesc, bool spi_err);/* ---------- * RI_FKey_check - * * Check foreign key existence (combined for INSERT and UPDATE). * ---------- */static DatumRI_FKey_check(PG_FUNCTION_ARGS){ TriggerData *trigdata = (TriggerData *) fcinfo->context; int tgnargs; char **tgargs; Relation fk_rel; Relation pk_rel; HeapTuple new_row; HeapTuple old_row; RI_QueryKey qkey; void *qplan; int i; int match_type; /* * Check that this is a valid trigger call on the right time and * event. */ ri_CheckTrigger(fcinfo, "RI_FKey_check", RI_TRIGTYPE_INUP); tgnargs = trigdata->tg_trigger->tgnargs; tgargs = trigdata->tg_trigger->tgargs; /* * Get the relation descriptors of the FK and PK tables and the new * tuple. * * pk_rel is opened in RowShareLock mode since that's what our eventual * SELECT FOR UPDATE will get on it. */ pk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock); fk_rel = trigdata->tg_relation; if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { old_row = trigdata->tg_trigtuple; new_row = trigdata->tg_newtuple; } else { old_row = NULL; new_row = trigdata->tg_trigtuple; } /* * We should not even consider checking the row if it is no longer * valid since it was either deleted (doesn't matter) or updated (in * which case it'll be checked with its final values). * * Note: we need not SetBufferCommitInfoNeedsSave() here since the * new tuple's commit state can't possibly change. */ if (new_row) { if (!HeapTupleSatisfiesItself(new_row->t_data)) { heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); } } /* ---------- * SQL3 11.9 <referential constraint definition> * Gereral rules 2) a): * If Rf and Rt are empty (no columns to compare given) * constraint is true if 0 < (SELECT COUNT(*) FROM T) * * Note: The special case that no columns are given cannot * occur up to now in Postgres, it's just there for * future enhancements. * ---------- */ if (tgnargs == 4) { ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, RI_PLAN_CHECK_LOOKUPPK_NOCOLS, fk_rel, pk_rel, tgnargs, tgargs); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { char querystr[MAX_QUOTED_REL_NAME_LEN + 100]; char pkrelname[MAX_QUOTED_REL_NAME_LEN]; /* --------- * The query string built is * SELECT 1 FROM ONLY <pktable> * ---------- */ quoteRelationName(pkrelname, pk_rel); snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x FOR UPDATE OF x", pkrelname); /* Prepare and save the plan */ qplan = ri_PlanCheck(querystr, 0, NULL, &qkey, fk_rel, pk_rel, true); } /* * Execute the plan */ ri_PerformCheck(&qkey, qplan, fk_rel, pk_rel, NULL, NULL, false, SPI_OK_SELECT, tgargs[RI_CONSTRAINT_NAME_ARGNO]); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); } match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]); if (match_type == RI_MATCH_TYPE_PARTIAL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, RI_PLAN_CHECK_LOOKUPPK, fk_rel, pk_rel, tgnargs, tgargs); switch (ri_NullCheck(fk_rel, new_row, &qkey, RI_KEYPAIR_FK_IDX)) { case RI_KEYS_ALL_NULL: /* * No check - if NULLs are allowed at all is already checked * by NOT NULL constraint. * * This is true for MATCH FULL, MATCH PARTIAL, and MATCH * <unspecified> */ heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); case RI_KEYS_SOME_NULL: /* * This is the only case that differs between the three kinds * of MATCH. */ switch (match_type) { case RI_MATCH_TYPE_FULL: /* * Not allowed - MATCH FULL says either all or none of * the attributes can be NULLs */ ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(trigdata->tg_relation), tgargs[RI_CONSTRAINT_NAME_ARGNO]), errdetail("MATCH FULL does not allow mixing of null and nonnull key values."))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); case RI_MATCH_TYPE_UNSPECIFIED: /* * MATCH <unspecified> - if ANY column is null, we * have a match. */ heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); case RI_MATCH_TYPE_PARTIAL: /* * MATCH PARTIAL - all non-null columns must match. * (not implemented, can be done by modifying the * query below to only include non-null columns, or by * writing a special version here) */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); } case RI_KEYS_NONE_NULL: /* * Have a full qualified key - continue below for all three * kinds of MATCH. */ break; } /* * No need to check anything if old and new references are the same on * UPDATE. */ if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { if (HeapTupleHeaderGetXmin(old_row->t_data) != GetCurrentTransactionId() && ri_KeysEqual(fk_rel, old_row, new_row, &qkey, RI_KEYPAIR_FK_IDX)) { heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); } } if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); /* * Fetch or prepare a saved plan for the real check */ if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { char querystr[MAX_QUOTED_REL_NAME_LEN + 100 + (MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS]; char pkrelname[MAX_QUOTED_REL_NAME_LEN]; char attname[MAX_QUOTED_NAME_LEN]; const char *querysep; Oid queryoids[RI_MAX_NUMKEYS]; /* ---------- * The query string built is * SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] * The type id's for the $ parameters are those of the * corresponding FK attributes. Thus, ri_PlanCheck could * eventually fail if the parser cannot identify some way * how to compare these two types by '='. * ---------- */ quoteRelationName(pkrelname, pk_rel); snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname); querysep = "WHERE"; for (i = 0; i < qkey.nkeypairs; i++) { quoteOneName(attname, tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]); snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d", querysep, attname, i + 1); querysep = "AND"; queryoids[i] = SPI_gettypeid(fk_rel->rd_att, qkey.keypair[i][RI_KEYPAIR_FK_IDX]); } strcat(querystr, " FOR UPDATE OF x"); /* Prepare and save the plan */ qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids, &qkey, fk_rel, pk_rel, true); } /* * Now check that foreign key exists in PK table */ ri_PerformCheck(&qkey, qplan, fk_rel, pk_rel, NULL, new_row, false, SPI_OK_SELECT, tgargs[RI_CONSTRAINT_NAME_ARGNO]); if (SPI_finish() != SPI_OK_FINISH) elog(ERROR, "SPI_finish failed"); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL);}/* ---------- * RI_FKey_check_ins - * * Check foreign key existence at insert event on FK table. * ---------- */DatumRI_FKey_check_ins(PG_FUNCTION_ARGS){ return RI_FKey_check(fcinfo);}/* ---------- * RI_FKey_check_upd - * * Check foreign key existence at update event on FK table. * ---------- */DatumRI_FKey_check_upd(PG_FUNCTION_ARGS){ return RI_FKey_check(fcinfo);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?