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 + -
显示快捷键?