Commit adfeef55 authored by Neil Conway's avatar Neil Conway

When enqueueing after-row triggers for updates of a table with a foreign

key, compare the new and old row versions. If the foreign key column has
not changed, we needn't enqueue the trigger, since the update cannot
violate the foreign key. This optimization was previously applied in the
RI trigger function, but it is more efficient to avoid firing the trigger
altogether. Per recent discussion on pgsql-hackers.

Also add a regression test for some unintuitive foreign key behavior, and
refactor some code that deals with the OIDs of the various RI trigger
functions.
parent f99b75b0
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.158 2005/05/30 06:52:38 neilc Exp $ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.159 2005/05/30 07:20:58 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -156,12 +156,6 @@ typedef struct NewColumnValue ...@@ -156,12 +156,6 @@ typedef struct NewColumnValue
} NewColumnValue; } NewColumnValue;
/* Used by attribute and relation renaming routines: */
#define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */
#define RI_TRIGGER_FK 2 /* is a trigger on the FK relation */
#define RI_TRIGGER_NONE 0 /* is not an RI trigger function */
static List *MergeAttributes(List *schema, List *supers, bool istemp, static List *MergeAttributes(List *schema, List *supers, bool istemp,
List **supOids, List **supconstr, int *supOidCount); List **supOids, List **supconstr, int *supOidCount);
static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno); static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno);
...@@ -246,7 +240,6 @@ static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, ...@@ -246,7 +240,6 @@ static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename); char *tablespacename);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace); static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace);
static void copy_relation_data(Relation rel, SMgrRelation dst); static void copy_relation_data(Relation rel, SMgrRelation dst);
static int ri_trigger_type(Oid tgfoid);
static void update_ri_trigger_args(Oid relid, static void update_ri_trigger_args(Oid relid,
const char *oldname, const char *oldname,
const char *newname, const char *newname,
...@@ -1571,39 +1564,6 @@ renamerel(Oid myrelid, const char *newrelname) ...@@ -1571,39 +1564,6 @@ renamerel(Oid myrelid, const char *newrelname)
relation_close(targetrelation, NoLock); relation_close(targetrelation, NoLock);
} }
/*
* Given a trigger function OID, determine whether it is an RI trigger,
* and if so whether it is attached to PK or FK relation.
*
* XXX this probably doesn't belong here; should be exported by
* ri_triggers.c
*/
static int
ri_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;
}
/* /*
* Scan pg_trigger for RI triggers that are on the specified relation * Scan pg_trigger for RI triggers that are on the specified relation
* (if fk_scan is false) or have it as the tgconstrrel (if fk_scan * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
...@@ -1663,7 +1623,7 @@ update_ri_trigger_args(Oid relid, ...@@ -1663,7 +1623,7 @@ update_ri_trigger_args(Oid relid,
const char *arga[RI_MAX_ARGUMENTS]; const char *arga[RI_MAX_ARGUMENTS];
const char *argp; const char *argp;
tg_type = ri_trigger_type(pg_trigger->tgfoid); tg_type = RI_FKey_trigger_type(pg_trigger->tgfoid);
if (tg_type == RI_TRIGGER_NONE) if (tg_type == RI_TRIGGER_NONE)
{ {
/* Not an RI trigger, forget it */ /* Not an RI trigger, forget it */
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.188 2005/05/06 17:24:53 tgl Exp $ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.189 2005/05/30 07:20:58 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -2994,54 +2994,47 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, ...@@ -2994,54 +2994,47 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
continue; continue;
/* /*
* If it is an RI UPDATE trigger, and the referenced keys have * If this is an UPDATE of a PK table or FK table that does
* not changed, short-circuit queuing of the event; there's no * not change the PK or FK respectively, we can skip queuing
* need to fire the trigger. * the event: there is no need to fire the trigger.
*/ */
if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE) if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
{ {
bool is_ri_trigger; switch (RI_FKey_trigger_type(trigger->tgfoid))
switch (trigger->tgfoid)
{ {
case F_RI_FKEY_NOACTION_UPD: case RI_TRIGGER_PK:
case F_RI_FKEY_CASCADE_UPD: /* Update on PK table */
case F_RI_FKEY_RESTRICT_UPD: if (RI_FKey_keyequal_upd_pk(trigger, rel, oldtup, newtup))
case F_RI_FKEY_SETNULL_UPD: {
case F_RI_FKEY_SETDEFAULT_UPD: /* key unchanged, so skip queuing this event */
is_ri_trigger = true; continue;
}
break; break;
default: case RI_TRIGGER_FK:
is_ri_trigger = false; /*
* Update on FK table
*
* There is one exception when updating FK tables:
* if the updated row was inserted by our own
* transaction and the FK is deferred, we still
* need to fire the trigger. This is because our
* UPDATE will invalidate the INSERT so the
* end-of-transaction INSERT RI trigger will not
* do anything, so we have to do the check for the
* UPDATE anyway.
*/
if (HeapTupleHeaderGetXmin(oldtup->t_data) !=
GetCurrentTransactionId() &&
RI_FKey_keyequal_upd_fk(trigger, rel, oldtup, newtup))
{
continue;
}
break; break;
}
if (is_ri_trigger) case RI_TRIGGER_NONE:
{ /* Not an FK trigger */
TriggerData LocTriggerData; break;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event =
TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW;
LocTriggerData.tg_relation = rel;
LocTriggerData.tg_trigtuple = oldtup;
LocTriggerData.tg_newtuple = newtup;
LocTriggerData.tg_trigger = trigger;
/*
* We do not currently know which buffers the passed tuples
* are in, but it does not matter because RI_FKey_keyequal_upd
* does not care. We could expand the API of this function
* if it becomes necessary to set these fields accurately.
*/
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
if (RI_FKey_keyequal_upd(&LocTriggerData))
{
/* key unchanged, so skip queuing this event */
continue;
}
} }
} }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
* *
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* *
* $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.78 2005/05/29 04:23:05 tgl Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.79 2005/05/30 07:20:58 neilc Exp $
* *
* ---------- * ----------
*/ */
...@@ -38,10 +38,11 @@ ...@@ -38,10 +38,11 @@
#include "optimizer/planmain.h" #include "optimizer/planmain.h"
#include "parser/parse_oper.h" #include "parser/parse_oper.h"
#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteHandler.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
#include "utils/acl.h" #include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h" #include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
#include "miscadmin.h" #include "miscadmin.h"
...@@ -375,22 +376,6 @@ RI_FKey_check(PG_FUNCTION_ARGS) ...@@ -375,22 +376,6 @@ RI_FKey_check(PG_FUNCTION_ARGS)
break; break;
} }
/*
* No need to check anything if old and new references are the same on
* UPDATE.
*/
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
if (HeapTupleHeaderGetXmin(old_row->t_data) !=
GetCurrentTransactionId() &&
ri_KeysEqual(fk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_FK_IDX))
{
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
}
}
if (SPI_connect() != SPI_OK_CONNECT) if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "SPI_connect failed"); elog(ERROR, "SPI_connect failed");
...@@ -2005,8 +1990,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ...@@ -2005,8 +1990,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
* corresponding to changed columns in pk_rel's key * corresponding to changed columns in pk_rel's key
*/ */
if (match_type == RI_MATCH_TYPE_FULL || if (match_type == RI_MATCH_TYPE_FULL ||
!ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey, !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX)) RI_KEYPAIR_PK_IDX))
{ {
snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL", snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
querysep, attname); querysep, attname);
...@@ -2016,7 +2001,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) ...@@ -2016,7 +2001,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
qualsep, attname, i + 1); qualsep, attname, i + 1);
qualsep = "AND"; qualsep = "AND";
queryoids[i] = SPI_gettypeid(pk_rel->rd_att, queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
qkey.keypair[i][RI_KEYPAIR_PK_IDX]); qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
} }
strcat(querystr, qualstr); strcat(querystr, qualstr);
...@@ -2451,30 +2436,27 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) ...@@ -2451,30 +2436,27 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
/* ---------- /* ----------
* RI_FKey_keyequal_upd - * RI_FKey_keyequal_upd_pk -
*
* Check if we have a key change on update.
* *
* This is not a real trigger procedure. It is used by the AFTER * Check if we have a key change on an update to a PK relation. This is
* trigger queue manager to detect "triggered data change violation". * used by the AFTER trigger queue manager to detect "triggered data
* change violation".
* ---------- * ----------
*/ */
bool bool
RI_FKey_keyequal_upd(TriggerData *trigdata) RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel,
HeapTuple old_row, HeapTuple new_row)
{ {
int tgnargs; int tgnargs;
char **tgargs; char **tgargs;
Relation fk_rel; Relation fk_rel;
Relation pk_rel;
HeapTuple new_row;
HeapTuple old_row;
RI_QueryKey qkey; RI_QueryKey qkey;
/* /*
* Check for the correct # of call arguments * Check for the correct # of call arguments
*/ */
tgnargs = trigdata->tg_trigger->tgnargs; tgnargs = trigger->tgnargs;
tgargs = trigdata->tg_trigger->tgargs; tgargs = trigger->tgargs;
if (tgnargs < 4 || if (tgnargs < 4 ||
tgnargs > RI_MAX_ARGUMENTS || tgnargs > RI_MAX_ARGUMENTS ||
(tgnargs % 2) != 0) (tgnargs % 2) != 0)
...@@ -2489,48 +2471,32 @@ RI_FKey_keyequal_upd(TriggerData *trigdata) ...@@ -2489,48 +2471,32 @@ RI_FKey_keyequal_upd(TriggerData *trigdata)
if (tgnargs == 4) if (tgnargs == 4)
return true; return true;
/* if (!OidIsValid(trigger->tgconstrrelid))
* Get the relation descriptors of the FK and PK tables and the new
* and old tuple.
*
* Use minimal locking for fk_rel here.
*/
if (!OidIsValid(trigdata->tg_trigger->tgconstrrelid))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("no target table given for trigger \"%s\" on table \"%s\"", errmsg("no target table given for trigger \"%s\" on table \"%s\"",
trigdata->tg_trigger->tgname, trigger->tgname,
RelationGetRelationName(trigdata->tg_relation)), RelationGetRelationName(pk_rel)),
errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT."))); errhint("Remove this referential integrity trigger and its mates, "
"then do ALTER TABLE ADD CONSTRAINT.")));
fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, AccessShareLock); fk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock);
pk_rel = trigdata->tg_relation;
new_row = trigdata->tg_newtuple;
old_row = trigdata->tg_trigtuple;
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{ {
/*
* MATCH <UNSPECIFIED>
*/
case RI_MATCH_TYPE_UNSPECIFIED: case RI_MATCH_TYPE_UNSPECIFIED:
case RI_MATCH_TYPE_FULL: case RI_MATCH_TYPE_FULL:
ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, ri_BuildQueryKeyFull(&qkey, trigger->tgoid,
RI_PLAN_KEYEQUAL_UPD, RI_PLAN_KEYEQUAL_UPD,
fk_rel, pk_rel, fk_rel, pk_rel,
tgnargs, tgargs); tgnargs, tgargs);
heap_close(fk_rel, AccessShareLock); heap_close(fk_rel, AccessShareLock);
/* /* Return if key's are equal */
* Return if key's are equal
*/
return ri_KeysEqual(pk_rel, old_row, new_row, &qkey, return ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_PK_IDX); RI_KEYPAIR_PK_IDX);
/* /* Handle MATCH PARTIAL set null delete. */
* Handle MATCH PARTIAL set null delete.
*/
case RI_MATCH_TYPE_PARTIAL: case RI_MATCH_TYPE_PARTIAL:
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
...@@ -2538,14 +2504,85 @@ RI_FKey_keyequal_upd(TriggerData *trigdata) ...@@ -2538,14 +2504,85 @@ RI_FKey_keyequal_upd(TriggerData *trigdata)
break; break;
} }
/* Never reached */
elog(ERROR, "invalid match_type");
return false;
}
/* ----------
* RI_FKey_keyequal_upd_fk -
*
* Check if we have a key change on an update to an FK relation. This is
* used by the AFTER trigger queue manager to detect "triggered data
* change violation".
* ----------
*/
bool
RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel,
HeapTuple old_row, HeapTuple new_row)
{
int tgnargs;
char **tgargs;
Relation pk_rel;
RI_QueryKey qkey;
/* /*
* Never reached * Check for the correct # of call arguments
*/ */
tgnargs = trigger->tgnargs;
tgargs = trigger->tgargs;
if (tgnargs < 4 ||
tgnargs > RI_MAX_ARGUMENTS ||
(tgnargs % 2) != 0)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" called with wrong number of trigger arguments",
"RI_FKey_keyequal_upd")));
/*
* Nothing to do if no column names to compare given
*/
if (tgnargs == 4)
return true;
if (!OidIsValid(trigger->tgconstrrelid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("no target table given for trigger \"%s\" on table \"%s\"",
trigger->tgname,
RelationGetRelationName(fk_rel)),
errhint("Remove this referential integrity trigger and its mates, "
"then do ALTER TABLE ADD CONSTRAINT.")));
pk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock);
switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
{
case RI_MATCH_TYPE_UNSPECIFIED:
case RI_MATCH_TYPE_FULL:
ri_BuildQueryKeyFull(&qkey, trigger->tgoid,
RI_PLAN_KEYEQUAL_UPD,
fk_rel, pk_rel,
tgnargs, tgargs);
heap_close(pk_rel, AccessShareLock);
/* Return if key's are equal */
return ri_KeysEqual(fk_rel, old_row, new_row, &qkey,
RI_KEYPAIR_FK_IDX);
/* Handle MATCH PARTIAL set null delete. */
case RI_MATCH_TYPE_PARTIAL:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("MATCH PARTIAL not yet implemented")));
break;
}
/* Never reached */
elog(ERROR, "invalid match_type"); elog(ERROR, "invalid match_type");
return false; return false;
} }
/* ---------- /* ----------
* RI_Initial_Check - * RI_Initial_Check -
* *
...@@ -2871,7 +2908,7 @@ ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno, ...@@ -2871,7 +2908,7 @@ ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
/* /*
* Initialize the key and fill in type, oid's and number of keypairs * Initialize the key and fill in type, oid's and number of keypairs
*/ */
memset((void *) key, 0, sizeof(RI_QueryKey)); memset(key, 0, sizeof(RI_QueryKey));
key->constr_type = RI_MATCH_TYPE_FULL; key->constr_type = RI_MATCH_TYPE_FULL;
key->constr_id = constr_id; key->constr_id = constr_id;
key->constr_queryno = constr_queryno; key->constr_queryno = constr_queryno;
...@@ -3489,7 +3526,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, ...@@ -3489,7 +3526,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
for (i = 0; i < key->nkeypairs; i++) for (i = 0; i < key->nkeypairs; i++)
{ {
/* /*
* Get one attributes oldvalue. If it is NULL - they're not equal. * Get one attribute's oldvalue. If it is NULL - they're not equal.
*/ */
oldvalue = SPI_getbinval(oldtup, rel->rd_att, oldvalue = SPI_getbinval(oldtup, rel->rd_att,
key->keypair[i][pairidx], &isnull); key->keypair[i][pairidx], &isnull);
...@@ -3497,7 +3534,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, ...@@ -3497,7 +3534,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
return false; return false;
/* /*
* Get one attributes oldvalue. If it is NULL - they're not equal. * Get one attribute's oldvalue. If it is NULL - they're not equal.
*/ */
newvalue = SPI_getbinval(newtup, rel->rd_att, newvalue = SPI_getbinval(newtup, rel->rd_att,
key->keypair[i][pairidx], &isnull); key->keypair[i][pairidx], &isnull);
...@@ -3505,7 +3542,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, ...@@ -3505,7 +3542,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
return false; return false;
/* /*
* Get the attributes type OID and call the '=' operator to * Get the attribute's type OID and call the '=' operator to
* compare the values. * compare the values.
*/ */
typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]); typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]);
...@@ -3644,3 +3681,32 @@ ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue) ...@@ -3644,3 +3681,32 @@ ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue)
return DatumGetBool(FunctionCall2(&(typentry->eq_opr_finfo), return DatumGetBool(FunctionCall2(&(typentry->eq_opr_finfo),
oldvalue, newvalue)); oldvalue, newvalue));
} }
/*
* 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;
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.53 2005/04/11 19:51:15 tgl Exp $ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.54 2005/05/30 07:20:58 neilc Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -168,9 +168,18 @@ extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); ...@@ -168,9 +168,18 @@ extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
/* /*
* in utils/adt/ri_triggers.c * in utils/adt/ri_triggers.c
*/ */
extern bool RI_FKey_keyequal_upd(TriggerData *trigdata); extern bool RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel,
HeapTuple old_row, HeapTuple new_row);
extern bool RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel,
HeapTuple old_row, HeapTuple new_row);
extern bool RI_Initial_Check(FkConstraint *fkconstraint, extern bool RI_Initial_Check(FkConstraint *fkconstraint,
Relation rel, Relation rel,
Relation pkrel); Relation pkrel);
#define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */
#define RI_TRIGGER_FK 2 /* is a trigger on the FK relation */
#define RI_TRIGGER_NONE 0 /* is not an RI trigger function */
extern int RI_FKey_trigger_type(Oid tgfoid);
#endif /* TRIGGER_H */ #endif /* TRIGGER_H */
...@@ -1061,6 +1061,8 @@ INSERT INTO fktable VALUES (100, 200); ...@@ -1061,6 +1061,8 @@ INSERT INTO fktable VALUES (100, 200);
COMMIT; COMMIT;
ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey" ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
DETAIL: Key (fk)=(200) is not present in table "pktable". DETAIL: Key (fk)=(200) is not present in table "pktable".
DROP TABLE pktable, fktable CASCADE;
NOTICE: drop cascades to constraint fktable_fk_fkey on table fktable
-- test notice about expensive referential integrity checks, -- test notice about expensive referential integrity checks,
-- where the index cannot be used because of type incompatibilities. -- where the index cannot be used because of type incompatibilities.
CREATE TEMP TABLE pktable ( CREATE TEMP TABLE pktable (
...@@ -1128,3 +1130,45 @@ WARNING: foreign key constraint "fk_241_132" will require costly sequential sca ...@@ -1128,3 +1130,45 @@ WARNING: foreign key constraint "fk_241_132" will require costly sequential sca
DETAIL: Key columns "x2" and "id1" are of different types: character varying and integer. DETAIL: Key columns "x2" and "id1" are of different types: character varying and integer.
WARNING: foreign key constraint "fk_241_132" will require costly sequential scans WARNING: foreign key constraint "fk_241_132" will require costly sequential scans
DETAIL: Key columns "x4" and "id3" are of different types: text and real. DETAIL: Key columns "x4" and "id3" are of different types: text and real.
DROP TABLE pktable, fktable CASCADE;
NOTICE: drop cascades to constraint fk_241_132 on table fktable
NOTICE: drop cascades to constraint fk_123_231 on table fktable
NOTICE: drop cascades to constraint fk_253_213 on table fktable
NOTICE: drop cascades to constraint fk_213_213 on table fktable
NOTICE: drop cascades to constraint fk_123_123 on table fktable
NOTICE: drop cascades to constraint fk_5_1 on table fktable
NOTICE: drop cascades to constraint fk_3_1 on table fktable
NOTICE: drop cascades to constraint fk_2_1 on table fktable
NOTICE: drop cascades to constraint fktable_x1_fkey on table fktable
NOTICE: drop cascades to constraint fk_4_2 on table fktable
NOTICE: drop cascades to constraint fk_1_2 on table fktable
NOTICE: drop cascades to constraint fktable_x2_fkey on table fktable
NOTICE: drop cascades to constraint fk_1_3 on table fktable
NOTICE: drop cascades to constraint fk_2_3 on table fktable
NOTICE: drop cascades to constraint fktable_x3_fkey on table fktable
-- test a tricky case: we can elide firing the FK check trigger during
-- an UPDATE if the UPDATE did not change the foreign key
-- field. However, we can't do this if our transaction was the one that
-- created the updated row and the trigger is deferred, since our UPDATE
-- will have invalidated the original newly-inserted tuple, and therefore
-- cause the on-INSERT RI trigger not to be fired.
CREATE TEMP TABLE pktable (
id int primary key,
other int
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
CREATE TEMP TABLE fktable (
id int primary key,
fk int references pktable deferrable initially deferred
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "fktable_pkey" for table "fktable"
INSERT INTO pktable VALUES (5, 10);
BEGIN;
-- doesn't match PK, but no error yet
INSERT INTO fktable VALUES (0, 20);
-- don't change FK
UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT
COMMIT;
ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
DETAIL: Key (fk)=(20) is not present in table "pktable".
...@@ -705,6 +705,8 @@ INSERT INTO fktable VALUES (100, 200); ...@@ -705,6 +705,8 @@ INSERT INTO fktable VALUES (100, 200);
-- error here on commit -- error here on commit
COMMIT; COMMIT;
DROP TABLE pktable, fktable CASCADE;
-- test notice about expensive referential integrity checks, -- test notice about expensive referential integrity checks,
-- where the index cannot be used because of type incompatibilities. -- where the index cannot be used because of type incompatibilities.
...@@ -774,3 +776,35 @@ FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1); ...@@ -774,3 +776,35 @@ FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1);
ALTER TABLE fktable ADD CONSTRAINT fk_241_132 ALTER TABLE fktable ADD CONSTRAINT fk_241_132
FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2); FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2);
DROP TABLE pktable, fktable CASCADE;
-- test a tricky case: we can elide firing the FK check trigger during
-- an UPDATE if the UPDATE did not change the foreign key
-- field. However, we can't do this if our transaction was the one that
-- created the updated row and the trigger is deferred, since our UPDATE
-- will have invalidated the original newly-inserted tuple, and therefore
-- cause the on-INSERT RI trigger not to be fired.
CREATE TEMP TABLE pktable (
id int primary key,
other int
);
CREATE TEMP TABLE fktable (
id int primary key,
fk int references pktable deferrable initially deferred
);
INSERT INTO pktable VALUES (5, 10);
BEGIN;
-- doesn't match PK, but no error yet
INSERT INTO fktable VALUES (0, 20);
-- don't change FK
UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT
COMMIT;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment