From adfeef55cbcc5dc72a772777f88c1be05a70dfee Mon Sep 17 00:00:00 2001 From: Neil Conway Date: Mon, 30 May 2005 07:20:59 +0000 Subject: [PATCH] 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. --- src/backend/commands/tablecmds.c | 44 +------ src/backend/commands/trigger.c | 75 ++++++------ src/backend/utils/adt/ri_triggers.c | 196 ++++++++++++++++++++---------- src/include/commands/trigger.h | 13 +- src/test/regress/expected/foreign_key.out | 44 +++++++ src/test/regress/sql/foreign_key.sql | 34 ++++++ 6 files changed, 256 insertions(+), 150 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c54104200d..4335792ee7 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 a78ad890b3..e0cfd33485 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 0dc1241012..a0d561f1f0 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 - */ 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 81a665e247..f6c1f88480 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 ae80b33c6f..e3ea729e6f 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 ad5865683f..5ab15c1b39 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; -- 2.11.0