diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c54104200d7eaf4b5b363a48a8a154ca6e25ed6c..4335792ee712dc82658ec48a160e1eb17a2a2437 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * 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 } 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, List **supOids, List **supconstr, int *supOidCount); static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno); @@ -246,7 +240,6 @@ static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, char *tablespacename); static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace); static void copy_relation_data(Relation rel, SMgrRelation dst); -static int ri_trigger_type(Oid tgfoid); static void update_ri_trigger_args(Oid relid, const char *oldname, const char *newname, @@ -1571,39 +1564,6 @@ renamerel(Oid myrelid, const char *newrelname) 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 * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan @@ -1663,7 +1623,7 @@ update_ri_trigger_args(Oid relid, const char *arga[RI_MAX_ARGUMENTS]; 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) { /* Not an RI trigger, forget it */ diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index a78ad890b3c84ee0252b4318cf56c70ec6c329f5..e0cfd33485547230eb987259370e45a90fe3b6b8 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * 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, continue; /* - * If it is an RI UPDATE trigger, and the referenced keys have - * not changed, short-circuit queuing of the event; there's no - * need to fire the trigger. + * If this is an UPDATE of a PK table or FK table that does + * not change the PK or FK respectively, we can skip queuing + * the event: there is no need to fire the trigger. */ if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE) { - bool is_ri_trigger; - - switch (trigger->tgfoid) + switch (RI_FKey_trigger_type(trigger->tgfoid)) { - case F_RI_FKEY_NOACTION_UPD: - case F_RI_FKEY_CASCADE_UPD: - case F_RI_FKEY_RESTRICT_UPD: - case F_RI_FKEY_SETNULL_UPD: - case F_RI_FKEY_SETDEFAULT_UPD: - is_ri_trigger = true; + case RI_TRIGGER_PK: + /* Update on PK table */ + if (RI_FKey_keyequal_upd_pk(trigger, rel, oldtup, newtup)) + { + /* key unchanged, so skip queuing this event */ + continue; + } break; - default: - is_ri_trigger = false; + case RI_TRIGGER_FK: + /* + * 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; - } - if (is_ri_trigger) - { - TriggerData LocTriggerData; - - 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; - } + case RI_TRIGGER_NONE: + /* Not an FK trigger */ + break; } } diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 0dc1241012434ca02edfe3960ed965a673163124..a0d561f1f0f53ecd9a0a67b5944185f9ef537cd5 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -17,7 +17,7 @@ * * 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 @@ #include "optimizer/planmain.h" #include "parser/parse_oper.h" #include "rewrite/rewriteHandler.h" -#include "utils/lsyscache.h" -#include "utils/typcache.h" #include "utils/acl.h" +#include "utils/fmgroids.h" #include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/typcache.h" #include "miscadmin.h" @@ -375,22 +376,6 @@ RI_FKey_check(PG_FUNCTION_ARGS) break; } - /* - * No need to check anything if old and new references are the same on - * UPDATE. - */ - if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) - { - if (HeapTupleHeaderGetXmin(old_row->t_data) != - GetCurrentTransactionId() && - ri_KeysEqual(fk_rel, old_row, new_row, &qkey, - RI_KEYPAIR_FK_IDX)) - { - heap_close(pk_rel, RowShareLock); - return PointerGetDatum(NULL); - } - } - if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -2005,8 +1990,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) * corresponding to changed columns in pk_rel's key */ if (match_type == RI_MATCH_TYPE_FULL || - !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey, - RI_KEYPAIR_PK_IDX)) + !ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey, + RI_KEYPAIR_PK_IDX)) { snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL", querysep, attname); @@ -2016,7 +2001,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS) qualsep, attname, i + 1); qualsep = "AND"; 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); @@ -2451,30 +2436,27 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) /* ---------- - * RI_FKey_keyequal_upd - - * - * Check if we have a key change on update. + * RI_FKey_keyequal_upd_pk - * - * This is not a real trigger procedure. It is used by the AFTER - * trigger queue manager to detect "triggered data change violation". + * Check if we have a key change on an update to a PK relation. This is + * used by the AFTER trigger queue manager to detect "triggered data + * change violation". * ---------- */ 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; char **tgargs; Relation fk_rel; - Relation pk_rel; - HeapTuple new_row; - HeapTuple old_row; RI_QueryKey qkey; /* * Check for the correct # of call arguments */ - tgnargs = trigdata->tg_trigger->tgnargs; - tgargs = trigdata->tg_trigger->tgargs; + tgnargs = trigger->tgnargs; + tgargs = trigger->tgargs; if (tgnargs < 4 || tgnargs > RI_MAX_ARGUMENTS || (tgnargs % 2) != 0) @@ -2489,48 +2471,32 @@ RI_FKey_keyequal_upd(TriggerData *trigdata) if (tgnargs == 4) return true; - /* - * 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)) + if (!OidIsValid(trigger->tgconstrrelid)) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("no target table given for trigger \"%s\" on table \"%s\"", - trigdata->tg_trigger->tgname, - RelationGetRelationName(trigdata->tg_relation)), - errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT."))); + errmsg("no target table given for trigger \"%s\" on table \"%s\"", + trigger->tgname, + RelationGetRelationName(pk_rel)), + errhint("Remove this referential integrity trigger and its mates, " + "then do ALTER TABLE ADD CONSTRAINT."))); - fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, AccessShareLock); - pk_rel = trigdata->tg_relation; - new_row = trigdata->tg_newtuple; - old_row = trigdata->tg_trigtuple; + fk_rel = heap_open(trigger->tgconstrrelid, AccessShareLock); switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO])) { - /* - * MATCH <UNSPECIFIED> - */ case RI_MATCH_TYPE_UNSPECIFIED: case RI_MATCH_TYPE_FULL: - ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid, + ri_BuildQueryKeyFull(&qkey, trigger->tgoid, RI_PLAN_KEYEQUAL_UPD, fk_rel, pk_rel, tgnargs, tgargs); - 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, RI_KEYPAIR_PK_IDX); - /* - * Handle MATCH PARTIAL set null delete. - */ + /* Handle MATCH PARTIAL set null delete. */ case RI_MATCH_TYPE_PARTIAL: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -2538,14 +2504,85 @@ RI_FKey_keyequal_upd(TriggerData *trigdata) 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"); return false; } - /* ---------- * RI_Initial_Check - * @@ -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 */ - memset((void *) key, 0, sizeof(RI_QueryKey)); + memset(key, 0, sizeof(RI_QueryKey)); key->constr_type = RI_MATCH_TYPE_FULL; key->constr_id = constr_id; key->constr_queryno = constr_queryno; @@ -3489,7 +3526,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, 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, key->keypair[i][pairidx], &isnull); @@ -3497,7 +3534,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, 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, key->keypair[i][pairidx], &isnull); @@ -3505,7 +3542,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, 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. */ typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]); @@ -3644,3 +3681,32 @@ ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue) return DatumGetBool(FunctionCall2(&(typentry->eq_opr_finfo), 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; +} diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 81a665e24767e5f9b4d8fdecd5d554ddf2355690..f6c1f88480b96450ce724038c47657422a32f70b 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * 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); /* * 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, Relation rel, 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 */ diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index ae80b33c6f74b0f009b34a1b814f9f279b93d638..e3ea729e6fa92bd3c02d64c80ab3826e3edf47d7 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1061,6 +1061,8 @@ INSERT INTO fktable VALUES (100, 200); COMMIT; ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey" 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, -- where the index cannot be used because of type incompatibilities. CREATE TEMP TABLE pktable ( @@ -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. 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. +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". diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index ad5865683fb252333735b8b1a1ed2430d5e161e1..5ab15c1b39be42af0d67afd0dd9eaf2a74644806 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -705,6 +705,8 @@ INSERT INTO fktable VALUES (100, 200); -- error here on commit COMMIT; +DROP TABLE pktable, fktable CASCADE; + -- test notice about expensive referential integrity checks, -- where the index cannot be used because of type incompatibilities. @@ -774,3 +776,35 @@ FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1); ALTER TABLE fktable ADD CONSTRAINT fk_241_132 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;