1999-09-30 16:54:24 +02:00
|
|
|
/* ----------
|
|
|
|
* ri_triggers.c
|
|
|
|
*
|
|
|
|
* Generic trigger procedures for referential integrity constraint
|
|
|
|
* checks.
|
|
|
|
*
|
2001-02-15 22:57:43 +01:00
|
|
|
* 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,
|
2011-09-16 06:42:53 +02:00
|
|
|
* and the SPI plans they point to are saved using SPI_keepplan().
|
2007-03-16 00:12:07 +01:00
|
|
|
* There is not currently any provision for throwing away a no-longer-needed
|
|
|
|
* plan --- consider improving this someday.
|
1999-09-30 16:54:24 +02:00
|
|
|
*
|
2001-02-15 22:57:43 +01:00
|
|
|
*
|
2012-01-02 00:01:58 +01:00
|
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
2001-02-15 22:57:43 +01:00
|
|
|
*
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/backend/utils/adt/ri_triggers.c
|
1999-09-30 16:54:24 +02:00
|
|
|
*
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Internal TODO:
|
|
|
|
*
|
1999-12-07 01:13:41 +01:00
|
|
|
* Add MATCH PARTIAL logic.
|
1999-12-06 19:02:47 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
|
1999-09-30 16:54:24 +02:00
|
|
|
#include "postgres.h"
|
|
|
|
|
2009-01-07 14:44:37 +01:00
|
|
|
#include "access/xact.h"
|
2010-07-22 02:47:59 +02:00
|
|
|
#include "access/sysattr.h"
|
2011-04-12 03:32:53 +02:00
|
|
|
#include "catalog/pg_collation.h"
|
2007-02-14 02:58:58 +01:00
|
|
|
#include "catalog/pg_constraint.h"
|
|
|
|
#include "catalog/pg_operator.h"
|
2009-01-07 14:44:37 +01:00
|
|
|
#include "catalog/pg_type.h"
|
1999-09-30 16:54:24 +02:00
|
|
|
#include "commands/trigger.h"
|
2010-07-22 02:47:59 +02:00
|
|
|
#include "executor/executor.h"
|
2007-03-16 00:12:07 +01:00
|
|
|
#include "executor/spi.h"
|
2007-02-14 02:58:58 +01:00
|
|
|
#include "parser/parse_coerce.h"
|
|
|
|
#include "parser/parse_relation.h"
|
|
|
|
#include "miscadmin.h"
|
2009-01-07 14:44:37 +01:00
|
|
|
#include "utils/builtins.h"
|
2005-05-30 09:20:59 +02:00
|
|
|
#include "utils/fmgroids.h"
|
2011-09-04 07:13:16 +02:00
|
|
|
#include "utils/guc.h"
|
2005-05-30 09:20:59 +02:00
|
|
|
#include "utils/lsyscache.h"
|
2007-02-14 02:58:58 +01:00
|
|
|
#include "utils/memutils.h"
|
2011-02-23 18:18:09 +01:00
|
|
|
#include "utils/rel.h"
|
2008-03-26 19:48:59 +01:00
|
|
|
#include "utils/snapmgr.h"
|
2009-01-07 14:44:37 +01:00
|
|
|
#include "utils/syscache.h"
|
2008-03-26 22:10:39 +01:00
|
|
|
#include "utils/tqual.h"
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Local definitions
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
#define RI_MAX_NUMKEYS INDEX_MAX_KEYS
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
#define RI_INIT_QUERYHASHSIZE 128
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
#define RI_KEYS_ALL_NULL 0
|
|
|
|
#define RI_KEYS_SOME_NULL 1
|
|
|
|
#define RI_KEYS_NONE_NULL 2
|
|
|
|
|
2012-06-19 00:50:03 +02:00
|
|
|
/* RI query type codes */
|
|
|
|
/* these queries are executed against the PK (referenced) table: */
|
|
|
|
#define RI_PLAN_CHECK_LOOKUPPK 1
|
|
|
|
#define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2
|
|
|
|
#define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK
|
|
|
|
/* these queries are executed against the FK (referencing) table: */
|
2003-03-15 22:19:40 +01:00
|
|
|
#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
|
2012-06-19 00:50:03 +02:00
|
|
|
#define RI_PLAN_SETDEFAULT_DEL_DOUPDATE 11
|
|
|
|
#define RI_PLAN_SETDEFAULT_UPD_DOUPDATE 12
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
#define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
|
|
|
|
#define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
|
|
|
|
|
2007-11-15 22:14:46 +01:00
|
|
|
#define RIAttName(rel, attnum) NameStr(*attnumAttName(rel, attnum))
|
2008-02-19 00:00:32 +01:00
|
|
|
#define RIAttType(rel, attnum) attnumTypeId(rel, attnum)
|
2011-06-09 20:32:50 +02:00
|
|
|
#define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum)
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2003-03-15 22:19:40 +01:00
|
|
|
#define RI_TRIGTYPE_INSERT 1
|
|
|
|
#define RI_TRIGTYPE_UPDATE 2
|
|
|
|
#define RI_TRIGTYPE_INUP 3
|
|
|
|
#define RI_TRIGTYPE_DELETE 4
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_ConstraintInfo
|
|
|
|
*
|
|
|
|
* Information extracted from an FK pg_constraint entry.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
typedef struct RI_ConstraintInfo
|
|
|
|
{
|
|
|
|
Oid constraint_id; /* OID of pg_constraint entry */
|
|
|
|
NameData conname; /* name of the FK constraint */
|
|
|
|
Oid pk_relid; /* referenced relation */
|
|
|
|
Oid fk_relid; /* referencing relation */
|
|
|
|
char confupdtype; /* foreign key's ON UPDATE action */
|
|
|
|
char confdeltype; /* foreign key's ON DELETE action */
|
|
|
|
char confmatchtype; /* foreign key's match type */
|
|
|
|
int nkeys; /* number of key columns */
|
2007-11-15 22:14:46 +01:00
|
|
|
int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
|
|
|
|
int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
|
|
|
|
Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK =
|
|
|
|
* FK) */
|
|
|
|
Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK =
|
|
|
|
* PK) */
|
|
|
|
Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK =
|
|
|
|
* FK) */
|
2007-11-15 23:25:18 +01:00
|
|
|
} RI_ConstraintInfo;
|
2007-02-14 02:58:58 +01:00
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_QueryKey
|
|
|
|
*
|
2007-02-14 02:58:58 +01:00
|
|
|
* The key identifying a prepared SPI plan in our query hashtable
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-04-12 19:17:23 +02:00
|
|
|
typedef struct RI_QueryKey
|
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
Oid constr_id; /* OID of pg_constraint entry */
|
|
|
|
int32 constr_queryno; /* query type ID, see RI_PLAN_XXX above */
|
1999-10-08 14:00:08 +02:00
|
|
|
} RI_QueryKey;
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_QueryHashEntry
|
|
|
|
* ----------
|
|
|
|
*/
|
2000-04-12 19:17:23 +02:00
|
|
|
typedef struct RI_QueryHashEntry
|
|
|
|
{
|
|
|
|
RI_QueryKey key;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr plan;
|
1999-10-08 14:00:08 +02:00
|
|
|
} RI_QueryHashEntry;
|
|
|
|
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/* ----------
|
|
|
|
* RI_CompareKey
|
|
|
|
*
|
|
|
|
* The key identifying an entry showing how to compare two values
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
typedef struct RI_CompareKey
|
|
|
|
{
|
|
|
|
Oid eq_opr; /* the equality operator to apply */
|
|
|
|
Oid typeid; /* the data type to apply it to */
|
2007-11-15 23:25:18 +01:00
|
|
|
} RI_CompareKey;
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_CompareHashEntry
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
typedef struct RI_CompareHashEntry
|
|
|
|
{
|
|
|
|
RI_CompareKey key;
|
2007-11-15 22:14:46 +01:00
|
|
|
bool valid; /* successfully initialized? */
|
|
|
|
FmgrInfo eq_opr_finfo; /* call info for equality fn */
|
2007-02-14 02:58:58 +01:00
|
|
|
FmgrInfo cast_func_finfo; /* in case we must coerce input */
|
2007-11-15 23:25:18 +01:00
|
|
|
} RI_CompareHashEntry;
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
/* ----------
|
|
|
|
* Local data
|
|
|
|
* ----------
|
|
|
|
*/
|
2004-01-07 19:56:30 +01:00
|
|
|
static HTAB *ri_query_cache = NULL;
|
2007-02-14 02:58:58 +01:00
|
|
|
static HTAB *ri_compare_cache = NULL;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Local function prototypes
|
|
|
|
* ----------
|
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
static void quoteOneName(char *buffer, const char *name);
|
|
|
|
static void quoteRelationName(char *buffer, Relation rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
static void ri_GenerateQual(StringInfo buf,
|
2007-11-15 22:14:46 +01:00
|
|
|
const char *sep,
|
|
|
|
const char *leftop, Oid leftoptype,
|
|
|
|
Oid opoid,
|
|
|
|
const char *rightop, Oid rightoptype);
|
2008-02-07 23:58:35 +01:00
|
|
|
static void ri_add_cast_to(StringInfo buf, Oid typid);
|
2011-04-12 03:32:53 +02:00
|
|
|
static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
|
2012-06-19 00:50:03 +02:00
|
|
|
static int ri_NullCheck(HeapTuple tup,
|
|
|
|
const RI_ConstraintInfo *riinfo, bool rel_is_pk);
|
|
|
|
static void ri_BuildQueryKey(RI_QueryKey *key,
|
|
|
|
const RI_ConstraintInfo *riinfo,
|
|
|
|
int32 constr_queryno);
|
2000-04-12 19:17:23 +02:00
|
|
|
static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
|
2007-11-15 23:25:18 +01:00
|
|
|
const RI_ConstraintInfo *riinfo, bool rel_is_pk);
|
2007-02-14 02:58:58 +01:00
|
|
|
static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
|
2007-11-15 22:14:46 +01:00
|
|
|
Datum oldvalue, Datum newvalue);
|
2003-03-15 22:19:40 +01:00
|
|
|
static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
|
2003-08-04 02:43:34 +02:00
|
|
|
HeapTuple old_row,
|
2007-11-15 23:25:18 +01:00
|
|
|
const RI_ConstraintInfo *riinfo);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
static void ri_InitHashTables(void);
|
2007-03-16 00:12:07 +01:00
|
|
|
static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
|
|
|
|
static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan);
|
2007-02-14 02:58:58 +01:00
|
|
|
static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2003-03-15 22:19:40 +01:00
|
|
|
static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
|
2003-08-04 02:43:34 +02:00
|
|
|
int tgkind);
|
2007-11-15 23:25:18 +01:00
|
|
|
static void ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
|
2007-02-14 02:58:58 +01:00
|
|
|
Trigger *trigger, Relation trig_rel, bool rel_is_pk);
|
2007-03-16 00:12:07 +01:00
|
|
|
static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
|
2003-08-04 02:43:34 +02:00
|
|
|
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
|
|
|
|
bool cache_plan);
|
2012-06-19 00:50:03 +02:00
|
|
|
static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo,
|
|
|
|
RI_QueryKey *qkey, SPIPlanPtr qplan,
|
2003-08-04 02:43:34 +02:00
|
|
|
Relation fk_rel, Relation pk_rel,
|
|
|
|
HeapTuple old_tuple, HeapTuple new_tuple,
|
2012-06-19 00:50:03 +02:00
|
|
|
bool detectNewRows, int expect_OK);
|
|
|
|
static void ri_ExtractValues(Relation rel, HeapTuple tup,
|
|
|
|
const RI_ConstraintInfo *riinfo, bool rel_is_pk,
|
2003-08-04 02:43:34 +02:00
|
|
|
Datum *vals, char *nulls);
|
2012-06-19 00:50:03 +02:00
|
|
|
static void ri_ReportViolation(const RI_ConstraintInfo *riinfo,
|
2003-08-04 02:43:34 +02:00
|
|
|
Relation pk_rel, Relation fk_rel,
|
2003-10-06 18:38:28 +02:00
|
|
|
HeapTuple violator, TupleDesc tupdesc,
|
2012-06-19 00:50:03 +02:00
|
|
|
int queryno, bool spi_err);
|
2003-03-15 22:19:40 +01:00
|
|
|
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
/* ----------
|
|
|
|
* RI_FKey_check -
|
|
|
|
*
|
2000-05-29 03:59:17 +02:00
|
|
|
* Check foreign key existence (combined for INSERT and UPDATE).
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
static Datum
|
|
|
|
RI_FKey_check(PG_FUNCTION_ARGS)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple new_row;
|
2004-10-30 22:53:06 +02:00
|
|
|
Buffer new_row_buf;
|
2000-04-12 19:17:23 +02:00
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_check", RI_TRIGTYPE_INUP);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2006-08-21 21:15:29 +02:00
|
|
|
* Get arguments.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
2007-11-15 22:14:46 +01:00
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, false);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
|
|
|
{
|
|
|
|
new_row = trigdata->tg_newtuple;
|
2004-10-30 22:53:06 +02:00
|
|
|
new_row_buf = trigdata->tg_newtuplebuf;
|
2000-04-12 19:17:23 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
1999-10-08 14:00:08 +02:00
|
|
|
new_row = trigdata->tg_trigtuple;
|
2004-10-30 22:53:06 +02:00
|
|
|
new_row_buf = trigdata->tg_trigtuplebuf;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
/*
|
2006-08-27 23:41:21 +02:00
|
|
|
* 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)
|
2006-10-04 02:30:14 +02:00
|
|
|
* or updated (in which case only the latest version of the row should be
|
2007-03-25 21:45:14 +02:00
|
|
|
* checked). Test its liveness according to SnapshotSelf.
|
2006-08-27 23:41:21 +02:00
|
|
|
*
|
|
|
|
* NOTE: The normal coding rule is that one must acquire the buffer
|
2007-03-25 21:45:14 +02:00
|
|
|
* content lock to call HeapTupleSatisfiesVisibility. We can skip that
|
|
|
|
* here because we know that AfterTriggerExecute just fetched the tuple
|
2006-10-04 02:30:14 +02:00
|
|
|
* successfully, so there cannot be a VACUUM compaction in progress on the
|
|
|
|
* page (either heap_fetch would have waited for the VACUUM, or the
|
2007-11-15 22:14:46 +01:00
|
|
|
* 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.
|
2002-04-02 00:36:13 +02:00
|
|
|
*/
|
2004-10-30 22:53:06 +02:00
|
|
|
Assert(new_row_buf != InvalidBuffer);
|
2007-03-25 21:45:14 +02:00
|
|
|
if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
|
2004-10-30 22:53:06 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2006-08-21 21:15:29 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 SHARE will get on it.
|
|
|
|
*/
|
|
|
|
fk_rel = trigdata->tg_relation;
|
2007-02-14 02:58:58 +01:00
|
|
|
pk_rel = heap_open(riinfo.pk_relid, RowShareLock);
|
2002-03-19 03:57:15 +01:00
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 4.17.3 <Table constraints>
|
1999-10-08 14:00:08 +02:00
|
|
|
* If Rf and Rt are empty (no columns to compare given)
|
|
|
|
* constraint is true if 0 < (SELECT COUNT(*) FROM T)
|
1999-12-06 20:50:49 +01:00
|
|
|
*
|
|
|
|
* Note: The special case that no columns are given cannot
|
2012-06-18 18:19:38 +02:00
|
|
|
* occur at present in Postgres (and is disallowed by the
|
|
|
|
* standard too); it's just there for future enhancements.
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-04-12 19:17:23 +02:00
|
|
|
{
|
2003-04-27 00:21:47 +02:00
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
2003-04-27 00:21:47 +02:00
|
|
|
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK);
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2002-04-02 00:36:13 +02:00
|
|
|
char querystr[MAX_QUOTED_REL_NAME_LEN + 100];
|
|
|
|
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/* ---------
|
1999-10-08 14:00:08 +02:00
|
|
|
* The query string built is
|
2001-11-12 07:09:09 +01:00
|
|
|
* SELECT 1 FROM ONLY <pktable>
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(pkrelname, pk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
snprintf(querystr, sizeof(querystr),
|
|
|
|
"SELECT 1 FROM ONLY %s x FOR SHARE OF x",
|
2002-09-04 22:31:48 +02:00
|
|
|
pkrelname);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
|
|
|
qplan = ri_PlanCheck(querystr, 0, NULL,
|
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* Execute the plan
|
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
NULL, NULL,
|
2003-10-01 23:30:53 +02:00
|
|
|
false,
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_SELECT);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(pk_rel, RowShareLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.confmatchtype == FKCONSTR_MATCH_PARTIAL)
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(new_row, &riinfo, false))
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No further check needed - an all-NULL key passes every type of
|
|
|
|
* foreign key constraint.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(pk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2000-02-07 18:50:38 +01:00
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* This is the only case that differs between the three kinds of
|
|
|
|
* MATCH.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Not allowed - MATCH FULL says either all or none of the
|
|
|
|
* attributes can be NULLs
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
|
2005-10-15 04:49:52 +02:00
|
|
|
RelationGetRelationName(trigdata->tg_relation),
|
2007-02-14 02:58:58 +01:00
|
|
|
NameStr(riinfo.conname)),
|
2003-09-25 08:58:07 +02:00
|
|
|
errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(pk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* MATCH SIMPLE - if ANY column is null, the key passes
|
|
|
|
* the constraint.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(pk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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)
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(pk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Have a full qualified key - continue below for all three kinds
|
|
|
|
* of MATCH.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
2002-07-30 18:33:21 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-02-07 18:50:38 +01:00
|
|
|
* Fetch or prepare a saved plan for the real check
|
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK);
|
|
|
|
|
2000-02-07 18:50:38 +01:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
2000-02-07 18:50:38 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2007-02-14 02:58:58 +01:00
|
|
|
* SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
|
2000-02-07 18:50:38 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding FK attributes.
|
2000-02-07 18:50:38 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(pkrelname, pk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
|
2000-02-07 18:50:38 +01:00
|
|
|
querysep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
2000-02-07 18:50:38 +01:00
|
|
|
querysep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = fk_type;
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, " FOR SHARE OF x");
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-02-07 18:50:38 +01:00
|
|
|
* Now check that foreign key exists in PK table
|
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
NULL, new_row,
|
2003-10-01 23:30:53 +02:00
|
|
|
false,
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_SELECT);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(pk_rel, RowShareLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_check_ins -
|
|
|
|
*
|
2000-05-29 03:59:17 +02:00
|
|
|
* Check foreign key existence at insert event on FK table.
|
2000-02-07 18:50:38 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_check_ins(PG_FUNCTION_ARGS)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
return RI_FKey_check(fcinfo);
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_check_upd -
|
|
|
|
*
|
2000-05-29 03:59:17 +02:00
|
|
|
* Check foreign key existence at update event on FK table.
|
2000-02-07 18:50:38 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_check_upd(PG_FUNCTION_ARGS)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
return RI_FKey_check(fcinfo);
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-07-30 18:33:21 +02:00
|
|
|
/* ----------
|
|
|
|
* ri_Check_Pk_Match
|
|
|
|
*
|
2007-02-14 02:58:58 +01:00
|
|
|
* Check for matching value of old pk row in current state for
|
2002-07-30 18:33:21 +02:00
|
|
|
* noaction triggers. Returns false if no row was found and a fk row
|
|
|
|
* could potentially be referencing this row, true otherwise.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static bool
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
|
|
|
|
HeapTuple old_row,
|
2007-11-15 23:25:18 +01:00
|
|
|
const RI_ConstraintInfo *riinfo)
|
2002-09-04 22:31:48 +02:00
|
|
|
{
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2002-07-30 18:33:21 +02:00
|
|
|
RI_QueryKey qkey;
|
2002-09-04 22:31:48 +02:00
|
|
|
int i;
|
|
|
|
bool result;
|
|
|
|
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, riinfo, true))
|
2002-07-30 18:33:21 +02:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
2002-09-04 22:31:48 +02:00
|
|
|
|
2002-07-30 18:33:21 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* No check - nothing could have been referencing this row anyway.
|
2002-07-30 18:33:21 +02:00
|
|
|
*/
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case RI_KEYS_SOME_NULL:
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* This is the only case that differs between the three kinds of
|
|
|
|
* MATCH.
|
2002-07-30 18:33:21 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo->confmatchtype)
|
2002-07-30 18:33:21 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2002-09-04 22:31:48 +02:00
|
|
|
|
2002-07-30 18:33:21 +02:00
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* MATCH SIMPLE/FULL - if ANY column is null, nothing
|
|
|
|
* could have been referencing this row.
|
2002-07-30 18:33:21 +02:00
|
|
|
*/
|
|
|
|
return true;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2002-07-30 18:33:21 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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)
|
2002-07-30 18:33:21 +02:00
|
|
|
*/
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2002-07-30 18:33:21 +02:00
|
|
|
break;
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo->confmatchtype);
|
|
|
|
break;
|
2002-07-30 18:33:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
case RI_KEYS_NONE_NULL:
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Have a full qualified key - continue below for all three kinds
|
|
|
|
* of MATCH.
|
2002-07-30 18:33:21 +02:00
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
2002-07-30 18:33:21 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Fetch or prepare a saved plan for the real check
|
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK_FROM_PK);
|
|
|
|
|
2002-07-30 18:33:21 +02:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
2002-07-30 18:33:21 +02:00
|
|
|
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-07-30 18:33:21 +02:00
|
|
|
const char *querysep;
|
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2007-02-14 02:58:58 +01:00
|
|
|
* SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
|
2002-07-30 18:33:21 +02:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* PK attributes themselves.
|
2002-07-30 18:33:21 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
2002-07-30 18:33:21 +02:00
|
|
|
quoteRelationName(pkrelname, pk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
|
2002-07-30 18:33:21 +02:00
|
|
|
querysep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo->nkeys; i++)
|
2002-07-30 18:33:21 +02:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-07-30 18:33:21 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
2002-07-30 18:33:21 +02:00
|
|
|
querysep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
2002-07-30 18:33:21 +02:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, " FOR SHARE OF x");
|
2002-07-30 18:33:21 +02:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
2002-07-30 18:33:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2003-03-15 22:19:40 +01:00
|
|
|
* We have a plan now. Run it.
|
2002-07-30 18:33:21 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
result = ri_PerformCheck(riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2003-10-01 23:30:53 +02:00
|
|
|
true, /* treat like update */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_SELECT);
|
2002-07-30 18:33:21 +02:00
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
2002-07-30 18:33:21 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2000-02-07 18:50:38 +01:00
|
|
|
/* ----------
|
|
|
|
* RI_FKey_noaction_del -
|
|
|
|
*
|
|
|
|
* Give an error and roll back the current transaction if the
|
2000-04-12 19:17:23 +02:00
|
|
|
* delete has resulted in a violation of the given referential
|
|
|
|
* integrity constraint.
|
2000-02-07 18:50:38 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_noaction_del(PG_FUNCTION_ARGS)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-02-07 18:50:38 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2002-09-04 22:31:48 +02:00
|
|
|
* fk_rel is opened in RowShareLock mode since that's what our eventual
|
2005-04-28 23:47:18 +02:00
|
|
|
* SELECT FOR SHARE will get on it.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
2000-02-07 18:50:38 +01:00
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
|
2002-09-04 22:31:48 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* There's either another row, or no row could match this one. In
|
|
|
|
* either case, we don't need to do the check.
|
2002-07-30 18:33:21 +02:00
|
|
|
*/
|
|
|
|
heap_close(fk_rel, RowShareLock);
|
|
|
|
return PointerGetDatum(NULL);
|
|
|
|
}
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 9) a) iv):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2012-06-18 18:19:38 +02:00
|
|
|
* ... ON DELETE NO ACTION
|
2000-04-12 19:17:23 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
2000-02-07 18:50:38 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2000-02-07 18:50:38 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* Fetch or prepare a saved plan for the restrict delete lookup
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_NOACTION_DEL_CHECKREF);
|
|
|
|
|
2000-02-07 18:50:38 +01:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
2000-02-07 18:50:38 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2007-02-14 02:58:58 +01:00
|
|
|
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
|
2000-02-07 18:50:38 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
2000-02-07 18:50:38 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
|
|
|
|
fkrelname);
|
2000-02-07 18:50:38 +01:00
|
|
|
querysep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
2000-02-07 18:50:38 +01:00
|
|
|
querysep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, " FOR SHARE OF x");
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to check for existing references.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_SELECT);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL restrict delete.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-02-07 18:50:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_noaction_upd -
|
|
|
|
*
|
|
|
|
* Give an error and roll back the current transaction if the
|
2000-04-12 19:17:23 +02:00
|
|
|
* update has resulted in a violation of the given referential
|
|
|
|
* integrity constraint.
|
2000-02-07 18:50:38 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple new_row;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-02-07 18:50:38 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the new and
|
|
|
|
* old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2002-09-04 22:31:48 +02:00
|
|
|
* fk_rel is opened in RowShareLock mode since that's what our eventual
|
2005-04-28 23:47:18 +02:00
|
|
|
* SELECT FOR SHARE will get on it.
|
2000-02-07 18:50:38 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
2000-02-07 18:50:38 +01:00
|
|
|
new_row = trigdata->tg_newtuple;
|
|
|
|
old_row = trigdata->tg_trigtuple;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
2000-02-07 18:50:38 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 10) a) iv):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2012-06-18 18:19:38 +02:00
|
|
|
* ... ON UPDATE NO ACTION
|
2000-04-12 19:17:23 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
2000-02-07 18:50:38 +01:00
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-02-07 18:50:38 +01:00
|
|
|
* No need to check anything if old and new keys are equal
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
|
2002-04-02 00:36:13 +02:00
|
|
|
{
|
|
|
|
heap_close(fk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2002-04-02 00:36:13 +02:00
|
|
|
}
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
|
2003-06-11 17:02:25 +02:00
|
|
|
{
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* There's either another row, or no row could match this one.
|
|
|
|
* In either case, we don't need to do the check.
|
2003-06-11 17:02:25 +02:00
|
|
|
*/
|
|
|
|
heap_close(fk_rel, RowShareLock);
|
|
|
|
return PointerGetDatum(NULL);
|
|
|
|
}
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* Fetch or prepare a saved plan for the noaction update lookup
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_NOACTION_UPD_CHECKREF);
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
1999-10-08 14:00:08 +02:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2007-02-14 02:58:58 +01:00
|
|
|
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
|
1999-10-08 14:00:08 +02:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
|
|
|
|
fkrelname);
|
1999-10-08 14:00:08 +02:00
|
|
|
querysep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
1999-10-08 14:00:08 +02:00
|
|
|
querysep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, " FOR SHARE OF x");
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to check for existing references.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_SELECT);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL noaction update.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-01-06 21:47:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
1999-09-30 16:54:24 +02:00
|
|
|
/* ----------
|
|
|
|
* RI_FKey_cascade_del -
|
|
|
|
*
|
|
|
|
* Cascaded delete foreign key references at delete event on PK table.
|
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_cascade_del(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* fk_rel is opened in RowExclusiveLock mode since that's what our
|
|
|
|
* eventual DELETE will get on it.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-10-08 14:00:08 +02:00
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 9) a) i):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2000-04-12 19:17:23 +02:00
|
|
|
* ... ON DELETE CASCADE
|
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* Fetch or prepare a saved plan for the cascaded delete
|
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
1999-10-08 14:00:08 +02:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2007-02-14 02:58:58 +01:00
|
|
|
* DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
|
1999-10-08 14:00:08 +02:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
|
1999-10-08 14:00:08 +02:00
|
|
|
querysep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
1999-10-08 14:00:08 +02:00
|
|
|
querysep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Build up the arguments from the key values
|
|
|
|
* in the deleted PK tuple and delete the referencing rows
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_DELETE);
|
2001-05-07 21:57:24 +02:00
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL cascaded delete.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_cascade_upd -
|
|
|
|
*
|
2012-06-18 18:19:38 +02:00
|
|
|
* Cascaded update foreign key references at update event on PK table.
|
1999-09-30 16:54:24 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple new_row;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
|
|
|
int j;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the new and
|
|
|
|
* old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* fk_rel is opened in RowExclusiveLock mode since that's what our
|
|
|
|
* eventual UPDATE will get on it.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-12-06 19:02:47 +01:00
|
|
|
new_row = trigdata->tg_newtuple;
|
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 10) a) i):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2000-04-12 19:17:23 +02:00
|
|
|
* ... ON UPDATE CASCADE
|
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* No need to do anything if old and new keys are equal
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
|
2002-04-02 00:36:13 +02:00
|
|
|
{
|
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2002-04-02 00:36:13 +02:00
|
|
|
}
|
1999-12-06 19:02:47 +01:00
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* Fetch or prepare a saved plan for the cascaded update
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
|
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
|
|
|
StringInfoData qualbuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
|
|
|
const char *qualsep;
|
1999-12-06 19:02:47 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS * 2];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2000-12-22 19:35:09 +01:00
|
|
|
* UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
|
2007-02-14 02:58:58 +01:00
|
|
|
* WHERE $n = fkatt1 [AND ...]
|
1999-12-06 19:02:47 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* 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.
|
1999-12-06 19:02:47 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
|
|
|
initStringInfo(&qualbuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
|
1999-12-06 19:02:47 +01:00
|
|
|
querysep = "";
|
|
|
|
qualsep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
1999-12-06 19:02:47 +01:00
|
|
|
querysep = ",";
|
|
|
|
qualsep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
|
|
|
queryoids[j] = pk_type;
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfoString(&querybuf, qualbuf.data);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to update the existing references.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, new_row,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_UPDATE);
|
2001-05-07 21:57:24 +02:00
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL cascade update.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_restrict_del -
|
|
|
|
*
|
|
|
|
* Restrict delete from PK table to rows unreferenced by foreign key.
|
2000-02-07 18:50:38 +01:00
|
|
|
*
|
2012-06-18 18:19:38 +02:00
|
|
|
* The SQL standard intends that this referential action occur BEFORE
|
|
|
|
* the delete is performed, rather than after. This appears to be
|
|
|
|
* the only difference between "NO ACTION" and "RESTRICT". In Postgres
|
|
|
|
* we still implement this as an AFTER trigger, but it's non-deferrable.
|
1999-09-30 16:54:24 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_restrict_del(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2002-09-04 22:31:48 +02:00
|
|
|
* fk_rel is opened in RowShareLock mode since that's what our eventual
|
2005-04-28 23:47:18 +02:00
|
|
|
* SELECT FOR SHARE will get on it.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-12-06 19:02:47 +01:00
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 9) a) iv):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2012-06-18 18:19:38 +02:00
|
|
|
* ... ON DELETE RESTRICT
|
2000-04-12 19:17:23 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* Fetch or prepare a saved plan for the restrict delete lookup
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF);
|
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
1999-12-06 19:02:47 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2007-02-14 02:58:58 +01:00
|
|
|
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
|
1999-12-06 19:02:47 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-12-06 19:02:47 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
|
|
|
|
fkrelname);
|
1999-12-06 19:02:47 +01:00
|
|
|
querysep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
1999-12-06 19:02:47 +01:00
|
|
|
querysep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, " FOR SHARE OF x");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to check for existing references.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_SELECT);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL restrict delete.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_restrict_upd -
|
|
|
|
*
|
|
|
|
* Restrict update of PK to rows unreferenced by foreign key.
|
2000-02-07 18:50:38 +01:00
|
|
|
*
|
2012-06-18 18:19:38 +02:00
|
|
|
* The SQL standard intends that this referential action occur BEFORE
|
|
|
|
* the update is performed, rather than after. This appears to be
|
|
|
|
* the only difference between "NO ACTION" and "RESTRICT". In Postgres
|
|
|
|
* we still implement this as an AFTER trigger, but it's non-deferrable.
|
1999-09-30 16:54:24 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple new_row;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the new and
|
|
|
|
* old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2002-09-04 22:31:48 +02:00
|
|
|
* fk_rel is opened in RowShareLock mode since that's what our eventual
|
2005-04-28 23:47:18 +02:00
|
|
|
* SELECT FOR SHARE will get on it.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-12-06 19:02:47 +01:00
|
|
|
new_row = trigdata->tg_newtuple;
|
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 10) a) iv):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2012-06-18 18:19:38 +02:00
|
|
|
* ... ON UPDATE RESTRICT
|
2000-04-12 19:17:23 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 19:02:47 +01:00
|
|
|
* No need to check anything if old and new keys are equal
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
|
2002-04-02 00:36:13 +02:00
|
|
|
{
|
|
|
|
heap_close(fk_rel, RowShareLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2002-04-02 00:36:13 +02:00
|
|
|
}
|
1999-12-06 19:02:47 +01:00
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* Fetch or prepare a saved plan for the restrict update lookup
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);
|
|
|
|
|
1999-12-06 19:02:47 +01:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
1999-12-06 19:02:47 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2007-02-14 02:58:58 +01:00
|
|
|
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
|
1999-12-06 19:02:47 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-12-06 19:02:47 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
|
|
|
|
fkrelname);
|
1999-12-06 19:02:47 +01:00
|
|
|
querysep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-12-06 19:02:47 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
1999-12-06 19:02:47 +01:00
|
|
|
querysep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, " FOR SHARE OF x");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to check for existing references.
|
1999-12-06 19:02:47 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_SELECT);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowShareLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 19:02:47 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL restrict update.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-12-06 19:02:47 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_setnull_del -
|
|
|
|
*
|
|
|
|
* Set foreign key references to NULL values at delete event on PK table.
|
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_setnull_del(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 20:50:49 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* fk_rel is opened in RowExclusiveLock mode since that's what our
|
|
|
|
* eventual UPDATE will get on it.
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-12-06 20:50:49 +01:00
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-12-06 20:50:49 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 9) a) ii):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2000-04-12 19:17:23 +02:00
|
|
|
* ... ON DELETE SET NULL
|
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-12-06 20:50:49 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-12-06 20:50:49 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-12-06 20:50:49 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Fetch or prepare a saved plan for the set null delete operation
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETNULL_DEL_DOUPDATE);
|
|
|
|
|
1999-12-06 20:50:49 +01:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
|
|
|
StringInfoData qualbuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
|
|
|
const char *qualsep;
|
1999-12-06 20:50:49 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2000-12-22 19:35:09 +01:00
|
|
|
* UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
|
2007-02-14 02:58:58 +01:00
|
|
|
* WHERE $1 = fkatt1 [AND ...]
|
1999-12-06 20:50:49 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-12-06 20:50:49 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
|
|
|
initStringInfo(&qualbuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
|
1999-12-06 20:50:49 +01:00
|
|
|
querysep = "";
|
|
|
|
qualsep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-12-06 20:50:49 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
1999-12-06 20:50:49 +01:00
|
|
|
querysep = ",";
|
|
|
|
qualsep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-12-06 20:50:49 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfoString(&querybuf, qualbuf.data);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2003-04-27 00:21:47 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-12-06 20:50:49 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to check for existing references.
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_UPDATE);
|
2001-05-07 21:57:24 +02:00
|
|
|
|
1999-12-06 20:50:49 +01:00
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL set null delete.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-12-06 20:50:49 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_setnull_upd -
|
|
|
|
*
|
|
|
|
* Set foreign key references to NULL at update event on PK table.
|
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple new_row;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 20:50:49 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* fk_rel is opened in RowExclusiveLock mode since that's what our
|
|
|
|
* eventual UPDATE will get on it.
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-12-06 20:50:49 +01:00
|
|
|
new_row = trigdata->tg_newtuple;
|
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-12-06 20:50:49 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 10) a) ii):
|
2012-06-18 18:12:52 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2000-04-12 19:17:23 +02:00
|
|
|
* ... ON UPDATE SET NULL
|
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-12-06 20:50:49 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-12-06 20:50:49 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-12-06 20:50:49 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
2000-02-07 18:50:38 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-06 20:50:49 +01:00
|
|
|
* No need to do anything if old and new keys are equal
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
|
2002-04-02 00:36:13 +02:00
|
|
|
{
|
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2002-04-02 00:36:13 +02:00
|
|
|
}
|
1999-12-06 20:50:49 +01:00
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Fetch or prepare a saved plan for the set null update operation
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETNULL_UPD_DOUPDATE);
|
|
|
|
|
2012-06-18 18:12:52 +02:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
1999-12-06 20:50:49 +01:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
|
|
|
StringInfoData qualbuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
|
|
|
const char *qualsep;
|
1999-12-06 20:50:49 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2000-12-22 19:35:09 +01:00
|
|
|
* UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
|
2007-02-14 02:58:58 +01:00
|
|
|
* WHERE $1 = fkatt1 [AND ...]
|
1999-12-06 20:50:49 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-12-06 20:50:49 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
|
|
|
initStringInfo(&qualbuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
|
1999-12-06 20:50:49 +01:00
|
|
|
querysep = "";
|
|
|
|
qualsep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-12-06 20:50:49 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2002-09-04 22:31:48 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
quoteOneName(attname,
|
|
|
|
RIAttName(fk_rel, riinfo.fk_attnums[i]));
|
2012-06-18 18:12:52 +02:00
|
|
|
appendStringInfo(&querybuf,
|
|
|
|
"%s %s = NULL",
|
|
|
|
querysep, attname);
|
2007-02-14 02:58:58 +01:00
|
|
|
sprintf(paramname, "$%d", i + 1);
|
|
|
|
ri_GenerateQual(&qualbuf, qualsep,
|
|
|
|
paramname, pk_type,
|
|
|
|
riinfo.pf_eq_oprs[i],
|
|
|
|
attname, fk_type);
|
2012-06-18 18:12:52 +02:00
|
|
|
querysep = ",";
|
1999-12-06 20:50:49 +01:00
|
|
|
qualsep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-12-06 20:50:49 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfoString(&querybuf, qualbuf.data);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2012-06-18 18:12:52 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2012-06-18 18:12:52 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-12-06 20:50:49 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to update the existing references.
|
1999-12-06 20:50:49 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_UPDATE);
|
2001-05-07 21:57:24 +02:00
|
|
|
|
1999-12-06 20:50:49 +01:00
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-06 20:50:49 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-04-12 19:17:23 +02:00
|
|
|
* Handle MATCH PARTIAL set null update.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-12-06 20:50:49 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_setdefault_del -
|
|
|
|
*
|
|
|
|
* Set foreign key references to defaults at delete event on PK table.
|
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-07 01:11:35 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* fk_rel is opened in RowExclusiveLock mode since that's what our
|
|
|
|
* eventual UPDATE will get on it.
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-12-07 01:11:35 +01:00
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 9) a) iii):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2000-04-12 19:17:23 +02:00
|
|
|
* ... ON DELETE SET DEFAULT
|
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-12-07 01:11:35 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-12-07 01:11:35 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 01:37:23 +02:00
|
|
|
* Fetch or prepare a saved plan for the set default delete
|
|
|
|
* operation
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETDEFAULT_DEL_DOUPDATE);
|
|
|
|
|
2012-06-19 01:37:23 +02:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
|
|
|
StringInfoData qualbuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
|
|
|
const char *qualsep;
|
1999-12-07 01:11:35 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
2003-03-27 20:25:40 +01:00
|
|
|
int i;
|
1999-12-07 01:11:35 +01:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2003-09-28 04:11:23 +02:00
|
|
|
* UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...]
|
2007-02-14 02:58:58 +01:00
|
|
|
* WHERE $1 = fkatt1 [AND ...]
|
1999-12-07 01:11:35 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-12-07 01:11:35 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
|
|
|
initStringInfo(&qualbuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
|
1999-12-07 01:11:35 +01:00
|
|
|
querysep = "";
|
|
|
|
qualsep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
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);
|
1999-12-07 01:11:35 +01:00
|
|
|
querysep = ",";
|
|
|
|
qualsep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-12-07 01:11:35 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfoString(&querybuf, qualbuf.data);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2012-06-19 01:37:23 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2012-06-19 01:37:23 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-12-07 01:11:35 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to update the existing references.
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_UPDATE);
|
2001-05-07 21:57:24 +02:00
|
|
|
|
1999-12-07 01:11:35 +01:00
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
|
|
|
|
2003-04-07 22:30:38 +02:00
|
|
|
/*
|
2012-06-19 04:45:07 +02:00
|
|
|
* 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_keyequal_upd_fk would therefore
|
|
|
|
* 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.)
|
2003-04-07 22:30:38 +02:00
|
|
|
*/
|
|
|
|
RI_FKey_noaction_del(fcinfo);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 01:37:23 +02:00
|
|
|
* Handle MATCH PARTIAL set default delete.
|
2000-04-12 19:17:23 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-12-07 01:11:35 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_setdefault_upd -
|
|
|
|
*
|
|
|
|
* Set foreign key references to defaults at update event on PK table.
|
|
|
|
* ----------
|
|
|
|
*/
|
2000-05-29 03:59:17 +02:00
|
|
|
Datum
|
|
|
|
RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
|
1999-09-30 16:54:24 +02:00
|
|
|
{
|
2000-05-29 03:59:17 +02:00
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-04-12 19:17:23 +02:00
|
|
|
Relation fk_rel;
|
|
|
|
Relation pk_rel;
|
|
|
|
HeapTuple new_row;
|
|
|
|
HeapTuple old_row;
|
|
|
|
RI_QueryKey qkey;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Check that this is a valid trigger call on the right time and event.
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* Get arguments.
|
|
|
|
*/
|
|
|
|
ri_FetchConstraintInfo(&riinfo,
|
|
|
|
trigdata->tg_trigger, trigdata->tg_relation, true);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-07 01:11:35 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Get the relation descriptors of the FK and PK tables and the old tuple.
|
2002-04-02 00:36:13 +02:00
|
|
|
*
|
2005-11-22 19:17:34 +01:00
|
|
|
* fk_rel is opened in RowExclusiveLock mode since that's what our
|
|
|
|
* eventual UPDATE will get on it.
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
|
2000-04-12 19:17:23 +02:00
|
|
|
pk_rel = trigdata->tg_relation;
|
1999-12-07 01:11:35 +01:00
|
|
|
new_row = trigdata->tg_newtuple;
|
|
|
|
old_row = trigdata->tg_trigtuple;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/* ----------
|
2012-06-18 18:19:38 +02:00
|
|
|
* SQL:2008 15.17 <Execution of referential actions>
|
|
|
|
* General rules 10) a) iii):
|
2012-06-18 02:16:07 +02:00
|
|
|
* MATCH SIMPLE/FULL
|
2000-04-12 19:17:23 +02:00
|
|
|
* ... ON UPDATE SET DEFAULT
|
|
|
|
* ----------
|
|
|
|
*/
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2012-06-19 00:50:03 +02:00
|
|
|
switch (ri_NullCheck(old_row, &riinfo, true))
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
|
|
|
case RI_KEYS_ALL_NULL:
|
|
|
|
case RI_KEYS_SOME_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
2012-06-18 18:19:38 +02:00
|
|
|
* No check needed - there cannot be any reference to old
|
|
|
|
* key if it contains a NULL
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2000-04-12 19:17:23 +02:00
|
|
|
|
1999-12-07 01:11:35 +01:00
|
|
|
case RI_KEYS_NONE_NULL:
|
2001-03-22 07:16:21 +01:00
|
|
|
|
|
|
|
/*
|
1999-12-07 01:11:35 +01:00
|
|
|
* Have a full qualified key - continue below
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-12-07 01:11:35 +01:00
|
|
|
* No need to do anything if old and new keys are equal
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
|
2002-04-02 00:36:13 +02:00
|
|
|
{
|
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2002-04-02 00:36:13 +02:00
|
|
|
}
|
1999-12-07 01:11:35 +01:00
|
|
|
|
|
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_connect failed");
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 01:37:23 +02:00
|
|
|
* Fetch or prepare a saved plan for the set default update
|
|
|
|
* operation
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETDEFAULT_UPD_DOUPDATE);
|
|
|
|
|
2012-06-19 01:37:23 +02:00
|
|
|
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
StringInfoData querybuf;
|
|
|
|
StringInfoData qualbuf;
|
2002-04-02 00:36:13 +02:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char attname[MAX_QUOTED_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char paramname[16];
|
2002-04-02 00:36:13 +02:00
|
|
|
const char *querysep;
|
|
|
|
const char *qualsep;
|
1999-12-07 01:11:35 +01:00
|
|
|
Oid queryoids[RI_MAX_NUMKEYS];
|
2003-03-27 20:25:40 +01:00
|
|
|
int i;
|
1999-12-07 01:11:35 +01:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* The query string built is
|
2003-09-28 04:11:23 +02:00
|
|
|
* UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...]
|
2007-02-14 02:58:58 +01:00
|
|
|
* WHERE $1 = fkatt1 [AND ...]
|
1999-12-07 01:11:35 +01:00
|
|
|
* The type id's for the $ parameters are those of the
|
2007-02-14 02:58:58 +01:00
|
|
|
* corresponding PK attributes.
|
1999-12-07 01:11:35 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
|
|
|
initStringInfo(&qualbuf);
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
|
1999-12-07 01:11:35 +01:00
|
|
|
querysep = "";
|
|
|
|
qualsep = "WHERE";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
1999-12-07 01:11:35 +01:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
quoteOneName(attname,
|
2007-02-14 02:58:58 +01:00
|
|
|
RIAttName(fk_rel, riinfo.fk_attnums[i]));
|
2012-06-18 18:12:52 +02:00
|
|
|
appendStringInfo(&querybuf,
|
|
|
|
"%s %s = DEFAULT",
|
|
|
|
querysep, attname);
|
2007-02-14 02:58:58 +01:00
|
|
|
sprintf(paramname, "$%d", i + 1);
|
|
|
|
ri_GenerateQual(&qualbuf, qualsep,
|
|
|
|
paramname, pk_type,
|
|
|
|
riinfo.pf_eq_oprs[i],
|
|
|
|
attname, fk_type);
|
2012-06-18 18:12:52 +02:00
|
|
|
querysep = ",";
|
1999-12-07 01:11:35 +01:00
|
|
|
qualsep = "AND";
|
2007-02-14 02:58:58 +01:00
|
|
|
queryoids[i] = pk_type;
|
1999-12-07 01:11:35 +01:00
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfoString(&querybuf, qualbuf.data);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2012-06-19 01:37:23 +02:00
|
|
|
/* Prepare and save the plan */
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
|
2012-06-19 01:37:23 +02:00
|
|
|
&qkey, fk_rel, pk_rel, true);
|
1999-12-07 01:11:35 +01:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have a plan now. Run it to update the existing references.
|
1999-12-07 01:11:35 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(&riinfo, &qkey, qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
fk_rel, pk_rel,
|
|
|
|
old_row, NULL,
|
2004-08-29 07:07:03 +02:00
|
|
|
true, /* must detect new rows */
|
2012-06-19 00:50:03 +02:00
|
|
|
SPI_OK_UPDATE);
|
2001-05-07 21:57:24 +02:00
|
|
|
|
1999-12-07 01:11:35 +01:00
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
2003-07-23 00:14:57 +02:00
|
|
|
elog(ERROR, "SPI_finish failed");
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
heap_close(fk_rel, RowExclusiveLock);
|
|
|
|
|
2003-04-07 22:30:38 +02:00
|
|
|
/*
|
2012-06-19 04:45:07 +02:00
|
|
|
* 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_keyequal_upd_fk would therefore
|
|
|
|
* 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.)
|
2003-04-07 22:30:38 +02:00
|
|
|
*/
|
|
|
|
RI_FKey_noaction_upd(fcinfo);
|
|
|
|
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-12-07 01:11:35 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2012-06-19 01:37:23 +02:00
|
|
|
* Handle MATCH PARTIAL set default update.
|
2000-04-12 19:17:23 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
1999-12-07 01:11:35 +01:00
|
|
|
}
|
|
|
|
|
2012-06-18 02:16:07 +02:00
|
|
|
/* Never reached */
|
2000-05-29 03:59:17 +02:00
|
|
|
return PointerGetDatum(NULL);
|
1999-09-30 16:54:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2000-01-06 21:47:01 +01:00
|
|
|
/* ----------
|
2005-05-30 09:20:59 +02:00
|
|
|
* RI_FKey_keyequal_upd_pk -
|
2000-01-06 21:47:01 +01:00
|
|
|
*
|
2005-05-30 09:20:59 +02:00
|
|
|
* Check if we have a key change on an update to a PK relation. This is
|
2005-10-29 20:39:17 +02:00
|
|
|
* used by the AFTER trigger queue manager to see if it can skip queuing
|
|
|
|
* an instance of an RI trigger.
|
2000-01-06 21:47:01 +01:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
bool
|
2005-05-30 09:20:59 +02:00
|
|
|
RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel,
|
|
|
|
HeapTuple old_row, HeapTuple new_row)
|
2000-01-06 21:47:01 +01:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2000-01-06 21:47:01 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2007-02-14 02:58:58 +01:00
|
|
|
* Get arguments.
|
2000-01-06 21:47:01 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
ri_FetchConstraintInfo(&riinfo, trigger, pk_rel, true);
|
2000-01-06 21:47:01 +01:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2000-01-06 21:47:01 +01:00
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2000-01-06 21:47:01 +01:00
|
|
|
return true;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
2000-01-06 21:47:01 +01:00
|
|
|
{
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2008-02-19 00:00:32 +01:00
|
|
|
/* Return true if keys are equal */
|
2007-02-14 02:58:58 +01:00
|
|
|
return ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true);
|
2000-01-06 21:47:01 +01:00
|
|
|
|
2005-10-15 04:49:52 +02:00
|
|
|
/* Handle MATCH PARTIAL set null delete. */
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
2000-01-06 21:47:01 +01:00
|
|
|
break;
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
2000-01-06 21:47:01 +01:00
|
|
|
}
|
|
|
|
|
2005-05-30 09:20:59 +02:00
|
|
|
/* Never reached */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* RI_FKey_keyequal_upd_fk -
|
|
|
|
*
|
|
|
|
* Check if we have a key change on an update to an FK relation. This is
|
2005-10-29 20:39:17 +02:00
|
|
|
* used by the AFTER trigger queue manager to see if it can skip queuing
|
|
|
|
* an instance of an RI trigger.
|
2005-05-30 09:20:59 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel,
|
|
|
|
HeapTuple old_row, HeapTuple new_row)
|
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
2005-05-30 09:20:59 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2007-02-14 02:58:58 +01:00
|
|
|
* Get arguments.
|
2000-01-06 21:47:01 +01:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
|
2005-05-30 09:20:59 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Nothing to do if no column names to compare given
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (riinfo.nkeys == 0)
|
2005-05-30 09:20:59 +02:00
|
|
|
return true;
|
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
switch (riinfo.confmatchtype)
|
2005-05-30 09:20:59 +02:00
|
|
|
{
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_FULL:
|
2008-02-19 00:00:32 +01:00
|
|
|
/* Return true if keys are equal */
|
2007-02-14 02:58:58 +01:00
|
|
|
return ri_KeysEqual(fk_rel, old_row, new_row, &riinfo, false);
|
2005-05-30 09:20:59 +02:00
|
|
|
|
2005-10-15 04:49:52 +02:00
|
|
|
/* Handle MATCH PARTIAL set null delete. */
|
2007-02-14 02:58:58 +01:00
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
2005-05-30 09:20:59 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
|
|
|
break;
|
2012-06-18 02:16:07 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
|
|
|
riinfo.confmatchtype);
|
|
|
|
break;
|
2005-05-30 09:20:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Never reached */
|
2000-01-06 21:47:01 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
/* ----------
|
|
|
|
* RI_Initial_Check -
|
|
|
|
*
|
|
|
|
* Check an entire table for non-matching values using a single query.
|
|
|
|
* This is not a trigger procedure, but is called during ALTER TABLE
|
|
|
|
* ADD FOREIGN KEY to validate the initial table contents.
|
|
|
|
*
|
2011-04-10 17:42:00 +02:00
|
|
|
* We expect that the caller has made provision to prevent any problems
|
2011-02-08 13:23:20 +01:00
|
|
|
* caused by concurrent actions. This could be either by locking rel and
|
|
|
|
* pkrel at ShareRowExclusiveLock or higher, or by otherwise ensuring
|
|
|
|
* that triggers implementing the checks are already active.
|
|
|
|
* Hence, we do not need to lock individual rows for the check.
|
2003-10-06 18:38:28 +02:00
|
|
|
*
|
|
|
|
* If the check fails because the current user doesn't have permissions
|
|
|
|
* to read both tables, return false to let our caller know that they will
|
|
|
|
* need to do something else to check the constraint.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
bool
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
|
2003-10-06 18:38:28 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_ConstraintInfo riinfo;
|
|
|
|
StringInfoData querybuf;
|
2003-10-06 18:38:28 +02:00
|
|
|
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
|
2007-02-14 02:58:58 +01:00
|
|
|
char fkrelname[MAX_QUOTED_REL_NAME_LEN];
|
|
|
|
char pkattname[MAX_QUOTED_NAME_LEN + 3];
|
|
|
|
char fkattname[MAX_QUOTED_NAME_LEN + 3];
|
2011-04-10 17:42:00 +02:00
|
|
|
RangeTblEntry *pkrte;
|
|
|
|
RangeTblEntry *fkrte;
|
2003-10-06 18:38:28 +02:00
|
|
|
const char *sep;
|
2007-02-14 02:58:58 +01:00
|
|
|
int i;
|
Improve and simplify CREATE EXTENSION's management of GUC variables.
CREATE EXTENSION needs to transiently set search_path, as well as
client_min_messages and log_min_messages. We were doing this by the
expedient of saving the current string value of each variable, doing a
SET LOCAL, and then doing another SET LOCAL with the previous value at
the end of the command. This is a bit expensive though, and it also fails
badly if there is anything funny about the existing search_path value,
as seen in a recent report from Roger Niederland. Fortunately, there's a
much better way, which is to piggyback on the GUC infrastructure previously
developed for functions with SET options. We just open a new GUC nesting
level, do our assignments with GUC_ACTION_SAVE, and then close the nesting
level when done. This automatically restores the prior settings without a
re-parsing pass, so (in principle anyway) there can't be an error. And
guc.c still takes care of cleanup in event of an error abort.
The CREATE EXTENSION code for this was modeled on some much older code in
ri_triggers.c, which I also changed to use the better method, even though
there wasn't really much risk of failure there. Also improve the comments
in guc.c to reflect this additional usage.
2011-10-06 02:44:16 +02:00
|
|
|
int save_nestlevel;
|
2004-02-03 18:34:04 +01:00
|
|
|
char workmembuf[32];
|
2003-10-06 18:38:28 +02:00
|
|
|
int spi_result;
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2003-10-06 18:38:28 +02:00
|
|
|
|
2010-07-22 02:47:59 +02:00
|
|
|
/* Fetch constraint info. */
|
|
|
|
ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
|
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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.)
|
2003-10-06 18:38:28 +02:00
|
|
|
*
|
|
|
|
* XXX are there any other show-stopper conditions to check?
|
|
|
|
*/
|
2010-07-22 02:47:59 +02:00
|
|
|
pkrte = makeNode(RangeTblEntry);
|
|
|
|
pkrte->rtekind = RTE_RELATION;
|
|
|
|
pkrte->relid = RelationGetRelid(pk_rel);
|
2011-02-23 01:23:23 +01:00
|
|
|
pkrte->relkind = pk_rel->rd_rel->relkind;
|
2010-07-22 02:47:59 +02:00
|
|
|
pkrte->requiredPerms = ACL_SELECT;
|
2003-10-06 18:38:28 +02:00
|
|
|
|
2010-07-22 02:47:59 +02:00
|
|
|
fkrte = makeNode(RangeTblEntry);
|
|
|
|
fkrte->rtekind = RTE_RELATION;
|
|
|
|
fkrte->relid = RelationGetRelid(fk_rel);
|
2011-02-23 01:23:23 +01:00
|
|
|
fkrte->relkind = fk_rel->rd_rel->relkind;
|
2010-07-22 02:47:59 +02:00
|
|
|
fkrte->requiredPerms = ACL_SELECT;
|
|
|
|
|
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
|
|
|
{
|
2011-04-10 17:42:00 +02:00
|
|
|
int attno;
|
2010-07-22 02:47:59 +02:00
|
|
|
|
|
|
|
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;
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
/*----------
|
|
|
|
* The query string built is:
|
2004-08-29 07:07:03 +02:00
|
|
|
* 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
|
2012-06-18 02:16:07 +02:00
|
|
|
* For MATCH SIMPLE:
|
2004-08-29 07:07:03 +02:00
|
|
|
* (fk.keycol1 IS NOT NULL [AND ...])
|
2003-10-06 18:38:28 +02:00
|
|
|
* For MATCH FULL:
|
2004-08-29 07:07:03 +02:00
|
|
|
* (fk.keycol1 IS NOT NULL [OR ...])
|
2011-04-12 03:32:53 +02:00
|
|
|
*
|
|
|
|
* We attach COLLATE clauses to the operators when comparing columns
|
|
|
|
* that have different collations.
|
2003-10-06 18:38:28 +02:00
|
|
|
*----------
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
initStringInfo(&querybuf);
|
|
|
|
appendStringInfo(&querybuf, "SELECT ");
|
2004-08-29 07:07:03 +02:00
|
|
|
sep = "";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
2003-10-06 18:38:28 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
quoteOneName(fkattname,
|
|
|
|
RIAttName(fk_rel, riinfo.fk_attnums[i]));
|
|
|
|
appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
|
2003-10-06 18:38:28 +02:00
|
|
|
sep = ", ";
|
|
|
|
}
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
quoteRelationName(pkrelname, pk_rel);
|
|
|
|
quoteRelationName(fkrelname, fk_rel);
|
|
|
|
appendStringInfo(&querybuf,
|
|
|
|
" FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
|
|
|
|
fkrelname, pkrelname);
|
2003-10-06 18:38:28 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
strcpy(pkattname, "pk.");
|
|
|
|
strcpy(fkattname, "fk.");
|
|
|
|
sep = "(";
|
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
2003-10-06 18:38:28 +02:00
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
|
2011-04-12 03:32:53 +02:00
|
|
|
Oid pk_coll = RIAttCollation(pk_rel, riinfo.pk_attnums[i]);
|
|
|
|
Oid fk_coll = RIAttCollation(fk_rel, riinfo.fk_attnums[i]);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
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);
|
2011-04-12 03:32:53 +02:00
|
|
|
if (pk_coll != fk_coll)
|
|
|
|
ri_GenerateQualCollation(&querybuf, pk_coll);
|
2007-02-14 02:58:58 +01:00
|
|
|
sep = "AND";
|
2003-10-06 18:38:28 +02:00
|
|
|
}
|
2004-08-29 07:07:03 +02:00
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* It's sufficient to test any one pk attribute for null to detect a join
|
|
|
|
* failure.
|
2003-10-06 18:38:28 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
quoteOneName(pkattname, RIAttName(pk_rel, riinfo.pk_attnums[0]));
|
|
|
|
appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
|
2003-10-06 18:38:28 +02:00
|
|
|
|
2004-08-29 07:07:03 +02:00
|
|
|
sep = "";
|
2007-02-14 02:58:58 +01:00
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
2003-10-06 18:38:28 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
quoteOneName(fkattname, RIAttName(fk_rel, riinfo.fk_attnums[i]));
|
|
|
|
appendStringInfo(&querybuf,
|
|
|
|
"%sfk.%s IS NOT NULL",
|
|
|
|
sep, fkattname);
|
|
|
|
switch (riinfo.confmatchtype)
|
2003-10-06 18:38:28 +02:00
|
|
|
{
|
2012-06-18 02:16:07 +02:00
|
|
|
case FKCONSTR_MATCH_SIMPLE:
|
2004-08-29 07:07:03 +02:00
|
|
|
sep = " AND ";
|
2003-10-06 18:38:28 +02:00
|
|
|
break;
|
|
|
|
case FKCONSTR_MATCH_FULL:
|
2004-08-29 07:07:03 +02:00
|
|
|
sep = " OR ";
|
2003-10-06 18:38:28 +02:00
|
|
|
break;
|
|
|
|
case FKCONSTR_MATCH_PARTIAL:
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
|
|
errmsg("MATCH PARTIAL not yet implemented")));
|
|
|
|
break;
|
|
|
|
default:
|
2012-06-18 02:16:07 +02:00
|
|
|
elog(ERROR, "unrecognized confmatchtype: %d",
|
2007-02-14 02:58:58 +01:00
|
|
|
riinfo.confmatchtype);
|
2003-10-06 18:38:28 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(&querybuf, ")");
|
2003-10-06 18:38:28 +02:00
|
|
|
|
2004-02-03 18:34:04 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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.
|
2004-02-03 18:34:04 +01:00
|
|
|
*
|
Improve and simplify CREATE EXTENSION's management of GUC variables.
CREATE EXTENSION needs to transiently set search_path, as well as
client_min_messages and log_min_messages. We were doing this by the
expedient of saving the current string value of each variable, doing a
SET LOCAL, and then doing another SET LOCAL with the previous value at
the end of the command. This is a bit expensive though, and it also fails
badly if there is anything funny about the existing search_path value,
as seen in a recent report from Roger Niederland. Fortunately, there's a
much better way, which is to piggyback on the GUC infrastructure previously
developed for functions with SET options. We just open a new GUC nesting
level, do our assignments with GUC_ACTION_SAVE, and then close the nesting
level when done. This automatically restores the prior settings without a
re-parsing pass, so (in principle anyway) there can't be an error. And
guc.c still takes care of cleanup in event of an error abort.
The CREATE EXTENSION code for this was modeled on some much older code in
ri_triggers.c, which I also changed to use the better method, even though
there wasn't really much risk of failure there. Also improve the comments
in guc.c to reflect this additional usage.
2011-10-06 02:44:16 +02:00
|
|
|
* 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.
|
2004-02-03 18:34:04 +01:00
|
|
|
*/
|
Improve and simplify CREATE EXTENSION's management of GUC variables.
CREATE EXTENSION needs to transiently set search_path, as well as
client_min_messages and log_min_messages. We were doing this by the
expedient of saving the current string value of each variable, doing a
SET LOCAL, and then doing another SET LOCAL with the previous value at
the end of the command. This is a bit expensive though, and it also fails
badly if there is anything funny about the existing search_path value,
as seen in a recent report from Roger Niederland. Fortunately, there's a
much better way, which is to piggyback on the GUC infrastructure previously
developed for functions with SET options. We just open a new GUC nesting
level, do our assignments with GUC_ACTION_SAVE, and then close the nesting
level when done. This automatically restores the prior settings without a
re-parsing pass, so (in principle anyway) there can't be an error. And
guc.c still takes care of cleanup in event of an error abort.
The CREATE EXTENSION code for this was modeled on some much older code in
ri_triggers.c, which I also changed to use the better method, even though
there wasn't really much risk of failure there. Also improve the comments
in guc.c to reflect this additional usage.
2011-10-06 02:44:16 +02:00
|
|
|
save_nestlevel = NewGUCNestLevel();
|
|
|
|
|
2004-02-03 18:34:04 +01:00
|
|
|
snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem);
|
|
|
|
(void) set_config_option("work_mem", workmembuf,
|
|
|
|
PGC_USERSET, PGC_S_SESSION,
|
Improve and simplify CREATE EXTENSION's management of GUC variables.
CREATE EXTENSION needs to transiently set search_path, as well as
client_min_messages and log_min_messages. We were doing this by the
expedient of saving the current string value of each variable, doing a
SET LOCAL, and then doing another SET LOCAL with the previous value at
the end of the command. This is a bit expensive though, and it also fails
badly if there is anything funny about the existing search_path value,
as seen in a recent report from Roger Niederland. Fortunately, there's a
much better way, which is to piggyback on the GUC infrastructure previously
developed for functions with SET options. We just open a new GUC nesting
level, do our assignments with GUC_ACTION_SAVE, and then close the nesting
level when done. This automatically restores the prior settings without a
re-parsing pass, so (in principle anyway) there can't be an error. And
guc.c still takes care of cleanup in event of an error abort.
The CREATE EXTENSION code for this was modeled on some much older code in
ri_triggers.c, which I also changed to use the better method, even though
there wasn't really much risk of failure there. Also improve the comments
in guc.c to reflect this additional usage.
2011-10-06 02:44:16 +02:00
|
|
|
GUC_ACTION_SAVE, true, 0);
|
2004-02-03 18:34:04 +01:00
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
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
|
2004-08-29 07:07:03 +02:00
|
|
|
* arguments to the plan.
|
2003-10-06 18:38:28 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
qplan = SPI_prepare(querybuf.data, 0, NULL);
|
2003-10-06 18:38:28 +02:00
|
|
|
|
|
|
|
if (qplan == NULL)
|
2007-02-14 02:58:58 +01:00
|
|
|
elog(ERROR, "SPI_prepare returned %d for %s",
|
|
|
|
SPI_result, querybuf.data);
|
2003-10-06 18:38:28 +02:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Run the plan. For safety we force a current snapshot to be used. (In
|
2011-04-10 17:42:00 +02:00
|
|
|
* 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.
|
2003-10-06 18:38:28 +02:00
|
|
|
*/
|
2004-09-13 22:10:13 +02:00
|
|
|
spi_result = SPI_execute_snapshot(qplan,
|
|
|
|
NULL, NULL,
|
2008-05-12 22:02:02 +02:00
|
|
|
GetLatestSnapshot(),
|
2004-09-13 22:10:13 +02:00
|
|
|
InvalidSnapshot,
|
2007-08-15 21:15:47 +02:00
|
|
|
true, false, 1);
|
2003-10-06 18:38:28 +02:00
|
|
|
|
|
|
|
/* Check result */
|
|
|
|
if (spi_result != SPI_OK_SELECT)
|
2004-09-13 22:10:13 +02:00
|
|
|
elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
|
2003-10-06 18:38:28 +02:00
|
|
|
|
|
|
|
/* Did we find a tuple violating the constraint? */
|
|
|
|
if (SPI_processed > 0)
|
|
|
|
{
|
|
|
|
HeapTuple tuple = SPI_tuptable->vals[0];
|
|
|
|
TupleDesc tupdesc = SPI_tuptable->tupdesc;
|
2012-06-19 00:50:03 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < riinfo.nkeys; i++)
|
|
|
|
riinfo.fk_attnums[i] = i + 1;
|
2003-10-06 18:38:28 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If it's MATCH FULL, and there are any nulls in the FK keys,
|
2005-10-15 04:49:52 +02:00
|
|
|
* complain about that rather than the lack of a match. MATCH FULL
|
|
|
|
* disallows partially-null FK rows.
|
2003-10-06 18:38:28 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL &&
|
|
|
|
ri_NullCheck(tuple, &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(riinfo.conname)),
|
|
|
|
errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
|
2003-10-06 18:38:28 +02:00
|
|
|
|
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
|
|
|
|
* query, which isn't true, but will cause it to use riinfo.fk_attnums
|
|
|
|
* as we need.
|
2003-10-06 18:38:28 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ReportViolation(&riinfo,
|
2007-02-14 02:58:58 +01:00
|
|
|
pk_rel, fk_rel,
|
2003-10-06 18:38:28 +02:00
|
|
|
tuple, tupdesc,
|
2012-06-19 00:50:03 +02:00
|
|
|
RI_PLAN_CHECK_LOOKUPPK, false);
|
2003-10-06 18:38:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
|
|
|
elog(ERROR, "SPI_finish failed");
|
|
|
|
|
2004-02-03 18:34:04 +01:00
|
|
|
/*
|
Improve and simplify CREATE EXTENSION's management of GUC variables.
CREATE EXTENSION needs to transiently set search_path, as well as
client_min_messages and log_min_messages. We were doing this by the
expedient of saving the current string value of each variable, doing a
SET LOCAL, and then doing another SET LOCAL with the previous value at
the end of the command. This is a bit expensive though, and it also fails
badly if there is anything funny about the existing search_path value,
as seen in a recent report from Roger Niederland. Fortunately, there's a
much better way, which is to piggyback on the GUC infrastructure previously
developed for functions with SET options. We just open a new GUC nesting
level, do our assignments with GUC_ACTION_SAVE, and then close the nesting
level when done. This automatically restores the prior settings without a
re-parsing pass, so (in principle anyway) there can't be an error. And
guc.c still takes care of cleanup in event of an error abort.
The CREATE EXTENSION code for this was modeled on some much older code in
ri_triggers.c, which I also changed to use the better method, even though
there wasn't really much risk of failure there. Also improve the comments
in guc.c to reflect this additional usage.
2011-10-06 02:44:16 +02:00
|
|
|
* Restore work_mem.
|
2004-02-03 18:34:04 +01:00
|
|
|
*/
|
Improve and simplify CREATE EXTENSION's management of GUC variables.
CREATE EXTENSION needs to transiently set search_path, as well as
client_min_messages and log_min_messages. We were doing this by the
expedient of saving the current string value of each variable, doing a
SET LOCAL, and then doing another SET LOCAL with the previous value at
the end of the command. This is a bit expensive though, and it also fails
badly if there is anything funny about the existing search_path value,
as seen in a recent report from Roger Niederland. Fortunately, there's a
much better way, which is to piggyback on the GUC infrastructure previously
developed for functions with SET options. We just open a new GUC nesting
level, do our assignments with GUC_ACTION_SAVE, and then close the nesting
level when done. This automatically restores the prior settings without a
re-parsing pass, so (in principle anyway) there can't be an error. And
guc.c still takes care of cleanup in event of an error abort.
The CREATE EXTENSION code for this was modeled on some much older code in
ri_triggers.c, which I also changed to use the better method, even though
there wasn't really much risk of failure there. Also improve the comments
in guc.c to reflect this additional usage.
2011-10-06 02:44:16 +02:00
|
|
|
AtEOXact_GUC(true, save_nestlevel);
|
2004-02-03 18:34:04 +01:00
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
return true;
|
|
|
|
}
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* Local functions below
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
/*
|
|
|
|
* quoteOneName --- safely quote a single SQL name
|
|
|
|
*
|
|
|
|
* buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
quoteOneName(char *buffer, const char *name)
|
|
|
|
{
|
|
|
|
/* Rather than trying to be smart, just always quote it. */
|
|
|
|
*buffer++ = '"';
|
|
|
|
while (*name)
|
|
|
|
{
|
|
|
|
if (*name == '"')
|
|
|
|
*buffer++ = '"';
|
|
|
|
*buffer++ = *name++;
|
|
|
|
}
|
|
|
|
*buffer++ = '"';
|
|
|
|
*buffer = '\0';
|
|
|
|
}
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2002-04-02 00:36:13 +02:00
|
|
|
/*
|
|
|
|
* quoteRelationName --- safely quote a fully qualified relation name
|
|
|
|
*
|
|
|
|
* buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
quoteRelationName(char *buffer, Relation rel)
|
|
|
|
{
|
2002-04-02 03:03:07 +02:00
|
|
|
quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel)));
|
2002-04-02 00:36:13 +02:00
|
|
|
buffer += strlen(buffer);
|
|
|
|
*buffer++ = '.';
|
|
|
|
quoteOneName(buffer, RelationGetRelationName(rel));
|
|
|
|
}
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* ri_GenerateQual --- generate a WHERE clause equating two variables
|
1999-10-08 14:00:08 +02:00
|
|
|
*
|
2007-02-14 02:58:58 +01:00
|
|
|
* The idea is to append " sep leftop op rightop" to buf. The complexity
|
|
|
|
* comes from needing to be sure that the parser will select the desired
|
|
|
|
* operator. We always name the operator using OPERATOR(schema.op) syntax
|
2008-02-07 23:58:35 +01:00
|
|
|
* (readability isn't a big priority here), so as to avoid search-path
|
|
|
|
* uncertainties. We have to emit casts too, if either input isn't already
|
|
|
|
* the input type of the operator; else we are at the mercy of the parser's
|
|
|
|
* heuristics for ambiguous-operator resolution.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
static void
|
|
|
|
ri_GenerateQual(StringInfo buf,
|
|
|
|
const char *sep,
|
|
|
|
const char *leftop, Oid leftoptype,
|
|
|
|
Oid opoid,
|
|
|
|
const char *rightop, Oid rightoptype)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
HeapTuple opertup;
|
|
|
|
Form_pg_operator operform;
|
|
|
|
char *oprname;
|
|
|
|
char *nspname;
|
|
|
|
|
2010-02-14 19:42:19 +01:00
|
|
|
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid));
|
2007-02-14 02:58:58 +01:00
|
|
|
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)
|
2008-02-07 23:58:35 +01:00
|
|
|
ri_add_cast_to(buf, operform->oprleft);
|
2007-02-14 02:58:58 +01:00
|
|
|
appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
|
|
|
|
appendStringInfoString(buf, oprname);
|
|
|
|
appendStringInfo(buf, ") %s", rightop);
|
|
|
|
if (rightoptype != operform->oprright)
|
2008-02-07 23:58:35 +01:00
|
|
|
ri_add_cast_to(buf, operform->oprright);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
ReleaseSysCache(opertup);
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2008-02-07 23:58:35 +01:00
|
|
|
/*
|
|
|
|
* Add a cast specification to buf. We spell out the type name the hard way,
|
|
|
|
* intentionally not using format_type_be(). This is to avoid corner cases
|
|
|
|
* for CHARACTER, BIT, and perhaps other types, where specifying the type
|
|
|
|
* using SQL-standard syntax results in undesirable data truncation. By
|
|
|
|
* doing it this way we can be certain that the cast will have default (-1)
|
|
|
|
* target typmod.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ri_add_cast_to(StringInfo buf, Oid typid)
|
|
|
|
{
|
|
|
|
HeapTuple typetup;
|
|
|
|
Form_pg_type typform;
|
|
|
|
char *typname;
|
|
|
|
char *nspname;
|
|
|
|
|
2010-02-14 19:42:19 +01:00
|
|
|
typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
|
2008-02-07 23:58:35 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-04-12 03:32:53 +02:00
|
|
|
/*
|
|
|
|
* ri_GenerateQualCollation --- add a COLLATE spec to a WHERE clause
|
|
|
|
*
|
|
|
|
* At present, we intentionally do not use this function for RI queries that
|
|
|
|
* compare a variable to a $n parameter. Since parameter symbols always have
|
|
|
|
* default collation, the effect will be to use the variable's collation.
|
|
|
|
* Now that is only strictly correct when testing the referenced column, since
|
|
|
|
* the SQL standard specifies that RI comparisons should use the referenced
|
|
|
|
* column's collation. However, so long as all collations have the same
|
|
|
|
* notion of equality (which they do, because texteq reduces to bitwise
|
|
|
|
* equality), there's no visible semantic impact from using the referencing
|
|
|
|
* column's collation when testing it, and this is a good thing to do because
|
|
|
|
* it lets us use a normal index on the referencing column. However, we do
|
|
|
|
* have to use this function when directly comparing the referencing and
|
|
|
|
* referenced columns, if they are of different collations; else the parser
|
|
|
|
* will fail to resolve the collation to use.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ri_GenerateQualCollation(StringInfo buf, Oid collation)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
|
|
|
/*
|
2011-06-09 20:32:50 +02:00
|
|
|
* We qualify the name always, for simplicity and to ensure the query is
|
|
|
|
* not search-path-dependent.
|
2011-04-12 03:32:53 +02:00
|
|
|
*/
|
|
|
|
quoteOneName(onename, get_namespace_name(colltup->collnamespace));
|
|
|
|
appendStringInfo(buf, " COLLATE %s", onename);
|
|
|
|
quoteOneName(onename, collname);
|
|
|
|
appendStringInfo(buf, ".%s", onename);
|
|
|
|
|
|
|
|
ReleaseSysCache(tp);
|
|
|
|
}
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
/* ----------
|
2012-06-19 00:50:03 +02:00
|
|
|
* ri_BuildQueryKey -
|
1999-10-08 14:00:08 +02:00
|
|
|
*
|
2012-06-19 00:50:03 +02:00
|
|
|
* Construct a hashtable key for a prepared SPI plan of an FK constraint.
|
1999-10-08 14:00:08 +02:00
|
|
|
*
|
2007-02-14 02:58:58 +01:00
|
|
|
* key: output argument, *key is filled in based on the other arguments
|
|
|
|
* riinfo: info from pg_constraint entry
|
2012-06-19 00:50:03 +02:00
|
|
|
* constr_queryno: an internal number identifying the query type
|
|
|
|
* (see RI_PLAN_XXX constants at head of file)
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
2000-04-12 19:17:23 +02:00
|
|
|
static void
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo,
|
|
|
|
int32 constr_queryno)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
/*
|
|
|
|
* We assume struct RI_QueryKey contains no padding bytes, else we'd
|
|
|
|
* need to use memset to clear them.
|
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
key->constr_id = riinfo->constraint_id;
|
2000-04-12 19:17:23 +02:00
|
|
|
key->constr_queryno = constr_queryno;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
2003-03-15 22:19:40 +01:00
|
|
|
/*
|
|
|
|
* Check that RI trigger function was called in expected context
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind)
|
|
|
|
{
|
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
|
|
|
|
|
|
|
if (!CALLED_AS_TRIGGER(fcinfo))
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
|
2004-08-29 07:07:03 +02:00
|
|
|
errmsg("function \"%s\" was not called by trigger manager", funcname)));
|
2003-07-23 00:14:57 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Check proper event
|
|
|
|
*/
|
2003-03-15 22:19:40 +01:00
|
|
|
if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
|
|
|
|
!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("function \"%s\" must be fired AFTER ROW", funcname)));
|
2003-03-15 22:19:40 +01:00
|
|
|
|
|
|
|
switch (tgkind)
|
|
|
|
{
|
|
|
|
case RI_TRIGTYPE_INSERT:
|
|
|
|
if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
|
|
|
|
errmsg("function \"%s\" must be fired for INSERT", funcname)));
|
2003-03-15 22:19:40 +01:00
|
|
|
break;
|
|
|
|
case RI_TRIGTYPE_UPDATE:
|
|
|
|
if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
|
|
|
|
errmsg("function \"%s\" must be fired for UPDATE", funcname)));
|
2003-03-15 22:19:40 +01:00
|
|
|
break;
|
|
|
|
case RI_TRIGTYPE_INUP:
|
|
|
|
if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) &&
|
|
|
|
!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
|
|
|
|
errmsg("function \"%s\" must be fired for INSERT or UPDATE",
|
|
|
|
funcname)));
|
2003-03-15 22:19:40 +01:00
|
|
|
break;
|
|
|
|
case RI_TRIGTYPE_DELETE:
|
|
|
|
if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
2005-10-15 04:49:52 +02:00
|
|
|
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
|
|
|
|
errmsg("function \"%s\" must be fired for DELETE", funcname)));
|
2003-03-15 22:19:40 +01:00
|
|
|
break;
|
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
}
|
2003-07-23 00:14:57 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Fetch the pg_constraint entry for the FK constraint, and fill *riinfo
|
|
|
|
*/
|
|
|
|
static void
|
2007-11-15 23:25:18 +01:00
|
|
|
ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
|
2007-02-14 02:58:58 +01:00
|
|
|
Trigger *trigger, Relation trig_rel, bool rel_is_pk)
|
|
|
|
{
|
|
|
|
Oid constraintOid = trigger->tgconstraint;
|
|
|
|
HeapTuple tup;
|
|
|
|
Form_pg_constraint conForm;
|
|
|
|
Datum adatum;
|
|
|
|
bool isNull;
|
|
|
|
ArrayType *arr;
|
|
|
|
int numkeys;
|
2003-07-23 00:14:57 +02:00
|
|
|
|
|
|
|
/*
|
2007-11-15 22:14:46 +01:00
|
|
|
* 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".
|
2003-07-23 00:14:57 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (!OidIsValid(constraintOid))
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
2007-11-15 22:14:46 +01:00
|
|
|
errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"",
|
|
|
|
trigger->tgname, RelationGetRelationName(trig_rel)),
|
2003-09-29 02:05:25 +02:00
|
|
|
errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT.")));
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
/* OK, fetch the tuple */
|
2010-02-14 19:42:19 +01:00
|
|
|
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
|
2007-02-14 02:58:58 +01:00
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
|
|
elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
|
|
|
|
conForm = (Form_pg_constraint) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
/* Do some easy cross-checks against the trigger call data */
|
|
|
|
if (rel_is_pk)
|
|
|
|
{
|
|
|
|
if (conForm->contype != CONSTRAINT_FOREIGN ||
|
|
|
|
conForm->conrelid != trigger->tgconstrrelid ||
|
|
|
|
conForm->confrelid != RelationGetRelid(trig_rel))
|
|
|
|
elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
|
|
|
|
trigger->tgname, RelationGetRelationName(trig_rel));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (conForm->contype != CONSTRAINT_FOREIGN ||
|
|
|
|
conForm->conrelid != RelationGetRelid(trig_rel) ||
|
|
|
|
conForm->confrelid != trigger->tgconstrrelid)
|
|
|
|
elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
|
|
|
|
trigger->tgname, RelationGetRelationName(trig_rel));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* And extract data */
|
|
|
|
riinfo->constraint_id = 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.
|
2007-11-15 22:14:46 +01:00
|
|
|
* We don't need to use deconstruct_array() since the array data is just
|
|
|
|
* going to look like a C array of values.
|
2007-02-14 02:58:58 +01:00
|
|
|
*/
|
|
|
|
adatum = SysCacheGetAttr(CONSTROID, tup,
|
|
|
|
Anum_pg_constraint_conkey, &isNull);
|
|
|
|
if (isNull)
|
|
|
|
elog(ERROR, "null conkey for constraint %u", constraintOid);
|
2007-11-15 22:14:46 +01:00
|
|
|
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
2007-02-14 02:58:58 +01:00
|
|
|
numkeys = ARR_DIMS(arr)[0];
|
|
|
|
if (ARR_NDIM(arr) != 1 ||
|
|
|
|
numkeys < 0 ||
|
|
|
|
numkeys > RI_MAX_NUMKEYS ||
|
|
|
|
ARR_HASNULL(arr) ||
|
|
|
|
ARR_ELEMTYPE(arr) != INT2OID)
|
|
|
|
elog(ERROR, "conkey is not a 1-D smallint array");
|
|
|
|
riinfo->nkeys = numkeys;
|
|
|
|
memcpy(riinfo->fk_attnums, ARR_DATA_PTR(arr), numkeys * sizeof(int16));
|
2008-01-25 05:46:07 +01:00
|
|
|
if ((Pointer) arr != DatumGetPointer(adatum))
|
|
|
|
pfree(arr); /* free de-toasted copy, if any */
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
adatum = SysCacheGetAttr(CONSTROID, tup,
|
|
|
|
Anum_pg_constraint_confkey, &isNull);
|
|
|
|
if (isNull)
|
|
|
|
elog(ERROR, "null confkey for constraint %u", constraintOid);
|
2007-11-15 22:14:46 +01:00
|
|
|
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
2007-02-14 02:58:58 +01:00
|
|
|
numkeys = ARR_DIMS(arr)[0];
|
|
|
|
if (ARR_NDIM(arr) != 1 ||
|
|
|
|
numkeys != riinfo->nkeys ||
|
|
|
|
numkeys > RI_MAX_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));
|
2008-01-25 05:46:07 +01:00
|
|
|
if ((Pointer) arr != DatumGetPointer(adatum))
|
|
|
|
pfree(arr); /* free de-toasted copy, if any */
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
adatum = SysCacheGetAttr(CONSTROID, tup,
|
|
|
|
Anum_pg_constraint_conpfeqop, &isNull);
|
|
|
|
if (isNull)
|
|
|
|
elog(ERROR, "null conpfeqop for constraint %u", constraintOid);
|
2007-11-15 22:14:46 +01:00
|
|
|
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
2007-02-14 02:58:58 +01:00
|
|
|
numkeys = ARR_DIMS(arr)[0];
|
ALTER TABLE: skip FK validation when it's safe to do so
We already skip rewriting the table in these cases, but we still force a
whole table scan to validate the data. This can be skipped, and thus
we can make the whole ALTER TABLE operation just do some catalog touches
instead of scanning the table, when these two conditions hold:
(a) Old and new pg_constraint.conpfeqop match exactly. This is actually
stronger than needed; we could loosen things by way of operator
families, but it'd require a lot more effort.
(b) The functions, if any, implementing a cast from the foreign type to
the primary opcintype are the same. For this purpose, we can consider a
binary coercion equivalent to an exact type match. When the opcintype
is polymorphic, require that the old and new foreign types match
exactly. (Since ri_triggers.c does use the executor, the stronger check
for polymorphic types is no mere future-proofing. However, no core type
exercises its necessity.)
Author: Noah Misch
Committer's note: catalog version bumped due to change of the Constraint
node. I can't actually find any way to have such a node in a stored
rule, but given that we have "out" support for them, better be safe.
2012-02-27 22:28:00 +01:00
|
|
|
/* see TryReuseForeignKey if you change the test below */
|
2007-02-14 02:58:58 +01:00
|
|
|
if (ARR_NDIM(arr) != 1 ||
|
|
|
|
numkeys != riinfo->nkeys ||
|
|
|
|
numkeys > RI_MAX_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));
|
2008-01-25 05:46:07 +01:00
|
|
|
if ((Pointer) arr != DatumGetPointer(adatum))
|
|
|
|
pfree(arr); /* free de-toasted copy, if any */
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
adatum = SysCacheGetAttr(CONSTROID, tup,
|
|
|
|
Anum_pg_constraint_conppeqop, &isNull);
|
|
|
|
if (isNull)
|
|
|
|
elog(ERROR, "null conppeqop for constraint %u", constraintOid);
|
2007-11-15 22:14:46 +01:00
|
|
|
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
2007-02-14 02:58:58 +01:00
|
|
|
numkeys = ARR_DIMS(arr)[0];
|
|
|
|
if (ARR_NDIM(arr) != 1 ||
|
|
|
|
numkeys != riinfo->nkeys ||
|
|
|
|
numkeys > RI_MAX_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));
|
2008-01-25 05:46:07 +01:00
|
|
|
if ((Pointer) arr != DatumGetPointer(adatum))
|
|
|
|
pfree(arr); /* free de-toasted copy, if any */
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
adatum = SysCacheGetAttr(CONSTROID, tup,
|
|
|
|
Anum_pg_constraint_conffeqop, &isNull);
|
|
|
|
if (isNull)
|
|
|
|
elog(ERROR, "null conffeqop for constraint %u", constraintOid);
|
2007-11-15 22:14:46 +01:00
|
|
|
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
2007-02-14 02:58:58 +01:00
|
|
|
numkeys = ARR_DIMS(arr)[0];
|
|
|
|
if (ARR_NDIM(arr) != 1 ||
|
|
|
|
numkeys != riinfo->nkeys ||
|
|
|
|
numkeys > RI_MAX_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));
|
2008-01-25 05:46:07 +01:00
|
|
|
if ((Pointer) arr != DatumGetPointer(adatum))
|
|
|
|
pfree(arr); /* free de-toasted copy, if any */
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
ReleaseSysCache(tup);
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2003-04-27 00:21:47 +02:00
|
|
|
/*
|
|
|
|
* Prepare execution plan for a query to enforce an RI restriction
|
|
|
|
*
|
|
|
|
* If cache_plan is true, the plan is saved into our plan hashtable
|
|
|
|
* so that we don't need to plan it again.
|
|
|
|
*/
|
2007-03-16 00:12:07 +01:00
|
|
|
static SPIPlanPtr
|
2003-04-27 00:21:47 +02:00
|
|
|
ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
|
|
|
|
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
|
|
|
|
bool cache_plan)
|
|
|
|
{
|
2007-03-16 00:12:07 +01:00
|
|
|
SPIPlanPtr qplan;
|
2003-04-27 00:21:47 +02:00
|
|
|
Relation query_rel;
|
2008-01-03 22:23:15 +01:00
|
|
|
Oid save_userid;
|
Prevent indirect security attacks via changing session-local state within
an allegedly immutable index function. It was previously recognized that
we had to prevent such a function from executing SET/RESET ROLE/SESSION
AUTHORIZATION, or it could trivially obtain the privileges of the session
user. However, since there is in general no privilege checking for changes
of session-local state, it is also possible for such a function to change
settings in a way that might subvert later operations in the same session.
Examples include changing search_path to cause an unexpected function to
be called, or replacing an existing prepared statement with another one
that will execute a function of the attacker's choosing.
The present patch secures VACUUM, ANALYZE, and CREATE INDEX/REINDEX against
these threats, which are the same places previously deemed to need protection
against the SET ROLE issue. GUC changes are still allowed, since there are
many useful cases for that, but we prevent security problems by forcing a
rollback of any GUC change after completing the operation. Other cases are
handled by throwing an error if any change is attempted; these include temp
table creation, closing a cursor, and creating or deleting a prepared
statement. (In 7.4, the infrastructure to roll back GUC changes doesn't
exist, so we settle for rejecting changes of "search_path" in these contexts.)
Original report and patch by Gurjeet Singh, additional analysis by
Tom Lane.
Security: CVE-2009-4136
2009-12-09 22:57:51 +01:00
|
|
|
int save_sec_context;
|
2003-04-27 00:21:47 +02:00
|
|
|
|
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* 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
|
2003-04-27 00:21:47 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK)
|
2003-04-27 00:21:47 +02:00
|
|
|
query_rel = pk_rel;
|
|
|
|
else
|
|
|
|
query_rel = fk_rel;
|
|
|
|
|
|
|
|
/* Switch to proper UID to perform check as */
|
Prevent indirect security attacks via changing session-local state within
an allegedly immutable index function. It was previously recognized that
we had to prevent such a function from executing SET/RESET ROLE/SESSION
AUTHORIZATION, or it could trivially obtain the privileges of the session
user. However, since there is in general no privilege checking for changes
of session-local state, it is also possible for such a function to change
settings in a way that might subvert later operations in the same session.
Examples include changing search_path to cause an unexpected function to
be called, or replacing an existing prepared statement with another one
that will execute a function of the attacker's choosing.
The present patch secures VACUUM, ANALYZE, and CREATE INDEX/REINDEX against
these threats, which are the same places previously deemed to need protection
against the SET ROLE issue. GUC changes are still allowed, since there are
many useful cases for that, but we prevent security problems by forcing a
rollback of any GUC change after completing the operation. Other cases are
handled by throwing an error if any change is attempted; these include temp
table creation, closing a cursor, and creating or deleting a prepared
statement. (In 7.4, the infrastructure to roll back GUC changes doesn't
exist, so we settle for rejecting changes of "search_path" in these contexts.)
Original report and patch by Gurjeet Singh, additional analysis by
Tom Lane.
Security: CVE-2009-4136
2009-12-09 22:57:51 +01:00
|
|
|
GetUserIdAndSecContext(&save_userid, &save_sec_context);
|
|
|
|
SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
|
|
|
|
save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
|
2003-04-27 00:21:47 +02:00
|
|
|
|
|
|
|
/* Create the plan */
|
|
|
|
qplan = SPI_prepare(querystr, nargs, argtypes);
|
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
if (qplan == NULL)
|
|
|
|
elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr);
|
|
|
|
|
Prevent indirect security attacks via changing session-local state within
an allegedly immutable index function. It was previously recognized that
we had to prevent such a function from executing SET/RESET ROLE/SESSION
AUTHORIZATION, or it could trivially obtain the privileges of the session
user. However, since there is in general no privilege checking for changes
of session-local state, it is also possible for such a function to change
settings in a way that might subvert later operations in the same session.
Examples include changing search_path to cause an unexpected function to
be called, or replacing an existing prepared statement with another one
that will execute a function of the attacker's choosing.
The present patch secures VACUUM, ANALYZE, and CREATE INDEX/REINDEX against
these threats, which are the same places previously deemed to need protection
against the SET ROLE issue. GUC changes are still allowed, since there are
many useful cases for that, but we prevent security problems by forcing a
rollback of any GUC change after completing the operation. Other cases are
handled by throwing an error if any change is attempted; these include temp
table creation, closing a cursor, and creating or deleting a prepared
statement. (In 7.4, the infrastructure to roll back GUC changes doesn't
exist, so we settle for rejecting changes of "search_path" in these contexts.)
Original report and patch by Gurjeet Singh, additional analysis by
Tom Lane.
Security: CVE-2009-4136
2009-12-09 22:57:51 +01:00
|
|
|
/* Restore UID and security context */
|
|
|
|
SetUserIdAndSecContext(save_userid, save_sec_context);
|
2003-04-27 00:21:47 +02:00
|
|
|
|
|
|
|
/* Save the plan if requested */
|
|
|
|
if (cache_plan)
|
|
|
|
{
|
2011-09-16 06:42:53 +02:00
|
|
|
SPI_keepplan(qplan);
|
2003-04-27 00:21:47 +02:00
|
|
|
ri_HashPreparedPlan(qkey, qplan);
|
|
|
|
}
|
|
|
|
|
|
|
|
return qplan;
|
|
|
|
}
|
|
|
|
|
2003-03-15 22:19:40 +01:00
|
|
|
/*
|
|
|
|
* Perform a query to enforce an RI restriction
|
|
|
|
*/
|
|
|
|
static bool
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_PerformCheck(const RI_ConstraintInfo *riinfo,
|
|
|
|
RI_QueryKey *qkey, SPIPlanPtr qplan,
|
2003-03-15 22:19:40 +01:00
|
|
|
Relation fk_rel, Relation pk_rel,
|
|
|
|
HeapTuple old_tuple, HeapTuple new_tuple,
|
2012-06-19 00:50:03 +02:00
|
|
|
bool detectNewRows, int expect_OK)
|
2003-03-15 22:19:40 +01:00
|
|
|
{
|
|
|
|
Relation query_rel,
|
|
|
|
source_rel;
|
2012-06-19 00:50:03 +02:00
|
|
|
bool source_is_pk;
|
2004-09-13 22:10:13 +02:00
|
|
|
Snapshot test_snapshot;
|
|
|
|
Snapshot crosscheck_snapshot;
|
2003-03-15 22:19:40 +01:00
|
|
|
int limit;
|
|
|
|
int spi_result;
|
2008-01-03 22:23:15 +01:00
|
|
|
Oid save_userid;
|
Prevent indirect security attacks via changing session-local state within
an allegedly immutable index function. It was previously recognized that
we had to prevent such a function from executing SET/RESET ROLE/SESSION
AUTHORIZATION, or it could trivially obtain the privileges of the session
user. However, since there is in general no privilege checking for changes
of session-local state, it is also possible for such a function to change
settings in a way that might subvert later operations in the same session.
Examples include changing search_path to cause an unexpected function to
be called, or replacing an existing prepared statement with another one
that will execute a function of the attacker's choosing.
The present patch secures VACUUM, ANALYZE, and CREATE INDEX/REINDEX against
these threats, which are the same places previously deemed to need protection
against the SET ROLE issue. GUC changes are still allowed, since there are
many useful cases for that, but we prevent security problems by forcing a
rollback of any GUC change after completing the operation. Other cases are
handled by throwing an error if any change is attempted; these include temp
table creation, closing a cursor, and creating or deleting a prepared
statement. (In 7.4, the infrastructure to roll back GUC changes doesn't
exist, so we settle for rejecting changes of "search_path" in these contexts.)
Original report and patch by Gurjeet Singh, additional analysis by
Tom Lane.
Security: CVE-2009-4136
2009-12-09 22:57:51 +01:00
|
|
|
int save_sec_context;
|
2003-03-15 22:19:40 +01:00
|
|
|
Datum vals[RI_MAX_NUMKEYS * 2];
|
|
|
|
char nulls[RI_MAX_NUMKEYS * 2];
|
|
|
|
|
|
|
|
/*
|
2012-06-19 00:50:03 +02:00
|
|
|
* 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
|
2003-03-15 22:19:40 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK)
|
2003-03-15 22:19:40 +01:00
|
|
|
query_rel = pk_rel;
|
|
|
|
else
|
|
|
|
query_rel = fk_rel;
|
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* The values for the query are taken from the table on which the trigger
|
2012-06-19 00:50:03 +02:00
|
|
|
* 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.
|
2003-03-15 22:19:40 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK)
|
2003-03-15 22:19:40 +01:00
|
|
|
{
|
|
|
|
source_rel = fk_rel;
|
2012-06-19 00:50:03 +02:00
|
|
|
source_is_pk = false;
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
source_rel = pk_rel;
|
2012-06-19 00:50:03 +02:00
|
|
|
source_is_pk = true;
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Extract the parameters to be passed into the query */
|
|
|
|
if (new_tuple)
|
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ExtractValues(source_rel, new_tuple, riinfo, source_is_pk,
|
2003-03-15 22:19:40 +01:00
|
|
|
vals, nulls);
|
|
|
|
if (old_tuple)
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk,
|
|
|
|
vals + riinfo->nkeys, nulls + riinfo->nkeys);
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk,
|
2003-03-15 22:19:40 +01:00
|
|
|
vals, nulls);
|
|
|
|
}
|
|
|
|
|
2003-10-01 23:30:53 +02:00
|
|
|
/*
|
2004-09-13 22:10:13 +02:00
|
|
|
* In READ COMMITTED mode, we just need to use an up-to-date regular
|
2005-10-15 04:49:52 +02:00
|
|
|
* snapshot, and we will see all rows that could be interesting. But in
|
2011-04-10 17:42:00 +02:00
|
|
|
* transaction-snapshot mode, we can't change the transaction snapshot. If
|
|
|
|
* the caller passes detectNewRows == false then it's okay to do the query
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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
|
2008-05-12 22:02:02 +02:00
|
|
|
* 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.
|
2003-10-01 23:30:53 +02:00
|
|
|
*/
|
2010-09-11 20:38:58 +02:00
|
|
|
if (IsolationUsesXactSnapshot() && detectNewRows)
|
2004-09-13 22:10:13 +02:00
|
|
|
{
|
2005-10-15 04:49:52 +02:00
|
|
|
CommandCounterIncrement(); /* be sure all my own work is visible */
|
2008-05-12 22:02:02 +02:00
|
|
|
test_snapshot = GetLatestSnapshot();
|
|
|
|
crosscheck_snapshot = GetTransactionSnapshot();
|
2004-09-13 22:10:13 +02:00
|
|
|
}
|
2003-10-01 23:30:53 +02:00
|
|
|
else
|
|
|
|
{
|
2004-09-13 22:10:13 +02:00
|
|
|
/* the default SPI behavior is okay */
|
|
|
|
test_snapshot = InvalidSnapshot;
|
|
|
|
crosscheck_snapshot = InvalidSnapshot;
|
2003-10-01 23:30:53 +02:00
|
|
|
}
|
2003-03-15 22:19:40 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is a select query (e.g., for a 'no action' or 'restrict'
|
2005-10-15 04:49:52 +02:00
|
|
|
* 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.
|
2003-03-15 22:19:40 +01:00
|
|
|
*/
|
|
|
|
limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0;
|
|
|
|
|
2003-10-01 23:30:53 +02:00
|
|
|
/* Switch to proper UID to perform check as */
|
Prevent indirect security attacks via changing session-local state within
an allegedly immutable index function. It was previously recognized that
we had to prevent such a function from executing SET/RESET ROLE/SESSION
AUTHORIZATION, or it could trivially obtain the privileges of the session
user. However, since there is in general no privilege checking for changes
of session-local state, it is also possible for such a function to change
settings in a way that might subvert later operations in the same session.
Examples include changing search_path to cause an unexpected function to
be called, or replacing an existing prepared statement with another one
that will execute a function of the attacker's choosing.
The present patch secures VACUUM, ANALYZE, and CREATE INDEX/REINDEX against
these threats, which are the same places previously deemed to need protection
against the SET ROLE issue. GUC changes are still allowed, since there are
many useful cases for that, but we prevent security problems by forcing a
rollback of any GUC change after completing the operation. Other cases are
handled by throwing an error if any change is attempted; these include temp
table creation, closing a cursor, and creating or deleting a prepared
statement. (In 7.4, the infrastructure to roll back GUC changes doesn't
exist, so we settle for rejecting changes of "search_path" in these contexts.)
Original report and patch by Gurjeet Singh, additional analysis by
Tom Lane.
Security: CVE-2009-4136
2009-12-09 22:57:51 +01:00
|
|
|
GetUserIdAndSecContext(&save_userid, &save_sec_context);
|
|
|
|
SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
|
|
|
|
save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
|
2003-10-01 23:30:53 +02:00
|
|
|
|
|
|
|
/* Finally we can run the query. */
|
2004-09-13 22:10:13 +02:00
|
|
|
spi_result = SPI_execute_snapshot(qplan,
|
|
|
|
vals, nulls,
|
|
|
|
test_snapshot, crosscheck_snapshot,
|
2007-08-15 21:15:47 +02:00
|
|
|
false, false, limit);
|
2003-03-15 22:19:40 +01:00
|
|
|
|
Prevent indirect security attacks via changing session-local state within
an allegedly immutable index function. It was previously recognized that
we had to prevent such a function from executing SET/RESET ROLE/SESSION
AUTHORIZATION, or it could trivially obtain the privileges of the session
user. However, since there is in general no privilege checking for changes
of session-local state, it is also possible for such a function to change
settings in a way that might subvert later operations in the same session.
Examples include changing search_path to cause an unexpected function to
be called, or replacing an existing prepared statement with another one
that will execute a function of the attacker's choosing.
The present patch secures VACUUM, ANALYZE, and CREATE INDEX/REINDEX against
these threats, which are the same places previously deemed to need protection
against the SET ROLE issue. GUC changes are still allowed, since there are
many useful cases for that, but we prevent security problems by forcing a
rollback of any GUC change after completing the operation. Other cases are
handled by throwing an error if any change is attempted; these include temp
table creation, closing a cursor, and creating or deleting a prepared
statement. (In 7.4, the infrastructure to roll back GUC changes doesn't
exist, so we settle for rejecting changes of "search_path" in these contexts.)
Original report and patch by Gurjeet Singh, additional analysis by
Tom Lane.
Security: CVE-2009-4136
2009-12-09 22:57:51 +01:00
|
|
|
/* Restore UID and security context */
|
|
|
|
SetUserIdAndSecContext(save_userid, save_sec_context);
|
2003-03-15 22:19:40 +01:00
|
|
|
|
|
|
|
/* Check result */
|
|
|
|
if (spi_result < 0)
|
2004-09-13 22:10:13 +02:00
|
|
|
elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
|
2003-03-15 22:19:40 +01:00
|
|
|
|
|
|
|
if (expect_OK >= 0 && spi_result != expect_OK)
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ReportViolation(riinfo,
|
2003-03-15 22:19:40 +01:00
|
|
|
pk_rel, fk_rel,
|
|
|
|
new_tuple ? new_tuple : old_tuple,
|
2003-10-06 18:38:28 +02:00
|
|
|
NULL,
|
2012-06-19 00:50:03 +02:00
|
|
|
qkey->constr_queryno, true);
|
2003-03-15 22:19:40 +01:00
|
|
|
|
|
|
|
/* XXX wouldn't it be clearer to do this part at the caller? */
|
2012-06-19 00:50:03 +02:00
|
|
|
if (qkey->constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK &&
|
|
|
|
expect_OK == SPI_OK_SELECT &&
|
2005-10-15 04:49:52 +02:00
|
|
|
(SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK))
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ReportViolation(riinfo,
|
2003-03-15 22:19:40 +01:00
|
|
|
pk_rel, fk_rel,
|
|
|
|
new_tuple ? new_tuple : old_tuple,
|
2003-10-06 18:38:28 +02:00
|
|
|
NULL,
|
2012-06-19 00:50:03 +02:00
|
|
|
qkey->constr_queryno, false);
|
2003-03-15 22:19:40 +01:00
|
|
|
|
|
|
|
return SPI_processed != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extract fields from a tuple into Datum/nulls arrays
|
|
|
|
*/
|
|
|
|
static void
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ExtractValues(Relation rel, HeapTuple tup,
|
|
|
|
const RI_ConstraintInfo *riinfo, bool rel_is_pk,
|
2003-03-15 22:19:40 +01:00
|
|
|
Datum *vals, char *nulls)
|
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
TupleDesc tupdesc = rel->rd_att;
|
|
|
|
const int16 *attnums;
|
2003-03-15 22:19:40 +01:00
|
|
|
int i;
|
|
|
|
bool isnull;
|
|
|
|
|
2012-06-19 00:50:03 +02:00
|
|
|
if (rel_is_pk)
|
|
|
|
attnums = riinfo->pk_attnums;
|
|
|
|
else
|
|
|
|
attnums = riinfo->fk_attnums;
|
|
|
|
|
|
|
|
for (i = 0; i < riinfo->nkeys; i++)
|
2003-03-15 22:19:40 +01:00
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
vals[i] = heap_getattr(tup, attnums[i], tupdesc,
|
|
|
|
&isnull);
|
2003-03-15 22:19:40 +01:00
|
|
|
nulls[i] = isnull ? 'n' : ' ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Produce an error report
|
|
|
|
*
|
|
|
|
* If the failed constraint was on insert/update to the FK table,
|
|
|
|
* we want the key names and values extracted from there, and the error
|
2003-07-23 00:14:57 +02:00
|
|
|
* message to look like 'key blah is not present in PK'.
|
2003-03-15 22:19:40 +01:00
|
|
|
* Otherwise, the attr names and values come from the PK table and the
|
2003-07-23 00:14:57 +02:00
|
|
|
* message looks like 'key blah is still referenced from FK'.
|
2003-03-15 22:19:40 +01:00
|
|
|
*/
|
|
|
|
static void
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_ReportViolation(const RI_ConstraintInfo *riinfo,
|
2003-03-15 22:19:40 +01:00
|
|
|
Relation pk_rel, Relation fk_rel,
|
2003-10-06 18:38:28 +02:00
|
|
|
HeapTuple violator, TupleDesc tupdesc,
|
2012-06-19 00:50:03 +02:00
|
|
|
int queryno, bool spi_err)
|
2003-03-15 22:19:40 +01:00
|
|
|
{
|
2009-08-01 21:59:41 +02:00
|
|
|
StringInfoData key_names;
|
|
|
|
StringInfoData key_values;
|
2003-03-15 22:19:40 +01:00
|
|
|
bool onfk;
|
2012-06-19 00:50:03 +02:00
|
|
|
const int16 *attnums;
|
|
|
|
int idx;
|
2003-03-15 22:19:40 +01:00
|
|
|
|
2003-07-23 00:14:57 +02:00
|
|
|
if (spi_err)
|
|
|
|
ereport(ERROR,
|
2003-09-10 01:22:21 +02:00
|
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
2003-07-23 00:14:57 +02:00
|
|
|
errmsg("referential integrity query on \"%s\" from constraint \"%s\" on \"%s\" gave unexpected result",
|
|
|
|
RelationGetRelationName(pk_rel),
|
2012-06-19 00:50:03 +02:00
|
|
|
NameStr(riinfo->conname),
|
2003-07-23 00:14:57 +02:00
|
|
|
RelationGetRelationName(fk_rel)),
|
|
|
|
errhint("This is most likely due to a rule having rewritten the query.")));
|
|
|
|
|
2003-03-15 22:19:40 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Determine which relation to complain about. If tupdesc wasn't passed
|
|
|
|
* by caller, assume the violator tuple came from there.
|
2003-03-15 22:19:40 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
onfk = (queryno == RI_PLAN_CHECK_LOOKUPPK);
|
2003-03-15 22:19:40 +01:00
|
|
|
if (onfk)
|
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
attnums = riinfo->fk_attnums;
|
2003-10-06 18:38:28 +02:00
|
|
|
if (tupdesc == NULL)
|
|
|
|
tupdesc = fk_rel->rd_att;
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
attnums = riinfo->pk_attnums;
|
2003-10-06 18:38:28 +02:00
|
|
|
if (tupdesc == NULL)
|
|
|
|
tupdesc = pk_rel->rd_att;
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Special case - if there are no keys at all, this is a 'no column'
|
2005-10-15 04:49:52 +02:00
|
|
|
* constraint - no need to try to extract the values, and the message in
|
|
|
|
* this case looks different.
|
2003-03-15 22:19:40 +01:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
if (riinfo->nkeys == 0)
|
2003-03-15 22:19:40 +01:00
|
|
|
{
|
2003-07-23 00:14:57 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
|
2012-06-19 00:50:03 +02:00
|
|
|
RelationGetRelationName(fk_rel),
|
|
|
|
NameStr(riinfo->conname)),
|
2003-07-23 00:14:57 +02:00
|
|
|
errdetail("No rows were found in \"%s\".",
|
|
|
|
RelationGetRelationName(pk_rel))));
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Get printable versions of the keys involved */
|
2009-08-01 21:59:41 +02:00
|
|
|
initStringInfo(&key_names);
|
|
|
|
initStringInfo(&key_values);
|
2012-06-19 00:50:03 +02:00
|
|
|
for (idx = 0; idx < riinfo->nkeys; idx++)
|
2003-03-15 22:19:40 +01:00
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
int fnum = attnums[idx];
|
2003-08-04 02:43:34 +02:00
|
|
|
char *name,
|
|
|
|
*val;
|
2003-03-15 22:19:40 +01:00
|
|
|
|
2003-10-06 18:38:28 +02:00
|
|
|
name = SPI_fname(tupdesc, fnum);
|
|
|
|
val = SPI_getvalue(violator, tupdesc, fnum);
|
2003-03-15 22:19:40 +01:00
|
|
|
if (!val)
|
|
|
|
val = "null";
|
|
|
|
|
2009-08-01 21:59:41 +02:00
|
|
|
if (idx > 0)
|
2003-03-15 22:19:40 +01:00
|
|
|
{
|
2009-08-01 21:59:41 +02:00
|
|
|
appendStringInfoString(&key_names, ", ");
|
|
|
|
appendStringInfoString(&key_values, ", ");
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
2009-08-01 21:59:41 +02:00
|
|
|
appendStringInfoString(&key_names, name);
|
|
|
|
appendStringInfoString(&key_values, val);
|
2003-07-23 00:14:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (onfk)
|
2003-08-04 02:43:34 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
|
2003-09-25 08:58:07 +02:00
|
|
|
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
|
2012-06-19 00:50:03 +02:00
|
|
|
RelationGetRelationName(fk_rel),
|
|
|
|
NameStr(riinfo->conname)),
|
2005-10-15 04:49:52 +02:00
|
|
|
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
|
2009-08-01 21:59:41 +02:00
|
|
|
key_names.data, key_values.data,
|
2005-10-15 04:49:52 +02:00
|
|
|
RelationGetRelationName(pk_rel))));
|
2003-07-23 00:14:57 +02:00
|
|
|
else
|
2003-08-04 02:43:34 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
|
2005-12-28 17:47:21 +01:00
|
|
|
errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"",
|
2003-08-04 02:43:34 +02:00
|
|
|
RelationGetRelationName(pk_rel),
|
2012-06-19 00:50:03 +02:00
|
|
|
NameStr(riinfo->conname),
|
|
|
|
RelationGetRelationName(fk_rel)),
|
2005-10-15 04:49:52 +02:00
|
|
|
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
|
2009-08-01 21:59:41 +02:00
|
|
|
key_names.data, key_values.data,
|
2005-10-15 04:49:52 +02:00
|
|
|
RelationGetRelationName(fk_rel))));
|
2003-03-15 22:19:40 +01:00
|
|
|
}
|
|
|
|
|
1999-10-08 14:00:08 +02:00
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* ri_NullCheck -
|
|
|
|
*
|
|
|
|
* Determine the NULL state of all key values in a tuple
|
|
|
|
*
|
|
|
|
* Returns one of RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL or RI_KEYS_SOME_NULL.
|
|
|
|
* ----------
|
|
|
|
*/
|
2000-04-12 19:17:23 +02:00
|
|
|
static int
|
2012-06-19 00:50:03 +02:00
|
|
|
ri_NullCheck(HeapTuple tup,
|
|
|
|
const RI_ConstraintInfo *riinfo, bool rel_is_pk)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
const int16 *attnums;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
|
|
|
bool allnull = true;
|
|
|
|
bool nonenull = true;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2012-06-19 00:50:03 +02:00
|
|
|
if (rel_is_pk)
|
|
|
|
attnums = riinfo->pk_attnums;
|
|
|
|
else
|
|
|
|
attnums = riinfo->fk_attnums;
|
|
|
|
|
|
|
|
for (i = 0; i < riinfo->nkeys; i++)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2012-06-19 00:50:03 +02:00
|
|
|
if (heap_attisnull(tup, attnums[i]))
|
1999-10-08 14:00:08 +02:00
|
|
|
nonenull = false;
|
|
|
|
else
|
|
|
|
allnull = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (allnull)
|
|
|
|
return RI_KEYS_ALL_NULL;
|
|
|
|
|
|
|
|
if (nonenull)
|
|
|
|
return RI_KEYS_NONE_NULL;
|
|
|
|
|
|
|
|
return RI_KEYS_SOME_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* ri_InitHashTables -
|
|
|
|
*
|
2007-02-14 02:58:58 +01:00
|
|
|
* Initialize our internal hash tables for prepared
|
|
|
|
* query plans and comparison operators.
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ri_InitHashTables(void)
|
|
|
|
{
|
|
|
|
HASHCTL ctl;
|
|
|
|
|
|
|
|
memset(&ctl, 0, sizeof(ctl));
|
2000-04-12 19:17:23 +02:00
|
|
|
ctl.keysize = sizeof(RI_QueryKey);
|
2001-10-01 07:36:17 +02:00
|
|
|
ctl.entrysize = sizeof(RI_QueryHashEntry);
|
|
|
|
ctl.hash = tag_hash;
|
2001-10-05 19:28:13 +02:00
|
|
|
ri_query_cache = hash_create("RI query cache", RI_INIT_QUERYHASHSIZE,
|
|
|
|
&ctl, HASH_ELEM | HASH_FUNCTION);
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
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);
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* ri_FetchPreparedPlan -
|
|
|
|
*
|
|
|
|
* Lookup for a query key in our private hash table of prepared
|
|
|
|
* and saved SPI execution plans. Return the plan if found or NULL.
|
|
|
|
* ----------
|
|
|
|
*/
|
2007-03-16 00:12:07 +01:00
|
|
|
static SPIPlanPtr
|
1999-10-08 14:00:08 +02:00
|
|
|
ri_FetchPreparedPlan(RI_QueryKey *key)
|
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
RI_QueryHashEntry *entry;
|
2009-06-11 16:49:15 +02:00
|
|
|
SPIPlanPtr plan;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* On the first call initialize the hashtable
|
|
|
|
*/
|
|
|
|
if (!ri_query_cache)
|
|
|
|
ri_InitHashTables();
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* Lookup for the key
|
|
|
|
*/
|
2000-04-12 19:17:23 +02:00
|
|
|
entry = (RI_QueryHashEntry *) hash_search(ri_query_cache,
|
2001-10-01 07:36:17 +02:00
|
|
|
(void *) key,
|
2001-10-05 19:28:13 +02:00
|
|
|
HASH_FIND, NULL);
|
1999-10-08 14:00:08 +02:00
|
|
|
if (entry == NULL)
|
|
|
|
return NULL;
|
2008-09-16 01:37:40 +02:00
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* 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.
|
2008-09-16 01:37:40 +02:00
|
|
|
*
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* Otherwise we might as well flush the cached plan now, to free a little
|
|
|
|
* memory space before we make a new one.
|
2008-09-16 01:37:40 +02:00
|
|
|
*/
|
|
|
|
entry->plan = NULL;
|
|
|
|
if (plan)
|
|
|
|
SPI_freeplan(plan);
|
|
|
|
|
|
|
|
return NULL;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* ri_HashPreparedPlan -
|
|
|
|
*
|
|
|
|
* Add another plan to our private SPI query plan hashtable.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static void
|
2007-03-16 00:12:07 +01:00
|
|
|
ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
RI_QueryHashEntry *entry;
|
|
|
|
bool found;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1999-10-08 14:00:08 +02:00
|
|
|
* On the first call initialize the hashtable
|
|
|
|
*/
|
|
|
|
if (!ri_query_cache)
|
|
|
|
ri_InitHashTables();
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2009-06-11 16:49:15 +02:00
|
|
|
* Add the new plan. We might be overwriting an entry previously found
|
|
|
|
* invalid by ri_FetchPreparedPlan.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2000-04-12 19:17:23 +02:00
|
|
|
entry = (RI_QueryHashEntry *) hash_search(ri_query_cache,
|
2001-10-01 07:36:17 +02:00
|
|
|
(void *) key,
|
|
|
|
HASH_ENTER, &found);
|
2008-09-16 01:37:40 +02:00
|
|
|
Assert(!found || entry->plan == NULL);
|
1999-10-08 14:00:08 +02:00
|
|
|
entry->plan = plan;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* ri_KeysEqual -
|
|
|
|
*
|
|
|
|
* Check if all key values in OLD and NEW are equal.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static bool
|
2000-04-12 19:17:23 +02:00
|
|
|
ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
|
2007-11-15 23:25:18 +01:00
|
|
|
const RI_ConstraintInfo *riinfo, bool rel_is_pk)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
TupleDesc tupdesc = RelationGetDescr(rel);
|
|
|
|
const int16 *attnums;
|
|
|
|
const Oid *eq_oprs;
|
2000-04-12 19:17:23 +02:00
|
|
|
int i;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
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++)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
Datum oldvalue;
|
|
|
|
Datum newvalue;
|
|
|
|
bool isnull;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2005-05-30 09:20:59 +02:00
|
|
|
* Get one attribute's oldvalue. If it is NULL - they're not equal.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
oldvalue = heap_getattr(oldtup, attnums[i], tupdesc, &isnull);
|
1999-10-08 14:00:08 +02:00
|
|
|
if (isnull)
|
|
|
|
return false;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2007-02-14 02:58:58 +01:00
|
|
|
* Get one attribute's newvalue. If it is NULL - they're not equal.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2012-06-19 00:50:03 +02:00
|
|
|
newvalue = heap_getattr(newtup, attnums[i], tupdesc, &isnull);
|
1999-10-08 14:00:08 +02:00
|
|
|
if (isnull)
|
|
|
|
return false;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2007-02-14 02:58:58 +01:00
|
|
|
* Compare them with the appropriate equality operator.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (!ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]),
|
|
|
|
oldvalue, newvalue))
|
1999-10-08 14:00:08 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* ri_AttributesEqual -
|
|
|
|
*
|
2007-02-14 02:58:58 +01:00
|
|
|
* Call the appropriate equality comparison operator for two values.
|
2000-05-29 03:59:17 +02:00
|
|
|
*
|
|
|
|
* NB: we have already checked that neither value is null.
|
1999-10-08 14:00:08 +02:00
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static bool
|
2007-02-14 02:58:58 +01:00
|
|
|
ri_AttributesEqual(Oid eq_opr, Oid typeid,
|
|
|
|
Datum oldvalue, Datum newvalue)
|
|
|
|
{
|
|
|
|
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 */
|
|
|
|
}
|
|
|
|
|
2011-04-13 01:19:24 +02:00
|
|
|
/*
|
2011-06-09 20:32:50 +02:00
|
|
|
* Apply the comparison operator. We assume it doesn't care about
|
|
|
|
* collations.
|
2011-04-13 01:19:24 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
return DatumGetBool(FunctionCall2(&entry->eq_opr_finfo,
|
|
|
|
oldvalue, newvalue));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------
|
|
|
|
* ri_HashCompareOp -
|
|
|
|
*
|
|
|
|
* See if we know how to compare two values, and create a new hash entry
|
|
|
|
* if not.
|
|
|
|
* ----------
|
|
|
|
*/
|
|
|
|
static RI_CompareHashEntry *
|
|
|
|
ri_HashCompareOp(Oid eq_opr, Oid typeid)
|
1999-10-08 14:00:08 +02:00
|
|
|
{
|
2007-02-14 02:58:58 +01:00
|
|
|
RI_CompareKey key;
|
|
|
|
RI_CompareHashEntry *entry;
|
|
|
|
bool found;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2007-02-14 02:58:58 +01:00
|
|
|
* On the first call initialize the hashtable
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (!ri_compare_cache)
|
|
|
|
ri_InitHashTables();
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
1999-10-08 14:00:08 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2007-02-14 02:58:58 +01:00
|
|
|
* 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.
|
1999-10-08 14:00:08 +02:00
|
|
|
*/
|
2007-02-14 02:58:58 +01:00
|
|
|
if (!entry->valid)
|
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
Oid lefttype,
|
|
|
|
righttype,
|
|
|
|
castfunc;
|
2007-06-05 23:31:09 +02:00
|
|
|
CoercionPathType pathtype;
|
2007-02-14 02:58:58 +01:00
|
|
|
|
|
|
|
/* We always need to know how to call the equality operator */
|
|
|
|
fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo,
|
|
|
|
TopMemoryContext);
|
|
|
|
|
|
|
|
/*
|
2007-11-15 22:14:46 +01:00
|
|
|
* 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.
|
2007-03-28 01:21:12 +02:00
|
|
|
*
|
|
|
|
* XXX eventually it would be good to support array-coercion cases
|
2007-11-15 22:14:46 +01:00
|
|
|
* 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.
|
2007-06-05 23:31:09 +02:00
|
|
|
*
|
|
|
|
* XXX perhaps also consider supporting CoerceViaIO? No need at the
|
|
|
|
* moment since that will never be generated for implicit coercions.
|
2007-02-14 02:58:58 +01:00
|
|
|
*/
|
|
|
|
op_input_types(eq_opr, &lefttype, &righttype);
|
|
|
|
Assert(lefttype == righttype);
|
|
|
|
if (typeid == lefttype)
|
2007-11-15 22:14:46 +01:00
|
|
|
castfunc = InvalidOid; /* simplest case */
|
2007-06-05 23:31:09 +02:00
|
|
|
else
|
2007-02-14 02:58:58 +01:00
|
|
|
{
|
2007-06-05 23:31:09 +02:00
|
|
|
pathtype = find_coercion_pathway(lefttype, typeid,
|
|
|
|
COERCION_IMPLICIT,
|
|
|
|
&castfunc);
|
|
|
|
if (pathtype != COERCION_PATH_FUNC &&
|
|
|
|
pathtype != COERCION_PATH_RELABELTYPE)
|
|
|
|
{
|
2008-05-19 06:14:24 +02:00
|
|
|
/*
|
|
|
|
* The declared input type of the eq_opr might be a
|
2009-11-05 05:38:29 +01:00
|
|
|
* polymorphic type such as ANYARRAY or ANYENUM, or other
|
|
|
|
* special cases such as RECORD; find_coercion_pathway
|
|
|
|
* currently doesn't subsume these special cases.
|
2008-05-19 06:14:24 +02:00
|
|
|
*/
|
2009-11-05 05:38:29 +01:00
|
|
|
if (!IsPolymorphicType(lefttype) &&
|
|
|
|
!IsBinaryCoercible(typeid, lefttype))
|
2007-06-05 23:31:09 +02:00
|
|
|
elog(ERROR, "no conversion function from %s to %s",
|
|
|
|
format_type_be(typeid),
|
|
|
|
format_type_be(lefttype));
|
|
|
|
}
|
2007-02-14 02:58:58 +01:00
|
|
|
}
|
|
|
|
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;
|
1999-10-08 14:00:08 +02:00
|
|
|
}
|
2005-05-30 09:20:59 +02:00
|
|
|
|
2007-02-14 02:58:58 +01:00
|
|
|
|
2005-05-30 09:20:59 +02:00
|
|
|
/*
|
|
|
|
* Given a trigger function OID, determine whether it is an RI trigger,
|
|
|
|
* and if so whether it is attached to PK or FK relation.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
RI_FKey_trigger_type(Oid tgfoid)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|