From f14fdad85845b5701d6cda0107a9dd196f81085b Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 12 Nov 2001 00:46:36 +0000 Subject: [PATCH] Make ALTER TABLE RENAME update foreign-key trigger arguments correctly. Brent Verner, with review and kibitzing from Tom Lane. --- src/backend/commands/rename.c | 284 +++++++++++++++++++++++++++- src/backend/utils/adt/ri_triggers.c | 12 +- src/include/commands/trigger.h | 21 +- 3 files changed, 304 insertions(+), 13 deletions(-) diff --git a/src/backend/commands/rename.c b/src/backend/commands/rename.c index d319161b9a..c12d714f21 100644 --- a/src/backend/commands/rename.c +++ b/src/backend/commands/rename.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/Attic/rename.c,v 1.61 2001/11/05 17:46:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/Attic/rename.c,v 1.62 2001/11/12 00:46:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,25 +16,43 @@ #include +#include "access/genam.h" #include "access/heapam.h" +#include "access/itup.h" #include "catalog/catname.h" #include "catalog/pg_index.h" +#include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/heap.h" #include "catalog/indexing.h" #include "catalog/catalog.h" #include "commands/rename.h" +#include "commands/trigger.h" #include "miscadmin.h" #include "storage/smgr.h" #include "optimizer/prep.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteSupport.h" #include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/relcache.h" #include "utils/syscache.h" #include "utils/temprel.h" +#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 int ri_trigger_type(Oid tgfoid); +static void update_ri_trigger_args(Oid relid, + const char* oldname, + const char* newname, + bool fk_scan, + bool update_relname); + + /* * renameatt - changes the name of a attribute in a relation * @@ -226,6 +244,22 @@ renameatt(char *relname, freeList(indexoidlist); heap_close(attrelation, RowExclusiveLock); + + /* + * Update att name in any RI triggers associated with the relation. + */ + if (targetrelation->rd_rel->reltriggers > 0) + { + /* update tgargs column reference where att is primary key */ + update_ri_trigger_args(RelationGetRelid(targetrelation), + oldattname, newattname, + false, false); + /* update tgargs column reference where att is foreign key */ + update_ri_trigger_args(RelationGetRelid(targetrelation), + oldattname, newattname, + true, false); + } + heap_close(targetrelation, NoLock); /* close rel but keep lock! */ } @@ -240,6 +274,7 @@ renamerel(const char *oldrelname, const char *newrelname) HeapTuple reltup; Oid reloid; char relkind; + bool relhastriggers; Relation irelations[Num_pg_class_indices]; if (!allowSystemTableMods && IsSystemRelationName(oldrelname)) @@ -265,6 +300,7 @@ renamerel(const char *oldrelname, const char *newrelname) reloid = RelationGetRelid(targetrelation); relkind = targetrelation->rd_rel->relkind; + relhastriggers = (targetrelation->rd_rel->reltriggers > 0); /* * Close rel, but keep exclusive lock! @@ -331,4 +367,250 @@ renamerel(const char *oldrelname, const char *newrelname) newrulename = MakeRetrieveViewRuleName(newrelname); RenameRewriteRule(oldrulename, newrulename); } + + /* + * Update rel name in any RI triggers associated with the relation. + */ + if (relhastriggers) + { + /* update tgargs where relname is primary key */ + update_ri_trigger_args(reloid, + oldrelname, newrelname, + false, true); + /* update tgargs where relname is foreign key */ + update_ri_trigger_args(reloid, + oldrelname, newrelname, + true, true); + } +} + +/* + * 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 + * is true). Update RI trigger args fields matching oldname to contain + * newname instead. If update_relname is true, examine the relname + * fields; otherwise examine the attname fields. + */ +static void +update_ri_trigger_args(Oid relid, + const char* oldname, + const char* newname, + bool fk_scan, + bool update_relname) +{ + Relation tgrel; + Relation irel; + ScanKeyData skey[1]; + IndexScanDesc idxtgscan; + RetrieveIndexResult idxres; + Datum values[Natts_pg_trigger]; + char nulls[Natts_pg_trigger]; + char replaces[Natts_pg_trigger]; + + tgrel = heap_openr(TriggerRelationName, RowExclusiveLock); + if (fk_scan) + irel = index_openr(TriggerConstrRelidIndex); + else + irel = index_openr(TriggerRelidIndex); + + ScanKeyEntryInitialize(&skey[0], 0x0, + 1, /* always column 1 of index */ + F_OIDEQ, + ObjectIdGetDatum(relid)); + idxtgscan = index_beginscan(irel, false, 1, skey); + + while ((idxres = index_getnext(idxtgscan, ForwardScanDirection)) != NULL) + { + HeapTupleData tupledata; + Buffer buffer; + HeapTuple tuple; + Form_pg_trigger pg_trigger; + bytea* val; + bytea* newtgargs; + bool isnull; + int tg_type; + bool examine_pk; + bool changed; + int tgnargs; + int i; + int newlen; + const char *arga[RI_MAX_ARGUMENTS]; + const char *argp; + + tupledata.t_self = idxres->heap_iptr; + heap_fetch(tgrel, SnapshotNow, &tupledata, &buffer, idxtgscan); + pfree(idxres); + if (!tupledata.t_data) + continue; + tuple = &tupledata; + pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple); + tg_type = ri_trigger_type(pg_trigger->tgfoid); + if (tg_type == RI_TRIGGER_NONE) + { + /* Not an RI trigger, forget it */ + ReleaseBuffer(buffer); + continue; + } + + /* + * It is an RI trigger, so parse the tgargs bytea. + * + * NB: we assume the field will never be compressed or moved + * out of line; so does trigger.c ... + */ + tgnargs = pg_trigger->tgnargs; + val = (bytea *) fastgetattr(tuple, + Anum_pg_trigger_tgargs, + tgrel->rd_att, &isnull); + if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO || + tgnargs > RI_MAX_ARGUMENTS) + { + /* This probably shouldn't happen, but ignore busted triggers */ + ReleaseBuffer(buffer); + continue; + } + argp = (const char *) VARDATA(val); + for (i = 0; i < tgnargs; i++) + { + arga[i] = argp; + argp += strlen(argp)+1; + } + + /* + * Figure out which item(s) to look at. If the trigger is + * primary-key type and attached to my rel, I should look at + * the PK fields; if it is foreign-key type and attached to my + * rel, I should look at the FK fields. But the opposite rule + * holds when examining triggers found by tgconstrrel search. + */ + examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan); + + changed = false; + if (update_relname) + { + /* Change the relname if needed */ + i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO; + if (strcmp(arga[i], oldname) == 0) + { + arga[i] = newname; + changed = true; + } + } + else + { + /* Change attname(s) if needed */ + i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX : + RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX; + for (; i < tgnargs; i += 2) + { + if (strcmp(arga[i], oldname) == 0) + { + arga[i] = newname; + changed = true; + } + } + } + + if (!changed) + { + /* Don't need to update this tuple */ + ReleaseBuffer(buffer); + continue; + } + + /* + * Construct modified tgargs bytea. + */ + newlen = VARHDRSZ; + for (i = 0; i < tgnargs; i++) + newlen += strlen(arga[i]) + 1; + newtgargs = (bytea *) palloc(newlen); + VARATT_SIZEP(newtgargs) = newlen; + newlen = VARHDRSZ; + for (i = 0; i < tgnargs; i++) + { + strcpy(((char *) newtgargs) + newlen, arga[i]); + newlen += strlen(arga[i]) + 1; + } + + /* + * Build modified tuple. + */ + for (i = 0; i < Natts_pg_trigger; i++) + { + values[i] = (Datum) 0; + replaces[i] = ' '; + nulls[i] = ' '; + } + values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs); + replaces[Anum_pg_trigger_tgargs - 1] = 'r'; + + tuple = heap_modifytuple(tuple, tgrel, values, nulls, replaces); + + /* + * Now we can release hold on original tuple. + */ + ReleaseBuffer(buffer); + + /* + * Update pg_trigger and its indexes + */ + simple_heap_update(tgrel, &tuple->t_self, tuple); + + { + Relation irelations[Num_pg_attr_indices]; + + CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, irelations); + CatalogIndexInsert(irelations, Num_pg_trigger_indices, tgrel, tuple); + CatalogCloseIndices(Num_pg_trigger_indices, irelations); + } + + /* free up our scratch memory */ + pfree(newtgargs); + heap_freetuple(tuple); + } + + index_endscan(idxtgscan); + index_close(irel); + + heap_close(tgrel, RowExclusiveLock); + + /* + * Increment cmd counter to make updates visible; this is needed + * in case the same tuple has to be updated again by next pass + * (can happen in case of a self-referential FK relationship). + */ + CommandCounterIncrement(); } diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index b71732a194..071b7248d8 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 2000-2001, PostgreSQL Global Development Group * Copyright 1999 Jan Wieck * - * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.29 2001/10/25 05:49:45 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.30 2001/11/12 00:46:36 tgl Exp $ * * ---------- */ @@ -43,16 +43,6 @@ * Local definitions * ---------- */ -#define RI_CONSTRAINT_NAME_ARGNO 0 -#define RI_FK_RELNAME_ARGNO 1 -#define RI_PK_RELNAME_ARGNO 2 -#define RI_MATCH_TYPE_ARGNO 3 -#define RI_FIRST_ATTNAME_ARGNO 4 - -#define RI_MAX_NUMKEYS 16 -#define RI_MAX_ARGUMENTS (RI_FIRST_ATTNAME_ARGNO + (RI_MAX_NUMKEYS * 2)) -#define RI_KEYPAIR_FK_IDX 0 -#define RI_KEYPAIR_PK_IDX 1 #define RI_INIT_QUERYHASHSIZE 128 #define RI_INIT_OPREQHASHSIZE 128 diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index c0399cbf10..eae66cccaa 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: trigger.h,v 1.30 2001/11/05 17:46:33 momjian Exp $ + * $Id: trigger.h,v 1.31 2001/11/12 00:46:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -78,6 +78,25 @@ typedef struct TriggerData #define TRIGGER_FIRED_AFTER(event) \ (!TRIGGER_FIRED_BEFORE (event)) +/* + * RI trigger function arguments are stored in pg_trigger.tgargs bytea + * + * constrname\0fkrel\0pkrel\0matchtype\0fkatt\0pkatt\0fkatt\0pkatt\0... + * + * There are one or more pairs of fkatt/pkatt names. + */ +#define RI_CONSTRAINT_NAME_ARGNO 0 +#define RI_FK_RELNAME_ARGNO 1 +#define RI_PK_RELNAME_ARGNO 2 +#define RI_MATCH_TYPE_ARGNO 3 +#define RI_FIRST_ATTNAME_ARGNO 4 /* first attname pair starts here */ + +#define RI_KEYPAIR_FK_IDX 0 +#define RI_KEYPAIR_PK_IDX 1 + +#define RI_MAX_NUMKEYS INDEX_MAX_KEYS +#define RI_MAX_ARGUMENTS (RI_FIRST_ATTNAME_ARGNO + (RI_MAX_NUMKEYS * 2)) + extern void CreateTrigger(CreateTrigStmt *stmt); extern void DropTrigger(DropTrigStmt *stmt); -- 2.40.0