OSDN Git Service

Make ALTER TABLE RENAME update foreign-key trigger arguments correctly.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 12 Nov 2001 00:46:36 +0000 (00:46 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 12 Nov 2001 00:46:36 +0000 (00:46 +0000)
Brent Verner, with review and kibitzing from Tom Lane.

src/backend/commands/rename.c
src/backend/utils/adt/ri_triggers.c
src/include/commands/trigger.h

index d319161..c12d714 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 
 #include <errno.h>
 
+#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();
 }
index b71732a..071b724 100644 (file)
@@ -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 $
  *
  * ----------
  */
  * 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
index c0399cb..eae66cc 100644 (file)
@@ -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);