*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
} 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);
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,
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
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
}
}
*
* 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 $
*
* ----------
*/
#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"
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");
* 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);
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);
/* ----------
- * 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)
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),
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 -
*
/*
* 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;
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);
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);
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]);
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;
+}
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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 */
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 (
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".
-- 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.
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;