]> granicus.if.org Git - postgresql/blobdiff - src/backend/commands/trigger.c
Second phase of committing Rod Taylor's pg_depend/pg_constraint patch.
[postgresql] / src / backend / commands / trigger.c
index b0196c90deeb45db1b23a2cee8dee722e6c1e641..b27dd0f4380f5aa56b1ab2c9a450e90f4dc53306 100644 (file)
  * trigger.c
  *       PostgreSQL TRIGGERs support code.
  *
+ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *       $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.121 2002/07/12 18:43:16 tgl Exp $
+ *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "catalog/catalog.h"
 #include "catalog/catname.h"
+#include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
-#include "commands/comment.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
+#include "parser/parse_func.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/inval.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
-#include "utils/tqual.h"
 
-DLLIMPORT TriggerData *CurrentTriggerData = NULL;
 
-void           RelationBuildTriggers(Relation relation);
-void           FreeTriggerDesc(Relation relation);
-
-static void DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger);
-static HeapTuple GetTupleForTrigger(EState *estate, ItemPointer tid,
+static void InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx);
+static HeapTuple GetTupleForTrigger(EState *estate,
+                                  ResultRelInfo *relinfo,
+                                  ItemPointer tid,
                                   TupleTableSlot **newSlot);
-
-extern GlobalMemory CacheCxt;
-
-void
-CreateTrigger(CreateTrigStmt *stmt)
+static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
+                                       FmgrInfo *finfo,
+                                       MemoryContext per_tuple_context);
+static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
+                                                HeapTuple oldtup, HeapTuple newtup);
+static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
+                                          Relation rel, FmgrInfo *finfo,
+                                          MemoryContext per_tuple_context);
+
+
+Oid
+CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 {
        int16           tgtype;
-       int16           tgattr[8] = {0};
+       int16           tgattr[FUNC_MAX_ARGS];
        Datum           values[Natts_pg_trigger];
        char            nulls[Natts_pg_trigger];
        Relation        rel;
+       AclResult       aclresult;
        Relation        tgrel;
-       HeapScanDesc tgscan;
+       SysScanDesc     tgscan;
        ScanKeyData key;
        Relation        pgrel;
        HeapTuple       tuple;
        Relation        idescs[Num_pg_trigger_indices];
        Relation        ridescs[Num_pg_class_indices];
-       MemoryContext oldcxt;
-       Oid                     fargtypes[8];
+       Oid                     fargtypes[FUNC_MAX_ARGS];
+       Oid                     funcoid;
+       Oid                     funclang;
+       Oid                     trigoid;
        int                     found = 0;
        int                     i;
        char            constrtrigname[NAMEDATALEN];
-       char       *constrname = "";
-       Oid                     constrrelid = 0;
+       char       *trigname;
+       char       *constrname;
+       Oid                     constrrelid;
+       ObjectAddress   myself,
+                                       referenced;
 
-       if (!allowSystemTableMods && IsSystemRelationName(stmt->relname))
-               elog(ERROR, "CreateTrigger: can't create trigger for system relation %s", stmt->relname);
+       rel = heap_openrv(stmt->relation, AccessExclusiveLock);
 
-#ifndef NO_SECURITY
-       if (!pg_ownercheck(GetPgUserName(), stmt->relname, RELNAME))
-               elog(ERROR, "%s: %s", stmt->relname, aclcheck_error_strings[ACLCHECK_NOT_OWNER]);
-#endif
+       if (rel->rd_rel->relkind != RELKIND_RELATION)
+               elog(ERROR, "CreateTrigger: relation \"%s\" is not a table",
+                        stmt->relation->relname);
 
-       /* ----------
-        * If trigger is a constraint, user trigger name as constraint
-        * name and build a unique trigger name instead.
-        * ----------
+       if (!allowSystemTableMods && IsSystemRelation(rel))
+               elog(ERROR, "CreateTrigger: can't create trigger for system relation %s",
+                       stmt->relation->relname);
+
+       aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
+                                                 stmt->isconstraint ? ACL_REFERENCES : ACL_TRIGGER);
+       if (aclresult != ACLCHECK_OK)
+               aclcheck_error(aclresult, RelationGetRelationName(rel));
+
+       /*
+        * If trigger is an RI constraint, use specified trigger name as
+        * constraint name and build a unique trigger name instead.
+        * This is mainly for backwards compatibility with CREATE CONSTRAINT
+        * TRIGGER commands.
         */
        if (stmt->isconstraint)
        {
+               snprintf(constrtrigname, sizeof(constrtrigname),
+                                "RI_ConstraintTrigger_%u", newoid());
+               trigname = constrtrigname;
                constrname = stmt->trigname;
-               stmt->trigname = constrtrigname;
-               sprintf(constrtrigname, "RI_ConstraintTrigger_%d", newoid());
-
-               if (strcmp(stmt->constrrelname, "") == 0)
-                       constrrelid = 0;
-               else
-               {
-                       rel = heap_openr(stmt->constrrelname, NoLock);
-                       if (rel == NULL)
-                               elog(ERROR, "table \"%s\" does not exist",
-                                                       stmt->constrrelname);
-                       constrrelid = rel->rd_id;
-                       heap_close(rel, NoLock);
-               }
+       }
+       else
+       {
+               trigname = stmt->trigname;
+               constrname = "";
        }
 
-       rel = heap_openr(stmt->relname, AccessExclusiveLock);
+       if (stmt->constrrel != NULL)
+               constrrelid = RangeVarGetRelid(stmt->constrrel, false);
+       else
+               constrrelid = InvalidOid;
 
        TRIGGER_CLEAR_TYPE(tgtype);
        if (stmt->before)
@@ -127,64 +151,85 @@ CreateTrigger(CreateTrigStmt *stmt)
                }
        }
 
-       /* Scan pg_trigger */
+       /*
+        * Scan pg_trigger for existing triggers on relation.  We do this mainly
+        * because we must count them; a secondary benefit is to give a nice
+        * error message if there's already a trigger of the same name.  (The
+        * unique index on tgrelid/tgname would complain anyway.)
+        *
+        * NOTE that this is cool only because we have AccessExclusiveLock on the
+        * relation, so the trigger set won't be changing underneath us.
+        */
        tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
-       ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
-                                                  F_OIDEQ, RelationGetRelid(rel));
-       tgscan = heap_beginscan(tgrel, 0, SnapshotNow, 1, &key);
-       while (HeapTupleIsValid(tuple = heap_getnext(tgscan, 0)))
+       ScanKeyEntryInitialize(&key, 0,
+                                                  Anum_pg_trigger_tgrelid,
+                                                  F_OIDEQ,
+                                                  ObjectIdGetDatum(RelationGetRelid(rel)));
+       tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
+                                                               SnapshotNow, 1, &key);
+       while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
        {
                Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
 
-               if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+               if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
                        elog(ERROR, "CreateTrigger: trigger %s already defined on relation %s",
-                                stmt->trigname, stmt->relname);
-               else
-                       found++;
+                                trigname, stmt->relation->relname);
+               found++;
        }
-       heap_endscan(tgscan);
-
-       MemSet(fargtypes, 0, 8 * sizeof(Oid));
-       tuple = SearchSysCacheTuple(PROCNAME,
-                                                               PointerGetDatum(stmt->funcname),
-                                                               Int32GetDatum(0),
-                                                               PointerGetDatum(fargtypes),
-                                                               0);
-       if (!HeapTupleIsValid(tuple) ||
-               ((Form_pg_proc) GETSTRUCT(tuple))->pronargs != 0)
+       systable_endscan(tgscan);
+
+       /*
+        * Find and validate the trigger function.
+        */
+       MemSet(fargtypes, 0, FUNC_MAX_ARGS * sizeof(Oid));
+       funcoid = LookupFuncName(stmt->funcname, 0, fargtypes);
+       if (!OidIsValid(funcoid))
+               elog(ERROR, "CreateTrigger: function %s() does not exist",
+                        NameListToString(stmt->funcname));
+       tuple = SearchSysCache(PROCOID,
+                                                  ObjectIdGetDatum(funcoid),
+                                                  0, 0, 0);
+       if (!HeapTupleIsValid(tuple))
                elog(ERROR, "CreateTrigger: function %s() does not exist",
-                        stmt->funcname);
+                        NameListToString(stmt->funcname));
        if (((Form_pg_proc) GETSTRUCT(tuple))->prorettype != 0)
                elog(ERROR, "CreateTrigger: function %s() must return OPAQUE",
-                        stmt->funcname);
-       if (((Form_pg_proc) GETSTRUCT(tuple))->prolang != ClanguageId &&
-               ((Form_pg_proc) GETSTRUCT(tuple))->prolang != INTERNALlanguageId)
+                        NameListToString(stmt->funcname));
+       funclang = ((Form_pg_proc) GETSTRUCT(tuple))->prolang;
+       ReleaseSysCache(tuple);
+
+       if (funclang != ClanguageId && funclang != INTERNALlanguageId)
        {
                HeapTuple       langTup;
 
-               langTup = SearchSysCacheTuple(LANGOID,
-                       ObjectIdGetDatum(((Form_pg_proc) GETSTRUCT(tuple))->prolang),
-                                                                         0, 0, 0);
+               langTup = SearchSysCache(LANGOID,
+                                                                ObjectIdGetDatum(funclang),
+                                                                0, 0, 0);
                if (!HeapTupleIsValid(langTup))
-                       elog(ERROR, "CreateTrigger: cache lookup for PL failed");
-
+                       elog(ERROR, "CreateTrigger: cache lookup for language %u failed",
+                                funclang);
                if (((Form_pg_language) GETSTRUCT(langTup))->lanispl == false)
-                       elog(ERROR, "CreateTrigger: only builtin, C and PL functions are supported");
+                       elog(ERROR, "CreateTrigger: only internal, C and PL functions are supported");
+               ReleaseSysCache(langTup);
        }
 
+       /*
+        * Build the new pg_trigger tuple.
+        */
        MemSet(nulls, ' ', Natts_pg_trigger * sizeof(char));
 
        values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
-       values[Anum_pg_trigger_tgname - 1] = NameGetDatum(namein(stmt->trigname));
-       values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(tuple->t_data->t_oid);
+       values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein,
+                                                                               CStringGetDatum(trigname));
+       values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
        values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
-
-       values[Anum_pg_trigger_tgenabled - 1]           = true;
-       values[Anum_pg_trigger_tgisconstraint - 1]      = stmt->isconstraint;
-       values[Anum_pg_trigger_tgconstrname - 1]        = PointerGetDatum(constrname);;
-       values[Anum_pg_trigger_tgconstrrelid - 1]       = constrrelid;
-       values[Anum_pg_trigger_tgdeferrable - 1]        = stmt->deferrable;
-       values[Anum_pg_trigger_tginitdeferred - 1]      = stmt->initdeferred;
+       values[Anum_pg_trigger_tgenabled - 1] = BoolGetDatum(true);
+       values[Anum_pg_trigger_tgisconstraint - 1] = BoolGetDatum(stmt->isconstraint);
+       values[Anum_pg_trigger_tgconstrname - 1] = DirectFunctionCall1(namein,
+                                                                                                                                  CStringGetDatum(constrname));
+       values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
+       values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
+       values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);
 
        if (stmt->args)
        {
@@ -195,9 +240,9 @@ CreateTrigger(CreateTrigStmt *stmt)
 
                foreach(le, stmt->args)
                {
-                       char       *ar = (char *) lfirst(le);
+                       char       *ar = ((Value *) lfirst(le))->val.str;
 
-                       len += strlen(ar) + VARHDRSZ;
+                       len += strlen(ar) + 4;
                        for (; *ar; ar++)
                        {
                                if (*ar == '\\')
@@ -205,10 +250,10 @@ CreateTrigger(CreateTrigStmt *stmt)
                        }
                }
                args = (char *) palloc(len + 1);
-               args[0] = 0;
+               args[0] = '\0';
                foreach(le, stmt->args)
                {
-                       char       *s = (char *) lfirst(le);
+                       char       *s = ((Value *) lfirst(le))->val.str;
                        char       *d = args + strlen(args);
 
                        while (*s)
@@ -217,286 +262,448 @@ CreateTrigger(CreateTrigStmt *stmt)
                                        *d++ = '\\';
                                *d++ = *s++;
                        }
-                       *d = 0;
-                       strcat(args, "\\000");
+                       strcpy(d, "\\000");
                }
                values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(nargs);
-               values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(byteain(args));
+               values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
+                                                                                                 CStringGetDatum(args));
        }
        else
        {
                values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(0);
-               values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(byteain(""));
+               values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
+                                                                                                       CStringGetDatum(""));
        }
+       MemSet(tgattr, 0, FUNC_MAX_ARGS * sizeof(int16));
        values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
 
        tuple = heap_formtuple(tgrel->rd_att, values, nulls);
-       heap_insert(tgrel, tuple);
+
+       /*
+        * Insert tuple into pg_trigger.
+        */
+       trigoid = simple_heap_insert(tgrel, tuple);
+
        CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, idescs);
        CatalogIndexInsert(idescs, Num_pg_trigger_indices, tgrel, tuple);
        CatalogCloseIndices(Num_pg_trigger_indices, idescs);
+
+       myself.classId = RelationGetRelid(tgrel);
+       myself.objectId = trigoid;
+       myself.objectSubId = 0;
+
        heap_freetuple(tuple);
        heap_close(tgrel, RowExclusiveLock);
 
        pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
        pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
 
-       /* update pg_class */
+       /*
+        * Update relation's pg_class entry.  Crucial side-effect: other
+        * backends (and this one too!) are sent SI message to make them
+        * rebuild relcache entries.
+        */
        pgrel = heap_openr(RelationRelationName, RowExclusiveLock);
-       tuple = SearchSysCacheTupleCopy(RELNAME,
-                                                                       PointerGetDatum(stmt->relname),
-                                                                       0, 0, 0);
+       tuple = SearchSysCacheCopy(RELOID,
+                                                          ObjectIdGetDatum(RelationGetRelid(rel)),
+                                                          0, 0, 0);
        if (!HeapTupleIsValid(tuple))
-               elog(ERROR, "CreateTrigger: relation %s not found in pg_class", stmt->relname);
+               elog(ERROR, "CreateTrigger: relation %s not found in pg_class",
+                        stmt->relation->relname);
 
        ((Form_pg_class) GETSTRUCT(tuple))->reltriggers = found + 1;
-       RelationInvalidateHeapTuple(pgrel, tuple);
-       heap_update(pgrel, &tuple->t_self, tuple, NULL);
+
+       simple_heap_update(pgrel, &tuple->t_self, tuple);
+
        CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
        CatalogIndexInsert(ridescs, Num_pg_class_indices, pgrel, tuple);
        CatalogCloseIndices(Num_pg_class_indices, ridescs);
+
        heap_freetuple(tuple);
        heap_close(pgrel, RowExclusiveLock);
 
-       CommandCounterIncrement();
-       oldcxt = MemoryContextSwitchTo((MemoryContext) CacheCxt);
-       FreeTriggerDesc(rel);
-       rel->rd_rel->reltriggers = found + 1;
-       RelationBuildTriggers(rel);
-       MemoryContextSwitchTo(oldcxt);
+       /*
+        * We used to try to update the rel's relcache entry here, but that's
+        * fairly pointless since it will happen as a byproduct of the
+        * upcoming CommandCounterIncrement...
+        */
+
+       /*
+        * Record dependencies for trigger.  Always place a normal dependency
+        * on the function.  If we are doing this in response to an explicit
+        * CREATE TRIGGER command, also make trigger be auto-dropped if its
+        * relation is dropped or if the FK relation is dropped.  (Auto drop
+        * is compatible with our pre-7.3 behavior.)  If the trigger is being
+        * made for a constraint, we can skip the relation links; the dependency
+        * on the constraint will indirectly depend on the relations.
+        */
+       referenced.classId = RelOid_pg_proc;
+       referenced.objectId = funcoid;
+       referenced.objectSubId = 0;
+       recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+       if (!forConstraint)
+       {
+               referenced.classId = RelOid_pg_class;
+               referenced.objectId = RelationGetRelid(rel);
+               referenced.objectSubId = 0;
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+               if (constrrelid != InvalidOid)
+               {
+                       referenced.classId = RelOid_pg_class;
+                       referenced.objectId = constrrelid;
+                       referenced.objectSubId = 0;
+                       recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+               }
+       }
+
        /* Keep lock on target rel until end of xact */
        heap_close(rel, NoLock);
+
+       return trigoid;
 }
 
+/*
+ * DropTrigger - drop an individual trigger by name
+ */
 void
-DropTrigger(DropTrigStmt *stmt)
+DropTrigger(Oid relid, const char *trigname, DropBehavior behavior)
+{
+       Relation                tgrel;
+       ScanKeyData             skey[2];
+       SysScanDesc             tgscan;
+       HeapTuple               tup;
+       ObjectAddress object;
+
+       /*
+        * Find the trigger, verify permissions, set up object address
+        */
+       tgrel = heap_openr(TriggerRelationName, AccessShareLock);
+
+       ScanKeyEntryInitialize(&skey[0], 0x0,
+                                                  Anum_pg_trigger_tgrelid, F_OIDEQ,
+                                                  ObjectIdGetDatum(relid));
+
+       ScanKeyEntryInitialize(&skey[1], 0x0,
+                                                  Anum_pg_trigger_tgname, F_NAMEEQ,
+                                                  CStringGetDatum(trigname));
+
+       tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
+                                                               SnapshotNow, 2, skey);
+
+       tup = systable_getnext(tgscan);
+
+       if (!HeapTupleIsValid(tup))
+               elog(ERROR, "DropTrigger: there is no trigger %s on relation %s",
+                        trigname, get_rel_name(relid));
+
+       if (!pg_class_ownercheck(relid, GetUserId()))
+               aclcheck_error(ACLCHECK_NOT_OWNER, get_rel_name(relid));
+
+       object.classId = RelationGetRelid(tgrel);
+       object.objectId = tup->t_data->t_oid;
+       object.objectSubId = 0;
+
+       systable_endscan(tgscan);
+       heap_close(tgrel, AccessShareLock);
+
+       /*
+        * Do the deletion
+        */
+       performDeletion(&object, behavior);
+}
+
+/*
+ * Guts of trigger deletion.
+ */
+void
+RemoveTriggerById(Oid trigOid)
 {
-       Relation        rel;
        Relation        tgrel;
-       HeapScanDesc tgscan;
-       ScanKeyData key;
+       SysScanDesc     tgscan;
+       ScanKeyData     skey[1];
+       HeapTuple       tup;
+       Oid                     relid;
+       Relation        rel;
        Relation        pgrel;
        HeapTuple       tuple;
+       Form_pg_class   classForm;
        Relation        ridescs[Num_pg_class_indices];
-       MemoryContext oldcxt;
-       int                     found = 0;
-       int                     tgfound = 0;
 
-#ifndef NO_SECURITY
-       if (!pg_ownercheck(GetPgUserName(), stmt->relname, RELNAME))
-               elog(ERROR, "%s: %s", stmt->relname, aclcheck_error_strings[ACLCHECK_NOT_OWNER]);
-#endif
+       tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
 
-       rel = heap_openr(stmt->relname, AccessExclusiveLock);
+       /*
+        * Find the trigger to delete.
+        */
+       ScanKeyEntryInitialize(&skey[0], 0x0,
+                                                  ObjectIdAttributeNumber, F_OIDEQ,
+                                                  ObjectIdGetDatum(trigOid));
 
-       tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
-       ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
-                                                  F_OIDEQ, RelationGetRelid(rel));
-       tgscan = heap_beginscan(tgrel, 0, SnapshotNow, 1, &key);
-       while (HeapTupleIsValid(tuple = heap_getnext(tgscan, 0)))
-       {
-               Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+       tgscan = systable_beginscan(tgrel, TriggerOidIndex, true,
+                                                               SnapshotNow, 1, skey);
 
-               if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
-               {
+       tup = systable_getnext(tgscan);
+       if (!HeapTupleIsValid(tup))
+               elog(ERROR, "RemoveTriggerById: Trigger %u does not exist",
+                        trigOid);
 
-                 /*** Delete any comments associated with this trigger ***/
+       /*
+        * Open and exclusive-lock the relation the trigger belongs to.
+        */
+       relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
 
-                 DeleteComments(tuple->t_data->t_oid);
+       rel = heap_open(relid, AccessExclusiveLock);
 
-                 heap_delete(tgrel, &tuple->t_self, NULL);
-                 tgfound++;
-                 
-               }
-               else
-                       found++;
-       }
-       if (tgfound == 0)
-               elog(ERROR, "DropTrigger: there is no trigger %s on relation %s",
-                        stmt->trigname, stmt->relname);
-       if (tgfound > 1)
-               elog(NOTICE, "DropTrigger: found (and deleted) %d triggers %s on relation %s",
-                        tgfound, stmt->trigname, stmt->relname);
-       heap_endscan(tgscan);
+       if (rel->rd_rel->relkind != RELKIND_RELATION)
+               elog(ERROR, "DropTrigger: relation \"%s\" is not a table",
+                        RelationGetRelationName(rel));
+
+       if (!allowSystemTableMods && IsSystemRelation(rel))
+               elog(ERROR, "DropTrigger: can't drop trigger for system relation %s",
+                        RelationGetRelationName(rel));
+
+       /*
+        * Delete the pg_trigger tuple.
+        */
+       simple_heap_delete(tgrel, &tup->t_self);
+
+       systable_endscan(tgscan);
        heap_close(tgrel, RowExclusiveLock);
 
-       /* update pg_class */
+       /*
+        * Update relation's pg_class entry.  Crucial side-effect: other
+        * backends (and this one too!) are sent SI message to make them
+        * rebuild relcache entries.
+        *
+        * Note this is OK only because we have AccessExclusiveLock on the rel,
+        * so no one else is creating/deleting triggers on this rel at the same
+        * time.
+        */
        pgrel = heap_openr(RelationRelationName, RowExclusiveLock);
-       tuple = SearchSysCacheTupleCopy(RELNAME,
-                                                                       PointerGetDatum(stmt->relname),
-                                                                       0, 0, 0);
+       tuple = SearchSysCacheCopy(RELOID,
+                                                          ObjectIdGetDatum(relid),
+                                                          0, 0, 0);
        if (!HeapTupleIsValid(tuple))
-               elog(ERROR, "DropTrigger: relation %s not found in pg_class", stmt->relname);
+               elog(ERROR, "DropTrigger: relation %s not found in pg_class",
+                        RelationGetRelationName(rel));
+       classForm = (Form_pg_class) GETSTRUCT(tuple);
+
+       if (classForm->reltriggers == 0)
+               elog(ERROR, "DropTrigger: relation %s has reltriggers = 0",
+                        RelationGetRelationName(rel));
+       classForm->reltriggers--;
+
+       simple_heap_update(pgrel, &tuple->t_self, tuple);
 
-       ((Form_pg_class) GETSTRUCT(tuple))->reltriggers = found;
-       RelationInvalidateHeapTuple(pgrel, tuple);
-       heap_update(pgrel, &tuple->t_self, tuple, NULL);
        CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
        CatalogIndexInsert(ridescs, Num_pg_class_indices, pgrel, tuple);
        CatalogCloseIndices(Num_pg_class_indices, ridescs);
+
        heap_freetuple(tuple);
+
        heap_close(pgrel, RowExclusiveLock);
 
-       CommandCounterIncrement();
-       oldcxt = MemoryContextSwitchTo((MemoryContext) CacheCxt);
-       FreeTriggerDesc(rel);
-       rel->rd_rel->reltriggers = found;
-       if (found > 0)
-               RelationBuildTriggers(rel);
-       MemoryContextSwitchTo(oldcxt);
-       /* Keep lock on target rel until end of xact */
+       /* Keep lock on trigger's rel until end of xact */
        heap_close(rel, NoLock);
 }
 
+/*
+ *             renametrig              - changes the name of a trigger on a relation
+ *
+ *             trigger name is changed in trigger catalog.
+ *             No record of the previous name is kept.
+ *
+ *             get proper relrelation from relation catalog (if not arg)
+ *             scan trigger catalog
+ *                             for name conflict (within rel)
+ *                             for original trigger (if not arg)
+ *             modify tgname in trigger tuple
+ *             update row in catalog
+ */
 void
-RelationRemoveTriggers(Relation rel)
+renametrig(Oid relid,
+                  const char *oldname,
+                  const char *newname)
 {
+       Relation        targetrel;
        Relation        tgrel;
-       HeapScanDesc tgscan;
-       ScanKeyData key;
-       HeapTuple       tup;
-       Form_pg_trigger pg_trigger;
-
-       tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
-       ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
-                                                  F_OIDEQ, RelationGetRelid(rel));
-
-       tgscan = heap_beginscan(tgrel, 0, SnapshotNow, 1, &key);
-
-       while (HeapTupleIsValid(tup = heap_getnext(tgscan, 0))) {
-
-         /*** Delete any comments associated with this trigger ***/
-
-         DeleteComments(tup->t_data->t_oid);
-         
-         heap_delete(tgrel, &tup->t_self, NULL);
-
-       }
+       HeapTuple       tuple;
+       SysScanDesc     tgscan;
+       ScanKeyData key[2];
+       Relation        idescs[Num_pg_trigger_indices];
 
-       heap_endscan(tgscan);
+       /*
+        * Grab an exclusive lock on the target table, which we will NOT
+        * release until end of transaction.
+        */
+       targetrel = heap_open(relid, AccessExclusiveLock);
 
+       /*
+        * Scan pg_trigger twice for existing triggers on relation.  We do this in
+        * order to ensure a trigger does not exist with newname (The unique index
+        * on tgrelid/tgname would complain anyway) and to ensure a trigger does
+        * exist with oldname.
+        *
+        * NOTE that this is cool only because we have AccessExclusiveLock on the
+        * relation, so the trigger set won't be changing underneath us.
+        */
+       tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
 
-       /* ----------
-        * Also drop all constraint triggers referencing this relation
-        * ----------
+       /*
+        * First pass -- look for name conflict
         */
-       ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgconstrrelid,
-                                                       F_OIDEQ, RelationGetRelid(rel));
+       ScanKeyEntryInitialize(&key[0], 0,
+                                                  Anum_pg_trigger_tgrelid,
+                                                  F_OIDEQ,
+                                                  ObjectIdGetDatum(relid));
+       ScanKeyEntryInitialize(&key[1], 0,
+                                                  Anum_pg_trigger_tgname,
+                                                  F_NAMEEQ,
+                                                  PointerGetDatum(newname));
+       tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
+                                                               SnapshotNow, 2, key);
+       if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+               elog(ERROR, "renametrig: trigger %s already defined on relation %s",
+                        newname, RelationGetRelationName(targetrel));
+       systable_endscan(tgscan);
 
-       tgscan = heap_beginscan(tgrel, 0, SnapshotNow, 1, &key);
-       while (HeapTupleIsValid(tup = heap_getnext(tgscan, 0)))
+       /*
+        * Second pass -- look for trigger existing with oldname and update
+        */
+       ScanKeyEntryInitialize(&key[0], 0,
+                                                  Anum_pg_trigger_tgrelid,
+                                                  F_OIDEQ,
+                                                  ObjectIdGetDatum(relid));
+       ScanKeyEntryInitialize(&key[1], 0,
+                                                  Anum_pg_trigger_tgname,
+                                                  F_NAMEEQ,
+                                                  PointerGetDatum(oldname));
+       tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
+                                                               SnapshotNow, 2, key);
+       if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
        {
-               Relation                refrel;
-               DropTrigStmt    stmt;
-
-               pg_trigger = (Form_pg_trigger) GETSTRUCT(tup);
-               refrel = heap_open(pg_trigger->tgrelid, NoLock);
-
-               stmt.relname = pstrdup(RelationGetRelationName(refrel));
-               stmt.trigname = nameout(&pg_trigger->tgname);
+               /*
+                * Update pg_trigger tuple with new tgname.
+                */
+               tuple = heap_copytuple(tuple); /* need a modifiable copy */
 
-               elog(NOTICE, "DROP TABLE implicitly drops referential integrity trigger from table \"%s\"", stmt.relname);
+               namestrcpy(&((Form_pg_trigger) GETSTRUCT(tuple))->tgname, newname);
 
-               DropTrigger(&stmt);
+               simple_heap_update(tgrel, &tuple->t_self, tuple);
 
-               pfree(stmt.relname);
-               pfree(stmt.trigname);
+               /*
+                * keep system catalog indices current
+                */
+               CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, idescs);
+               CatalogIndexInsert(idescs, Num_pg_trigger_indices, tgrel, tuple);
+               CatalogCloseIndices(Num_pg_trigger_indices, idescs);
 
-               heap_close(refrel, NoLock);
+               /*
+                * Invalidate relation's relcache entry so that other backends (and
+                * this one too!) are sent SI message to make them rebuild relcache
+                * entries.  (Ideally this should happen automatically...)
+                */
+               CacheInvalidateRelcache(relid);
+       }
+       else
+       {
+               elog(ERROR, "renametrig: trigger %s not defined on relation %s",
+                        oldname, RelationGetRelationName(targetrel));
        }
-       heap_endscan(tgscan);
+
+       systable_endscan(tgscan);
 
        heap_close(tgrel, RowExclusiveLock);
+
+       /*
+        * Close rel, but keep exclusive lock!
+        */
+       heap_close(targetrel, NoLock);
 }
 
+/*
+ * Build trigger data to attach to the given relcache entry.
+ *
+ * Note that trigger data must be allocated in CacheMemoryContext
+ * to ensure it survives as long as the relcache entry.  But we
+ * are probably running in a less long-lived working context.
+ */
 void
 RelationBuildTriggers(Relation relation)
 {
-       TriggerDesc *trigdesc = (TriggerDesc *) palloc(sizeof(TriggerDesc));
+       TriggerDesc *trigdesc;
        int                     ntrigs = relation->rd_rel->reltriggers;
-       Trigger    *triggers = NULL;
-       Trigger    *build;
+       Trigger    *triggers;
+       int                     found = 0;
        Relation        tgrel;
-       Form_pg_trigger pg_trigger;
-       Relation        irel;
        ScanKeyData skey;
-       HeapTupleData tuple;
-       IndexScanDesc sd;
-       RetrieveIndexResult indexRes;
-       Buffer          buffer;
+       SysScanDesc     tgscan;
+       HeapTuple       htup;
        struct varlena *val;
        bool            isnull;
-       int                     found;
 
-       MemSet(trigdesc, 0, sizeof(TriggerDesc));
+       triggers = (Trigger *) MemoryContextAlloc(CacheMemoryContext,
+                                                                                         ntrigs * sizeof(Trigger));
 
+       /*
+        * Note: since we scan the triggers using TriggerRelidNameIndex,
+        * we will be reading the triggers in name order, except possibly
+        * during emergency-recovery operations (ie, IsIgnoringSystemIndexes).
+        * This in turn ensures that triggers will be fired in name order.
+        */
        ScanKeyEntryInitialize(&skey,
                                                   (bits16) 0x0,
-                                                  (AttrNumber) 1,
+                                                  (AttrNumber) Anum_pg_trigger_tgrelid,
                                                   (RegProcedure) F_OIDEQ,
                                                   ObjectIdGetDatum(RelationGetRelid(relation)));
 
        tgrel = heap_openr(TriggerRelationName, AccessShareLock);
-       irel = index_openr(TriggerRelidIndex);
-       sd = index_beginscan(irel, false, 1, &skey);
+       tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
+                                                               SnapshotNow, 1, &skey);
 
-       for (found = 0;;)
+       while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
        {
-               indexRes = index_getnext(sd, ForwardScanDirection);
-               if (!indexRes)
-                       break;
+               Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
+               Trigger    *build;
 
-               tuple.t_self = indexRes->heap_iptr;
-               heap_fetch(tgrel, SnapshotNow, &tuple, &buffer);
-               pfree(indexRes);
-               if (!tuple.t_data)
-                       continue;
-               if (found == ntrigs)
-                       elog(ERROR, "RelationBuildTriggers: unexpected record found for rel %.*s",
-                                NAMEDATALEN, RelationGetRelationName(relation));
-
-               pg_trigger = (Form_pg_trigger) GETSTRUCT(&tuple);
-
-               if (triggers == NULL)
-                       triggers = (Trigger *) palloc(sizeof(Trigger));
-               else
-                       triggers = (Trigger *) repalloc(triggers, (found + 1) * sizeof(Trigger));
+               if (found >= ntrigs)
+                       elog(ERROR, "RelationBuildTriggers: unexpected record found for rel %s",
+                                RelationGetRelationName(relation));
                build = &(triggers[found]);
 
-               build->tgoid = tuple.t_data->t_oid;
-               build->tgname = nameout(&pg_trigger->tgname);
+               build->tgoid = htup->t_data->t_oid;
+               build->tgname = MemoryContextStrdup(CacheMemoryContext,
+                                                        DatumGetCString(DirectFunctionCall1(nameout,
+                                                                       NameGetDatum(&pg_trigger->tgname))));
                build->tgfoid = pg_trigger->tgfoid;
-               build->tgfunc.fn_addr = NULL;
                build->tgtype = pg_trigger->tgtype;
                build->tgenabled = pg_trigger->tgenabled;
                build->tgisconstraint = pg_trigger->tgisconstraint;
+               build->tgconstrrelid = pg_trigger->tgconstrrelid;
                build->tgdeferrable = pg_trigger->tgdeferrable;
                build->tginitdeferred = pg_trigger->tginitdeferred;
                build->tgnargs = pg_trigger->tgnargs;
-               memcpy(build->tgattr, &(pg_trigger->tgattr), 8 * sizeof(int16));
-               val = (struct varlena *) fastgetattr(&tuple,
-                                                                                        Anum_pg_trigger_tgargs,
-                                                                                        tgrel->rd_att, &isnull);
-               if (isnull)
-                       elog(ERROR, "RelationBuildTriggers: tgargs IS NULL for rel %.*s",
-                                NAMEDATALEN, RelationGetRelationName(relation));
+               memcpy(build->tgattr, &(pg_trigger->tgattr),
+                          FUNC_MAX_ARGS * sizeof(int16));
                if (build->tgnargs > 0)
                {
                        char       *p;
                        int                     i;
 
-                       val = (struct varlena *) fastgetattr(&tuple,
+                       val = (struct varlena *) fastgetattr(htup,
                                                                                                 Anum_pg_trigger_tgargs,
                                                                                                 tgrel->rd_att, &isnull);
                        if (isnull)
-                               elog(ERROR, "RelationBuildTriggers: tgargs IS NULL for rel %.*s",
-                                        NAMEDATALEN, RelationGetRelationName(relation));
+                               elog(ERROR, "RelationBuildTriggers: tgargs IS NULL for rel %s",
+                                        RelationGetRelationName(relation));
                        p = (char *) VARDATA(val);
-                       build->tgargs = (char **) palloc(build->tgnargs * sizeof(char *));
+                       build->tgargs = (char **)
+                               MemoryContextAlloc(CacheMemoryContext,
+                                                                  build->tgnargs * sizeof(char *));
                        for (i = 0; i < build->tgnargs; i++)
                        {
-                               build->tgargs[i] = (char *) palloc(strlen(p) + 1);
-                               strcpy(build->tgargs[i], p);
+                               build->tgargs[i] = MemoryContextStrdup(CacheMemoryContext,
+                                                                                                          p);
                                p += strlen(p) + 1;
                        }
                }
@@ -504,35 +711,109 @@ RelationBuildTriggers(Relation relation)
                        build->tgargs = NULL;
 
                found++;
-               ReleaseBuffer(buffer);
        }
 
-       if (found < ntrigs)
-               elog(ERROR, "RelationBuildTriggers: %d record not found for rel %.*s",
-                        ntrigs - found,
-                        NAMEDATALEN, RelationGetRelationName(relation));
-
-       index_endscan(sd);
-       index_close(irel);
+       systable_endscan(tgscan);
        heap_close(tgrel, AccessShareLock);
 
+       if (found != ntrigs)
+               elog(ERROR, "RelationBuildTriggers: %d record(s) not found for rel %s",
+                        ntrigs - found,
+                        RelationGetRelationName(relation));
+
        /* Build trigdesc */
+       trigdesc = (TriggerDesc *) MemoryContextAlloc(CacheMemoryContext,
+                                                                                                 sizeof(TriggerDesc));
+       MemSet(trigdesc, 0, sizeof(TriggerDesc));
        trigdesc->triggers = triggers;
+       trigdesc->numtriggers = ntrigs;
        for (found = 0; found < ntrigs; found++)
+               InsertTrigger(trigdesc, &(triggers[found]), found);
+
+       relation->trigdesc = trigdesc;
+}
+
+/* Insert the given trigger into the appropriate index list(s) for it */
+static void
+InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx)
+{
+       uint16     *n;
+       int               **t,
+                         **tp;
+
+       if (TRIGGER_FOR_ROW(trigger->tgtype))
        {
-               build = &(triggers[found]);
-               DescribeTrigger(trigdesc, build);
+               /* ROW trigger */
+               if (TRIGGER_FOR_BEFORE(trigger->tgtype))
+               {
+                       n = trigdesc->n_before_row;
+                       t = trigdesc->tg_before_row;
+               }
+               else
+               {
+                       n = trigdesc->n_after_row;
+                       t = trigdesc->tg_after_row;
+               }
+       }
+       else
+       {
+               /* STATEMENT trigger */
+               if (TRIGGER_FOR_BEFORE(trigger->tgtype))
+               {
+                       n = trigdesc->n_before_statement;
+                       t = trigdesc->tg_before_statement;
+               }
+               else
+               {
+                       n = trigdesc->n_after_statement;
+                       t = trigdesc->tg_after_statement;
+               }
        }
 
-       relation->trigdesc = trigdesc;
+       if (TRIGGER_FOR_INSERT(trigger->tgtype))
+       {
+               tp = &(t[TRIGGER_EVENT_INSERT]);
+               if (*tp == NULL)
+                       *tp = (int *) MemoryContextAlloc(CacheMemoryContext,
+                                                                                        sizeof(int));
+               else
+                       *tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_INSERT] + 1) *
+                                                                  sizeof(int));
+               (*tp)[n[TRIGGER_EVENT_INSERT]] = indx;
+               (n[TRIGGER_EVENT_INSERT])++;
+       }
+
+       if (TRIGGER_FOR_DELETE(trigger->tgtype))
+       {
+               tp = &(t[TRIGGER_EVENT_DELETE]);
+               if (*tp == NULL)
+                       *tp = (int *) MemoryContextAlloc(CacheMemoryContext,
+                                                                                        sizeof(int));
+               else
+                       *tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_DELETE] + 1) *
+                                                                  sizeof(int));
+               (*tp)[n[TRIGGER_EVENT_DELETE]] = indx;
+               (n[TRIGGER_EVENT_DELETE])++;
+       }
 
+       if (TRIGGER_FOR_UPDATE(trigger->tgtype))
+       {
+               tp = &(t[TRIGGER_EVENT_UPDATE]);
+               if (*tp == NULL)
+                       *tp = (int *) MemoryContextAlloc(CacheMemoryContext,
+                                                                                        sizeof(int));
+               else
+                       *tp = (int *) repalloc(*tp, (n[TRIGGER_EVENT_UPDATE] + 1) *
+                                                                  sizeof(int));
+               (*tp)[n[TRIGGER_EVENT_UPDATE]] = indx;
+               (n[TRIGGER_EVENT_UPDATE])++;
+       }
 }
 
 void
-FreeTriggerDesc(Relation relation)
+FreeTriggerDesc(TriggerDesc *trigdesc)
 {
-       TriggerDesc *trigdesc = relation->trigdesc;
-       Trigger  ***t;
+       int               **t;
        Trigger    *trigger;
        int                     i;
 
@@ -540,24 +821,24 @@ FreeTriggerDesc(Relation relation)
                return;
 
        t = trigdesc->tg_before_statement;
-       for (i = 0; i < 3; i++)
+       for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
                if (t[i] != NULL)
                        pfree(t[i]);
        t = trigdesc->tg_before_row;
-       for (i = 0; i < 3; i++)
+       for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
                if (t[i] != NULL)
                        pfree(t[i]);
        t = trigdesc->tg_after_row;
-       for (i = 0; i < 3; i++)
+       for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
                if (t[i] != NULL)
                        pfree(t[i]);
        t = trigdesc->tg_after_statement;
-       for (i = 0; i < 3; i++)
+       for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
                if (t[i] != NULL)
                        pfree(t[i]);
 
        trigger = trigdesc->triggers;
-       for (i = 0; i < relation->rd_rel->reltriggers; i++)
+       for (i = 0; i < trigdesc->numtriggers; i++)
        {
                pfree(trigger->tgname);
                if (trigger->tgnargs > 0)
@@ -570,203 +851,262 @@ FreeTriggerDesc(Relation relation)
        }
        pfree(trigdesc->triggers);
        pfree(trigdesc);
-       relation->trigdesc = NULL;
-       return;
 }
 
-static void
-DescribeTrigger(TriggerDesc *trigdesc, Trigger *trigger)
+bool
+equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
 {
-       uint16     *n;
-       Trigger  ***t,
-                        ***tp;
+       int                     i,
+                               j;
 
-       if (TRIGGER_FOR_ROW(trigger->tgtype))           /* Is ROW/STATEMENT
-                                                                                                * trigger */
+       /*
+        * We need not examine the "index" data, just the trigger array
+        * itself; if we have the same triggers with the same types, the
+        * derived index data should match.
+        *
+        * As of 7.3 we assume trigger set ordering is significant in the
+        * comparison; so we just compare corresponding slots of the two sets.
+        */
+       if (trigdesc1 != NULL)
        {
-               if (TRIGGER_FOR_BEFORE(trigger->tgtype))
+               if (trigdesc2 == NULL)
+                       return false;
+               if (trigdesc1->numtriggers != trigdesc2->numtriggers)
+                       return false;
+               for (i = 0; i < trigdesc1->numtriggers; i++)
                {
-                       n = trigdesc->n_before_row;
-                       t = trigdesc->tg_before_row;
-               }
-               else
-               {
-                       n = trigdesc->n_after_row;
-                       t = trigdesc->tg_after_row;
-               }
-       }
-       else
-/* STATEMENT (NI) */
-       {
-               if (TRIGGER_FOR_BEFORE(trigger->tgtype))
-               {
-                       n = trigdesc->n_before_statement;
-                       t = trigdesc->tg_before_statement;
-               }
-               else
-               {
-                       n = trigdesc->n_after_statement;
-                       t = trigdesc->tg_after_statement;
+                       Trigger    *trig1 = trigdesc1->triggers + i;
+                       Trigger    *trig2 = trigdesc2->triggers + i;
+
+                       if (trig1->tgoid != trig2->tgoid)
+                               return false;
+                       if (strcmp(trig1->tgname, trig2->tgname) != 0)
+                               return false;
+                       if (trig1->tgfoid != trig2->tgfoid)
+                               return false;
+                       if (trig1->tgtype != trig2->tgtype)
+                               return false;
+                       if (trig1->tgenabled != trig2->tgenabled)
+                               return false;
+                       if (trig1->tgisconstraint != trig2->tgisconstraint)
+                               return false;
+                       if (trig1->tgconstrrelid != trig2->tgconstrrelid)
+                               return false;
+                       if (trig1->tgdeferrable != trig2->tgdeferrable)
+                               return false;
+                       if (trig1->tginitdeferred != trig2->tginitdeferred)
+                               return false;
+                       if (trig1->tgnargs != trig2->tgnargs)
+                               return false;
+                       if (memcmp(trig1->tgattr, trig2->tgattr,
+                                          sizeof(trig1->tgattr)) != 0)
+                               return false;
+                       for (j = 0; j < trig1->tgnargs; j++)
+                               if (strcmp(trig1->tgargs[j], trig2->tgargs[j]) != 0)
+                                       return false;
                }
        }
+       else if (trigdesc2 != NULL)
+               return false;
+       return true;
+}
 
-       if (TRIGGER_FOR_INSERT(trigger->tgtype))
-       {
-               tp = &(t[TRIGGER_EVENT_INSERT]);
-               if (*tp == NULL)
-                       *tp = (Trigger **) palloc(sizeof(Trigger *));
-               else
-                       *tp = (Trigger **) repalloc(*tp, (n[TRIGGER_EVENT_INSERT] + 1) *
-                                                                               sizeof(Trigger *));
-               (*tp)[n[TRIGGER_EVENT_INSERT]] = trigger;
-               (n[TRIGGER_EVENT_INSERT])++;
-       }
+/*
+ * Call a trigger function.
+ *
+ *             trigdata: trigger descriptor.
+ *             finfo: possibly-cached call info for the function.
+ *             per_tuple_context: memory context to execute the function in.
+ *
+ * Returns the tuple (or NULL) as returned by the function.
+ */
+static HeapTuple
+ExecCallTriggerFunc(TriggerData *trigdata,
+                                       FmgrInfo *finfo,
+                                       MemoryContext per_tuple_context)
+{
+       FunctionCallInfoData fcinfo;
+       Datum           result;
+       MemoryContext oldContext;
 
-       if (TRIGGER_FOR_DELETE(trigger->tgtype))
-       {
-               tp = &(t[TRIGGER_EVENT_DELETE]);
-               if (*tp == NULL)
-                       *tp = (Trigger **) palloc(sizeof(Trigger *));
-               else
-                       *tp = (Trigger **) repalloc(*tp, (n[TRIGGER_EVENT_DELETE] + 1) *
-                                                                               sizeof(Trigger *));
-               (*tp)[n[TRIGGER_EVENT_DELETE]] = trigger;
-               (n[TRIGGER_EVENT_DELETE])++;
-       }
+       /*
+        * We cache fmgr lookup info, to avoid making the lookup again on each
+        * call.
+        */
+       if (finfo->fn_oid == InvalidOid)
+               fmgr_info(trigdata->tg_trigger->tgfoid, finfo);
+
+       Assert(finfo->fn_oid == trigdata->tg_trigger->tgfoid);
+
+       /*
+        * Do the function evaluation in the per-tuple memory context, so that
+        * leaked memory will be reclaimed once per tuple. Note in particular
+        * that any new tuple created by the trigger function will live till
+        * the end of the tuple cycle.
+        */
+       oldContext = MemoryContextSwitchTo(per_tuple_context);
 
-       if (TRIGGER_FOR_UPDATE(trigger->tgtype))
-       {
-               tp = &(t[TRIGGER_EVENT_UPDATE]);
-               if (*tp == NULL)
-                       *tp = (Trigger **) palloc(sizeof(Trigger *));
-               else
-                       *tp = (Trigger **) repalloc(*tp, (n[TRIGGER_EVENT_UPDATE] + 1) *
-                                                                               sizeof(Trigger *));
-               (*tp)[n[TRIGGER_EVENT_UPDATE]] = trigger;
-               (n[TRIGGER_EVENT_UPDATE])++;
-       }
+       /*
+        * Call the function, passing no arguments but setting a context.
+        */
+       MemSet(&fcinfo, 0, sizeof(fcinfo));
 
-}
+       fcinfo.flinfo = finfo;
+       fcinfo.context = (Node *) trigdata;
 
-static HeapTuple
-ExecCallTriggerFunc(Trigger *trigger)
-{
+       result = FunctionCallInvoke(&fcinfo);
 
-       if (trigger->tgfunc.fn_addr == NULL)
-               fmgr_info(trigger->tgfoid, &trigger->tgfunc);
+       MemoryContextSwitchTo(oldContext);
 
-       if (trigger->tgfunc.fn_plhandler != NULL)
-       {
-               return (HeapTuple) (*(trigger->tgfunc.fn_plhandler))
-                       (&trigger->tgfunc);
-       }
+       /*
+        * Trigger protocol allows function to return a null pointer, but NOT
+        * to set the isnull result flag.
+        */
+       if (fcinfo.isnull)
+               elog(ERROR, "ExecCallTriggerFunc: function %u returned NULL",
+                        fcinfo.flinfo->fn_oid);
 
-       return (HeapTuple) ((*fmgr_faddr(&trigger->tgfunc)) ());
+       return (HeapTuple) DatumGetPointer(result);
 }
 
 HeapTuple
-ExecBRInsertTriggers(Relation rel, HeapTuple trigtuple)
+ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
+                                        HeapTuple trigtuple)
 {
-       TriggerData *SaveTriggerData;
-       int                     ntrigs = rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT];
-       Trigger   **trigger = rel->trigdesc->tg_before_row[TRIGGER_EVENT_INSERT];
+       TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+       int                     ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_INSERT];
+       int                *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_INSERT];
        HeapTuple       newtuple = trigtuple;
        HeapTuple       oldtuple;
+       TriggerData LocTriggerData;
        int                     i;
 
-       SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData));
-       SaveTriggerData->tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
-       SaveTriggerData->tg_relation = rel;
-       SaveTriggerData->tg_newtuple = NULL;
+       /* Allocate cache space for fmgr lookup info, if not done yet */
+       if (relinfo->ri_TrigFunctions == NULL)
+       {
+               relinfo->ri_TrigFunctions = (FmgrInfo *)
+                       palloc(trigdesc->numtriggers * sizeof(FmgrInfo));
+               MemSet(relinfo->ri_TrigFunctions, 0,
+                          trigdesc->numtriggers * sizeof(FmgrInfo));
+       }
+
+       LocTriggerData.type = T_TriggerData;
+       LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
+       LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+       LocTriggerData.tg_newtuple = NULL;
        for (i = 0; i < ntrigs; i++)
        {
-               if (!trigger[i]->tgenabled)
+               Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+
+               if (!trigger->tgenabled)
                        continue;
-               CurrentTriggerData = SaveTriggerData;
-               CurrentTriggerData->tg_trigtuple = oldtuple = newtuple;
-               CurrentTriggerData->tg_trigger = trigger[i];
-               newtuple = ExecCallTriggerFunc(trigger[i]);
+               LocTriggerData.tg_trigtuple = oldtuple = newtuple;
+               LocTriggerData.tg_trigger = trigger;
+               newtuple = ExecCallTriggerFunc(&LocTriggerData,
+                                                                  relinfo->ri_TrigFunctions + tgindx[i],
+                                                                          GetPerTupleMemoryContext(estate));
+               if (oldtuple != newtuple && oldtuple != trigtuple)
+                       heap_freetuple(oldtuple);
                if (newtuple == NULL)
                        break;
-               else if (oldtuple != newtuple && oldtuple != trigtuple)
-                       heap_freetuple(oldtuple);
        }
-       CurrentTriggerData = NULL;
-       pfree(SaveTriggerData);
        return newtuple;
 }
 
 void
-ExecARInsertTriggers(Relation rel, HeapTuple trigtuple)
+ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
+                                        HeapTuple trigtuple)
 {
-       DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_INSERT, NULL, trigtuple);
-       return;
+       TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+
+       if (trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
+               DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
+                                                                NULL, trigtuple);
 }
 
 bool
-ExecBRDeleteTriggers(EState *estate, ItemPointer tupleid)
+ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
+                                        ItemPointer tupleid)
 {
-       Relation        rel = estate->es_result_relation_info->ri_RelationDesc;
-       TriggerData *SaveTriggerData;
-       int                     ntrigs = rel->trigdesc->n_before_row[TRIGGER_EVENT_DELETE];
-       Trigger   **trigger = rel->trigdesc->tg_before_row[TRIGGER_EVENT_DELETE];
+       TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+       int                     ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_DELETE];
+       int                *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_DELETE];
+       TriggerData LocTriggerData;
        HeapTuple       trigtuple;
        HeapTuple       newtuple = NULL;
        TupleTableSlot *newSlot;
        int                     i;
 
-       trigtuple = GetTupleForTrigger(estate, tupleid, &newSlot);
+       trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
        if (trigtuple == NULL)
                return false;
 
-       SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData));
-       SaveTriggerData->tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
-       SaveTriggerData->tg_relation = rel;
-       SaveTriggerData->tg_newtuple = NULL;
+       /* Allocate cache space for fmgr lookup info, if not done yet */
+       if (relinfo->ri_TrigFunctions == NULL)
+       {
+               relinfo->ri_TrigFunctions = (FmgrInfo *)
+                       palloc(trigdesc->numtriggers * sizeof(FmgrInfo));
+               MemSet(relinfo->ri_TrigFunctions, 0,
+                          trigdesc->numtriggers * sizeof(FmgrInfo));
+       }
+
+       LocTriggerData.type = T_TriggerData;
+       LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
+       LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+       LocTriggerData.tg_newtuple = NULL;
        for (i = 0; i < ntrigs; i++)
        {
-               if (!trigger[i]->tgenabled)
+               Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+
+               if (!trigger->tgenabled)
                        continue;
-               CurrentTriggerData = SaveTriggerData;
-               CurrentTriggerData->tg_trigtuple = trigtuple;
-               CurrentTriggerData->tg_trigger = trigger[i];
-               newtuple = ExecCallTriggerFunc(trigger[i]);
+               LocTriggerData.tg_trigtuple = trigtuple;
+               LocTriggerData.tg_trigger = trigger;
+               newtuple = ExecCallTriggerFunc(&LocTriggerData,
+                                                                  relinfo->ri_TrigFunctions + tgindx[i],
+                                                                          GetPerTupleMemoryContext(estate));
                if (newtuple == NULL)
                        break;
                if (newtuple != trigtuple)
                        heap_freetuple(newtuple);
        }
-       CurrentTriggerData = NULL;
-       pfree(SaveTriggerData);
        heap_freetuple(trigtuple);
 
        return (newtuple == NULL) ? false : true;
 }
 
 void
-ExecARDeleteTriggers(EState *estate, ItemPointer tupleid)
+ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
+                                        ItemPointer tupleid)
 {
-       Relation        rel = estate->es_result_relation_info->ri_RelationDesc;
-       HeapTuple       trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
+       TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
-       DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_DELETE, trigtuple, NULL);
-       return;
+       if (trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
+       {
+               HeapTuple       trigtuple = GetTupleForTrigger(estate, relinfo,
+                                                                                                  tupleid, NULL);
+
+               DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
+                                                                trigtuple, NULL);
+               heap_freetuple(trigtuple);
+       }
 }
 
 HeapTuple
-ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
+ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
+                                        ItemPointer tupleid, HeapTuple newtuple)
 {
-       Relation        rel = estate->es_result_relation_info->ri_RelationDesc;
-       TriggerData *SaveTriggerData;
-       int                     ntrigs = rel->trigdesc->n_before_row[TRIGGER_EVENT_UPDATE];
-       Trigger   **trigger = rel->trigdesc->tg_before_row[TRIGGER_EVENT_UPDATE];
+       TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+       int                     ntrigs = trigdesc->n_before_row[TRIGGER_EVENT_UPDATE];
+       int                *tgindx = trigdesc->tg_before_row[TRIGGER_EVENT_UPDATE];
+       TriggerData LocTriggerData;
        HeapTuple       trigtuple;
        HeapTuple       oldtuple;
        HeapTuple       intuple = newtuple;
        TupleTableSlot *newSlot;
        int                     i;
 
-       trigtuple = GetTupleForTrigger(estate, tupleid, &newSlot);
+       trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, &newSlot);
        if (trigtuple == NULL)
                return NULL;
 
@@ -777,45 +1117,62 @@ ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
        if (newSlot != NULL)
                intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
 
-       SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData));
-       SaveTriggerData->tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
-       SaveTriggerData->tg_relation = rel;
+       /* Allocate cache space for fmgr lookup info, if not done yet */
+       if (relinfo->ri_TrigFunctions == NULL)
+       {
+               relinfo->ri_TrigFunctions = (FmgrInfo *)
+                       palloc(trigdesc->numtriggers * sizeof(FmgrInfo));
+               MemSet(relinfo->ri_TrigFunctions, 0,
+                          trigdesc->numtriggers * sizeof(FmgrInfo));
+       }
+
+       LocTriggerData.type = T_TriggerData;
+       LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
+       LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
        for (i = 0; i < ntrigs; i++)
        {
-               if (!trigger[i]->tgenabled)
+               Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+
+               if (!trigger->tgenabled)
                        continue;
-               CurrentTriggerData = SaveTriggerData;
-               CurrentTriggerData->tg_trigtuple = trigtuple;
-               CurrentTriggerData->tg_newtuple = oldtuple = newtuple;
-               CurrentTriggerData->tg_trigger = trigger[i];
-               newtuple = ExecCallTriggerFunc(trigger[i]);
+               LocTriggerData.tg_trigtuple = trigtuple;
+               LocTriggerData.tg_newtuple = oldtuple = newtuple;
+               LocTriggerData.tg_trigger = trigger;
+               newtuple = ExecCallTriggerFunc(&LocTriggerData,
+                                                                  relinfo->ri_TrigFunctions + tgindx[i],
+                                                                          GetPerTupleMemoryContext(estate));
+               if (oldtuple != newtuple && oldtuple != intuple)
+                       heap_freetuple(oldtuple);
                if (newtuple == NULL)
                        break;
-               else if (oldtuple != newtuple && oldtuple != intuple)
-                       heap_freetuple(oldtuple);
        }
-       CurrentTriggerData = NULL;
-       pfree(SaveTriggerData);
        heap_freetuple(trigtuple);
        return newtuple;
 }
 
 void
-ExecARUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple)
+ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
+                                        ItemPointer tupleid, HeapTuple newtuple)
 {
-       Relation        rel = estate->es_result_relation_info->ri_RelationDesc;
-       HeapTuple       trigtuple = GetTupleForTrigger(estate, tupleid, NULL);
+       TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+
+       if (trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
+       {
+               HeapTuple       trigtuple = GetTupleForTrigger(estate, relinfo,
+                                                                                                  tupleid, NULL);
 
-       DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_UPDATE, trigtuple, newtuple);
-       return;
+               DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
+                                                                trigtuple, newtuple);
+               heap_freetuple(trigtuple);
+       }
 }
 
-extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti, ItemPointer tid);
 
 static HeapTuple
-GetTupleForTrigger(EState *estate, ItemPointer tid, TupleTableSlot **newSlot)
+GetTupleForTrigger(EState *estate, ResultRelInfo *relinfo,
+                                  ItemPointer tid, TupleTableSlot **newSlot)
 {
-       Relation        relation = estate->es_result_relation_info->ri_RelationDesc;
+       Relation        relation = relinfo->ri_RelationDesc;
        HeapTupleData tuple;
        HeapTuple       result;
        Buffer          buffer;
@@ -830,7 +1187,8 @@ GetTupleForTrigger(EState *estate, ItemPointer tid, TupleTableSlot **newSlot)
                *newSlot = NULL;
                tuple.t_self = *tid;
 ltrmark:;
-               test = heap_mark4update(relation, &tuple, &buffer);
+               test = heap_mark4update(relation, &tuple, &buffer,
+                                                               GetCurrentCommandId());
                switch (test)
                {
                        case HeapTupleSelfUpdated:
@@ -847,7 +1205,7 @@ ltrmark:;
                                else if (!(ItemPointerEquals(&(tuple.t_self), tid)))
                                {
                                        TupleTableSlot *epqslot = EvalPlanQual(estate,
-                                        estate->es_result_relation_info->ri_RangeTableIndex,
+                                                                                        relinfo->ri_RangeTableIndex,
                                                                                                                &(tuple.t_self));
 
                                        if (!(TupIsNull(epqslot)))
@@ -906,37 +1264,41 @@ ltrmark:;
 
 /* ----------
  * Internal data to the deferred trigger mechanism is held
- * during entire session in a global memor created at startup and
- * over statements/commands in a separate global memory which
- * is created at transaction start and destroyed at transaction
- * end.
+ * during entire session in a global context created at startup and
+ * over statements/commands in a separate context which
+ * is created at transaction start and destroyed at transaction end.
  * ----------
  */
-static GlobalMemory            deftrig_gcxt = NULL;
-static GlobalMemory            deftrig_cxt = NULL;
+static MemoryContext deftrig_gcxt = NULL;
+static MemoryContext deftrig_cxt = NULL;
 
 /* ----------
  * Global data that tells which triggers are actually in
  * state IMMEDIATE or DEFERRED.
  * ----------
  */
-static bool                            deftrig_dfl_all_isset = false;
-static bool                            deftrig_dfl_all_isdeferred = false;
-static List                       *deftrig_dfl_trigstates = NIL;
+static bool deftrig_dfl_all_isset = false;
+static bool deftrig_dfl_all_isdeferred = false;
+static List *deftrig_dfl_trigstates = NIL;
 
-static bool                            deftrig_all_isset;
-static bool                            deftrig_all_isdeferred;
-static List                       *deftrig_trigstates;
+static bool deftrig_all_isset;
+static bool deftrig_all_isdeferred;
+static List *deftrig_trigstates;
 
 /* ----------
- * The list of events during the entire transaction.
+ * The list of pending deferred trigger events during the current transaction.
  *
- * XXX This must finally be held in a file because of the huge
- *     number of events that could occur in the real world.
+ * deftrig_events is the head, deftrig_event_tail is the last entry.
+ * Because this can grow pretty large, we don't use separate List nodes,
+ * but instead thread the list through the dte_next fields of the member
+ * nodes.  Saves just a few bytes per entry, but that adds up.
+ *
+ * XXX Need to be able to shove this data out to a file if it grows too
+ *        large...
  * ----------
  */
-static int                             deftrig_n_events;
-static List                       *deftrig_events;
+static DeferredTriggerEvent deftrig_events;
+static DeferredTriggerEvent deftrig_event_tail;
 
 
 /* ----------
@@ -949,51 +1311,45 @@ static List                         *deftrig_events;
 static bool
 deferredTriggerCheckState(Oid tgoid, int32 itemstate)
 {
-       MemoryContext                   oldcxt;
-       List                               *sl;
-       DeferredTriggerStatus   trigstate;
+       MemoryContext oldcxt;
+       List       *sl;
+       DeferredTriggerStatus trigstate;
 
-       /* ----------
-        * Not deferrable triggers (i.e. normal AFTER ROW triggers
-        * and constraints declared NOT DEFERRABLE, the state is
-        * allways false.
-        * ----------
+       /*
+        * Not deferrable triggers (i.e. normal AFTER ROW triggers and
+        * constraints declared NOT DEFERRABLE, the state is allways false.
         */
        if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
                return false;
 
-       /* ----------
+       /*
         * Lookup if we know an individual state for this trigger
-        * ----------
         */
-       foreach (sl, deftrig_trigstates)
+       foreach(sl, deftrig_trigstates)
        {
                trigstate = (DeferredTriggerStatus) lfirst(sl);
                if (trigstate->dts_tgoid == tgoid)
                        return trigstate->dts_tgisdeferred;
        }
 
-       /* ----------
-        * No individual state known - so if the user issued a
-        * SET CONSTRAINT ALL ..., we return that instead of the
-        * triggers default state.
-        * ----------
+       /*
+        * No individual state known - so if the user issued a SET CONSTRAINT
+        * ALL ..., we return that instead of the triggers default state.
         */
        if (deftrig_all_isset)
                return deftrig_all_isdeferred;
 
-       /* ----------
-        * No ALL state known either, remember the default state
-        * as the current and return that.
-        * ----------
+       /*
+        * No ALL state known either, remember the default state as the
+        * current and return that.
         */
-       oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
+       oldcxt = MemoryContextSwitchTo(deftrig_cxt);
 
        trigstate = (DeferredTriggerStatus)
-                       palloc(sizeof(DeferredTriggerStatusData));
-       trigstate->dts_tgoid            = tgoid;
-       trigstate->dts_tgisdeferred     = 
-                       ((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
+               palloc(sizeof(DeferredTriggerStatusData));
+       trigstate->dts_tgoid = tgoid;
+       trigstate->dts_tgisdeferred =
+               ((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
        deftrig_trigstates = lappend(deftrig_trigstates, trigstate);
 
        MemoryContextSwitchTo(oldcxt);
@@ -1008,156 +1364,139 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
  *     Add a new trigger event to the queue.
  * ----------
  */
-static void 
+static void
 deferredTriggerAddEvent(DeferredTriggerEvent event)
 {
-       deftrig_events = lappend(deftrig_events, event);
-       deftrig_n_events++;
-
-       return;
-}
-
-
-/* ----------
- * deferredTriggerGetPreviousEvent()
- *
- *     Backward scan the eventlist to find the event a given OLD tuple
- *     resulted from in the same transaction.
- * ----------
- */
-static DeferredTriggerEvent
-deferredTriggerGetPreviousEvent(Oid relid, ItemPointer ctid)
-{
-       DeferredTriggerEvent    previous;
-       int                                             n;
-
-       for (n = deftrig_n_events - 1; n >= 0; n--)
+       /*
+        * Since the event list could grow quite long, we keep track of the
+        * list tail and append there, rather than just doing a stupid
+        * "lappend". This avoids O(N^2) behavior for large numbers of events.
+        */
+       event->dte_next = NULL;
+       if (deftrig_event_tail == NULL)
        {
-               previous = (DeferredTriggerEvent) nth(n, deftrig_events);
-
-               if (previous->dte_relid != relid)
-                       continue;
-               if (previous->dte_event & TRIGGER_DEFERRED_CANCELED)
-                       continue;
-
-               if (ItemPointerGetBlockNumber(ctid) == 
-                                       ItemPointerGetBlockNumber(&(previous->dte_newctid)) &&
-                                       ItemPointerGetOffsetNumber(ctid) ==
-                                       ItemPointerGetOffsetNumber(&(previous->dte_newctid)))
-                       return previous;
+               /* first list entry */
+               deftrig_events = event;
+               deftrig_event_tail = event;
+       }
+       else
+       {
+               deftrig_event_tail->dte_next = event;
+               deftrig_event_tail = event;
        }
-
-       elog(ERROR, 
-               "deferredTriggerGetPreviousEvent(): event for tuple %s not found",
-               tidout(ctid));
-       return NULL;
 }
 
 
 /* ----------
- * deferredTriggerExecute()
+ * DeferredTriggerExecute()
  *
  *     Fetch the required tuples back from the heap and fire one
  *     single trigger function.
+ *
+ *     Frequently, this will be fired many times in a row for triggers of
+ *     a single relation.      Therefore, we cache the open relation and provide
+ *     fmgr lookup cache space at the caller level.
+ *
+ *     event: event currently being fired.
+ *     itemno: item within event currently being fired.
+ *     rel: open relation for event.
+ *     finfo: array of fmgr lookup cache entries (one per trigger of relation).
+ *     per_tuple_context: memory context to call trigger function in.
  * ----------
  */
 static void
-deferredTriggerExecute(DeferredTriggerEvent event, int itemno)
+DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
+                                          Relation rel, FmgrInfo *finfo,
+                                          MemoryContext per_tuple_context)
 {
-       Relation                rel;
-       TriggerData             SaveTriggerData;
-       HeapTupleData   oldtuple;
-       HeapTupleData   newtuple;
-       HeapTuple               rettuple;
-       Buffer                  oldbuffer;
-       Buffer                  newbuffer;
+       Oid                     tgoid = event->dte_item[itemno].dti_tgoid;
+       TriggerDesc *trigdesc = rel->trigdesc;
+       TriggerData LocTriggerData;
+       HeapTupleData oldtuple;
+       HeapTupleData newtuple;
+       HeapTuple       rettuple;
+       Buffer          oldbuffer;
+       Buffer          newbuffer;
+       int                     tgindx;
 
-       /* ----------
-        * Open the heap and fetch the required OLD and NEW tuples.
-        * ----------
+       /*
+        * Fetch the required OLD and NEW tuples.
         */
-       rel = heap_open(event->dte_relid, NoLock);
-
        if (ItemPointerIsValid(&(event->dte_oldctid)))
        {
                ItemPointerCopy(&(event->dte_oldctid), &(oldtuple.t_self));
-               heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer);
-               if (!oldtuple.t_data)
-                       elog(ERROR, "deferredTriggerExecute(): failed to fetch old tuple");
+               if (!heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer, false, NULL))
+                       elog(ERROR, "DeferredTriggerExecute: failed to fetch old tuple");
        }
 
        if (ItemPointerIsValid(&(event->dte_newctid)))
        {
                ItemPointerCopy(&(event->dte_newctid), &(newtuple.t_self));
-               heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer);
-               if (!newtuple.t_data)
-                       elog(ERROR, "deferredTriggerExecute(): failed to fetch new tuple");
+               if (!heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer, false, NULL))
+                       elog(ERROR, "DeferredTriggerExecute: failed to fetch new tuple");
        }
 
-       /* ----------
+       /*
         * Setup the trigger information
-        * ----------
         */
-       SaveTriggerData.tg_event    = (event->dte_event & TRIGGER_EVENT_OPMASK) |
-                                                                                                               TRIGGER_EVENT_ROW;
-       SaveTriggerData.tg_relation = rel;
+       LocTriggerData.type = T_TriggerData;
+       LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
+               TRIGGER_EVENT_ROW;
+       LocTriggerData.tg_relation = rel;
+
+       LocTriggerData.tg_trigger = NULL;
+       for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
+       {
+               if (trigdesc->triggers[tgindx].tgoid == tgoid)
+               {
+                       LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]);
+                       break;
+               }
+       }
+       if (LocTriggerData.tg_trigger == NULL)
+               elog(ERROR, "DeferredTriggerExecute: can't find trigger %u", tgoid);
 
        switch (event->dte_event & TRIGGER_EVENT_OPMASK)
        {
                case TRIGGER_EVENT_INSERT:
-                       SaveTriggerData.tg_trigtuple = &newtuple;
-                       SaveTriggerData.tg_newtuple  = NULL;
-                       SaveTriggerData.tg_trigger   = 
-                                       rel->trigdesc->tg_after_row[TRIGGER_EVENT_INSERT][itemno];
+                       LocTriggerData.tg_trigtuple = &newtuple;
+                       LocTriggerData.tg_newtuple = NULL;
                        break;
 
                case TRIGGER_EVENT_UPDATE:
-                       SaveTriggerData.tg_trigtuple = &oldtuple;
-                       SaveTriggerData.tg_newtuple  = &newtuple;
-                       SaveTriggerData.tg_trigger   = 
-                                       rel->trigdesc->tg_after_row[TRIGGER_EVENT_UPDATE][itemno];
+                       LocTriggerData.tg_trigtuple = &oldtuple;
+                       LocTriggerData.tg_newtuple = &newtuple;
                        break;
 
                case TRIGGER_EVENT_DELETE:
-                       SaveTriggerData.tg_trigtuple = &oldtuple;
-                       SaveTriggerData.tg_newtuple  = NULL;
-                       SaveTriggerData.tg_trigger   = 
-                                       rel->trigdesc->tg_after_row[TRIGGER_EVENT_DELETE][itemno];
+                       LocTriggerData.tg_trigtuple = &oldtuple;
+                       LocTriggerData.tg_newtuple = NULL;
                        break;
+       }
 
-               default:
-       } 
-
-       /* ----------
-        * Call the trigger and throw away an eventually returned
-        * updated tuple.
-        * ----------
+       /*
+        * Call the trigger and throw away an eventually returned updated
+        * tuple.
         */
-       CurrentTriggerData = &SaveTriggerData;
-       rettuple = ExecCallTriggerFunc(SaveTriggerData.tg_trigger);
-       CurrentTriggerData = NULL;
+       rettuple = ExecCallTriggerFunc(&LocTriggerData,
+                                                                  finfo + tgindx,
+                                                                  per_tuple_context);
        if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple)
                heap_freetuple(rettuple);
 
-       /* ----------
-        * Might have been a referential integrity constraint trigger.
-        * Reset the snapshot overriding flag.
-        * ----------
+       /*
+        * Might have been a referential integrity constraint trigger. Reset
+        * the snapshot overriding flag.
         */
        ReferentialIntegritySnapshotOverride = false;
 
-       /* ----------
-        * Release buffers and close the relation
-        * ----------
+       /*
+        * Release buffers
         */
        if (ItemPointerIsValid(&(event->dte_oldctid)))
                ReleaseBuffer(oldbuffer);
        if (ItemPointerIsValid(&(event->dte_newctid)))
                ReleaseBuffer(newbuffer);
-
-       heap_close(rel, NoLock);
-
-       return;
 }
 
 
@@ -1171,73 +1510,152 @@ deferredTriggerExecute(DeferredTriggerEvent event, int itemno)
 static void
 deferredTriggerInvokeEvents(bool immediate_only)
 {
-       List                                    *el;
-       DeferredTriggerEvent    event;
-       int                                             still_deferred_ones;
-       int                                             eventno = -1;
-       int                                             i;
+       DeferredTriggerEvent event,
+                               prev_event = NULL;
+       MemoryContext per_tuple_context;
+       Relation        rel = NULL;
+       FmgrInfo   *finfo = NULL;
 
-       /* ----------
-        * For now we process all events - to speedup transaction blocks
-        * we need to remember the actual end of the queue at EndQuery
-        * and process only events that are newer. On state changes we
-        * simply reset the position to the beginning of the queue and
-        * process all events once with the new states when the
-        * SET CONSTRAINTS ... command finishes and calls EndQuery.
-        * ----------
+       /*
+        * If immediate_only is true, we remove fully-processed events from
+        * the event queue to recycle space.  If immediate_only is false,
+        * we are going to discard the whole event queue on return anyway,
+        * so no need to bother with "retail" pfree's.
+        *
+        * In a scenario with many commands in a transaction and many
+        * deferred-to-end-of-transaction triggers, it could get annoying
+        * to rescan all the deferred triggers at each command end.
+        * To speed this up, we could remember the actual end of the queue at
+        * EndQuery and examine only events that are newer. On state changes
+        * we simply reset the saved position to the beginning of the queue
+        * and process all events once with the new states.
         */
-       foreach (el, deftrig_events)
+
+       /* Make a per-tuple memory context for trigger function calls */
+       per_tuple_context =
+               AllocSetContextCreate(CurrentMemoryContext,
+                                                         "DeferredTriggerTupleContext",
+                                                         ALLOCSET_DEFAULT_MINSIZE,
+                                                         ALLOCSET_DEFAULT_INITSIZE,
+                                                         ALLOCSET_DEFAULT_MAXSIZE);
+
+       event = deftrig_events;
+       while (event != NULL)
        {
-               eventno++;
+               bool            still_deferred_ones = false;
+               DeferredTriggerEvent next_event;
+               int                     i;
 
-               /* ----------
-                * Get the event and check if it is completely done.
-                * ----------
-                */
-               event = (DeferredTriggerEvent) lfirst(el);
-               if (event->dte_event & (TRIGGER_DEFERRED_DONE | 
-                                                               TRIGGER_DEFERRED_CANCELED))
-                       continue;
-       
-               /* ----------
-                * Check each trigger item in the event.
-                * ----------
+               /*
+                * Check if event is already completely done.
                 */
-               still_deferred_ones = false;
-               for (i = 0; i < event->dte_n_items; i++)
+               if (! (event->dte_event & (TRIGGER_DEFERRED_DONE |
+                                                                  TRIGGER_DEFERRED_CANCELED)))
                {
-                       if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE)
-                               continue;
+                       MemoryContextReset(per_tuple_context);
 
-                       /* ----------
-                        * This trigger item hasn't been called yet. Check if
-                        * we should call it now.
-                        * ----------
+                       /*
+                        * Check each trigger item in the event.
                         */
-                       if (immediate_only && deferredTriggerCheckState(
-                                                               event->dte_item[i].dti_tgoid, 
-                                                               event->dte_item[i].dti_state))
+                       for (i = 0; i < event->dte_n_items; i++)
                        {
-                               still_deferred_ones = true;
-                               continue;
-                       }
+                               if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE)
+                                       continue;
 
-                       /* ----------
-                        * So let's fire it...
-                        * ----------
-                        */
-                       deferredTriggerExecute(event, i);
-                       event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
+                               /*
+                                * This trigger item hasn't been called yet. Check if we
+                                * should call it now.
+                                */
+                               if (immediate_only &&
+                                       deferredTriggerCheckState(event->dte_item[i].dti_tgoid,
+                                                                                         event->dte_item[i].dti_state))
+                               {
+                                       still_deferred_ones = true;
+                                       continue;
+                               }
+
+                               /*
+                                * So let's fire it... but first, open the correct relation
+                                * if this is not the same relation as before.
+                                */
+                               if (rel == NULL || rel->rd_id != event->dte_relid)
+                               {
+                                       if (rel)
+                                               heap_close(rel, NoLock);
+                                       if (finfo)
+                                               pfree(finfo);
+
+                                       /*
+                                        * We assume that an appropriate lock is still held by the
+                                        * executor, so grab no new lock here.
+                                        */
+                                       rel = heap_open(event->dte_relid, NoLock);
+
+                                       /*
+                                        * Allocate space to cache fmgr lookup info for triggers
+                                        * of this relation.
+                                        */
+                                       finfo = (FmgrInfo *)
+                                               palloc(rel->trigdesc->numtriggers * sizeof(FmgrInfo));
+                                       MemSet(finfo, 0,
+                                                  rel->trigdesc->numtriggers * sizeof(FmgrInfo));
+                               }
+
+                               DeferredTriggerExecute(event, i, rel, finfo,
+                                                                          per_tuple_context);
+
+                               event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
+                       } /* end loop over items within event */
                }
 
-               /* ----------
-                * Remember in the event itself if all trigger items are
-                * done.
-                * ----------
+               /*
+                * If it's now completely done, throw it away.
+                *
+                * NB: it's possible the trigger calls above added more events to the
+                * queue, or that calls we will do later will want to add more,
+                * so we have to be careful about maintaining list validity here.
                 */
-               if (!still_deferred_ones)
-                       event->dte_event |= TRIGGER_DEFERRED_DONE;
+               next_event = event->dte_next;
+
+               if (still_deferred_ones)
+               {
+                       /* Not done, keep in list */
+                       prev_event = event;
+               }
+               else
+               {
+                       /* Done */
+                       if (immediate_only)
+                       {
+                               /* delink it from list and free it */
+                               if (prev_event)
+                                       prev_event->dte_next = next_event;
+                               else
+                                       deftrig_events = next_event;
+                               pfree(event);
+                       }
+                       else
+                       {
+                               /*
+                                * We will clean up later, but just for paranoia's sake,
+                                * mark the event done.
+                                */
+                               event->dte_event |= TRIGGER_DEFERRED_DONE;
+                       }
+               }
+
+               event = next_event;
        }
+
+       /* Update list tail pointer in case we just deleted tail event */
+       deftrig_event_tail = prev_event;
+
+       /* Release working resources */
+       if (rel)
+               heap_close(rel, NoLock);
+       if (finfo)
+               pfree(finfo);
+       MemoryContextDelete(per_tuple_context);
 }
 
 
@@ -1249,11 +1667,18 @@ deferredTriggerInvokeEvents(bool immediate_only)
  *     transactions.
  * ----------
  */
-int
+void
 DeferredTriggerInit(void)
 {
-       deftrig_gcxt = CreateGlobalMemory("DeferredTriggerSession");
-       return 0;
+       /*
+        * Since this context will never be reset, give it a minsize of 0.
+        * This avoids using any memory if the session never stores anything.
+        */
+       deftrig_gcxt = AllocSetContextCreate(TopMemoryContext,
+                                                                                "DeferredTriggerSession",
+                                                                                0,
+                                                                                ALLOCSET_DEFAULT_INITSIZE,
+                                                                                ALLOCSET_DEFAULT_MAXSIZE);
 }
 
 
@@ -1267,34 +1692,38 @@ DeferredTriggerInit(void)
 void
 DeferredTriggerBeginXact(void)
 {
-       MemoryContext                   oldcxt;
-       List                                    *l;
-       DeferredTriggerStatus   dflstat;
-       DeferredTriggerStatus   stat;
+       MemoryContext oldcxt;
+       List       *l;
+       DeferredTriggerStatus dflstat;
+       DeferredTriggerStatus stat;
 
        if (deftrig_cxt != NULL)
-               elog(FATAL, 
-                       "DeferredTriggerBeginXact() called while inside transaction");
+               elog(ERROR,
+                  "DeferredTriggerBeginXact() called while inside transaction");
 
-       /* ----------
-        * Create the per transaction memory context and copy all states
-        * from the per session context to here.
-        * ----------
+       /*
+        * Create the per transaction memory context and copy all states from
+        * the per session context to here.  Set the minsize to 0 to avoid
+        * wasting memory if there is no deferred trigger data.
         */
-       deftrig_cxt                             = CreateGlobalMemory("DeferredTriggerXact");
-       oldcxt = MemoryContextSwitchTo((MemoryContext)deftrig_cxt);
-
-       deftrig_all_isset               = deftrig_dfl_all_isset;
-       deftrig_all_isdeferred  = deftrig_dfl_all_isdeferred;
-       
-       deftrig_trigstates              = NIL;
-       foreach (l, deftrig_dfl_trigstates)
+       deftrig_cxt = AllocSetContextCreate(TopTransactionContext,
+                                                                               "DeferredTriggerXact",
+                                                                               0,
+                                                                               ALLOCSET_DEFAULT_INITSIZE,
+                                                                               ALLOCSET_DEFAULT_MAXSIZE);
+       oldcxt = MemoryContextSwitchTo(deftrig_cxt);
+
+       deftrig_all_isset = deftrig_dfl_all_isset;
+       deftrig_all_isdeferred = deftrig_dfl_all_isdeferred;
+
+       deftrig_trigstates = NIL;
+       foreach(l, deftrig_dfl_trigstates)
        {
                dflstat = (DeferredTriggerStatus) lfirst(l);
-               stat    = (DeferredTriggerStatus) 
-                                                               palloc(sizeof(DeferredTriggerStatusData));
+               stat = (DeferredTriggerStatus)
+                       palloc(sizeof(DeferredTriggerStatusData));
 
-               stat->dts_tgoid        = dflstat->dts_tgoid;
+               stat->dts_tgoid = dflstat->dts_tgoid;
                stat->dts_tgisdeferred = dflstat->dts_tgisdeferred;
 
                deftrig_trigstates = lappend(deftrig_trigstates, stat);
@@ -1302,8 +1731,8 @@ DeferredTriggerBeginXact(void)
 
        MemoryContextSwitchTo(oldcxt);
 
-       deftrig_n_events        = 0;
-       deftrig_events          = NIL;
+       deftrig_events = NULL;
+       deftrig_event_tail = NULL;
 }
 
 
@@ -1317,9 +1746,8 @@ DeferredTriggerBeginXact(void)
 void
 DeferredTriggerEndQuery(void)
 {
-       /* ----------
+       /*
         * Ignore call if we aren't in a transaction.
-        * ----------
         */
        if (deftrig_cxt == NULL)
                return;
@@ -1338,16 +1766,15 @@ DeferredTriggerEndQuery(void)
 void
 DeferredTriggerEndXact(void)
 {
-       /* ----------
+       /*
         * Ignore call if we aren't in a transaction.
-        * ----------
         */
        if (deftrig_cxt == NULL)
                return;
 
        deferredTriggerInvokeEvents(false);
 
-       GlobalMemoryDestroy(deftrig_cxt);
+       MemoryContextDelete(deftrig_cxt);
        deftrig_cxt = NULL;
 }
 
@@ -1363,14 +1790,13 @@ DeferredTriggerEndXact(void)
 void
 DeferredTriggerAbortXact(void)
 {
-       /* ----------
+       /*
         * Ignore call if we aren't in a transaction.
-        * ----------
         */
        if (deftrig_cxt == NULL)
                return;
 
-       GlobalMemoryDestroy(deftrig_cxt);
+       MemoryContextDelete(deftrig_cxt);
        deftrig_cxt = NULL;
 }
 
@@ -1384,85 +1810,71 @@ DeferredTriggerAbortXact(void)
 void
 DeferredTriggerSetState(ConstraintsSetStmt *stmt)
 {
-       Relation                                tgrel;
-       Relation                                irel;
-       List                                    *l;
-       List                                    *ls;
-       List                                    *lnext;
-       List                                    *loid = NIL;
-       MemoryContext                   oldcxt;
-       bool                                    found;
-       DeferredTriggerStatus   state;
+       Relation        tgrel;
+       List       *l;
+       List       *ls;
+       List       *loid = NIL;
+       MemoryContext oldcxt;
+       bool            found;
+       DeferredTriggerStatus state;
 
-       /* ----------
+       /*
         * Handle SET CONSTRAINTS ALL ...
-        * ----------
         */
-       if (stmt->constraints == NIL) {
+       if (stmt->constraints == NIL)
+       {
                if (!IsTransactionBlock())
                {
-                       /* ----------
+                       /*
                         * ... outside of a transaction block
-                        * ----------
-                        */
-                       oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_gcxt);
-
-                       /* ----------
+                        *
                         * Drop all information about individual trigger states per
                         * session.
-                        * ----------
                         */
                        l = deftrig_dfl_trigstates;
                        while (l != NIL)
                        {
-                               lnext = lnext(l);
+                               List       *next = lnext(l);
+
                                pfree(lfirst(l));
                                pfree(l);
-                               l = lnext;
+                               l = next;
                        }
                        deftrig_dfl_trigstates = NIL;
 
-                       /* ----------
+                       /*
                         * Set the session ALL state to known.
-                        * ----------
                         */
-                       deftrig_dfl_all_isset      = true;
+                       deftrig_dfl_all_isset = true;
                        deftrig_dfl_all_isdeferred = stmt->deferred;
 
-                       MemoryContextSwitchTo(oldcxt);
-
                        return;
-               } else {
-                       /* ----------
+               }
+               else
+               {
+                       /*
                         * ... inside of a transaction block
-                        * ----------
-                        */
-                       oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
-
-                       /* ----------
+                        *
                         * Drop all information about individual trigger states per
                         * transaction.
-                        * ----------
                         */
                        l = deftrig_trigstates;
                        while (l != NIL)
                        {
-                               lnext = lnext(l);
+                               List       *next = lnext(l);
+
                                pfree(lfirst(l));
                                pfree(l);
-                               l = lnext;
+                               l = next;
                        }
                        deftrig_trigstates = NIL;
 
-                       /* ----------
+                       /*
                         * Set the per transaction ALL state to known.
-                        * ----------
                         */
-                       deftrig_all_isset      = true;
+                       deftrig_all_isset = true;
                        deftrig_all_isdeferred = stmt->deferred;
 
-                       MemoryContextSwitchTo(oldcxt);
-
                        return;
                }
        }
@@ -1473,102 +1885,83 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
         * ----------
         */
        tgrel = heap_openr(TriggerRelationName, AccessShareLock);
-       irel = index_openr(TriggerConstrNameIndex);
 
-       foreach (l, stmt->constraints)
+       foreach(l, stmt->constraints)
        {
-               ScanKeyData                     skey;
-               HeapTupleData           tuple;
-               IndexScanDesc           sd;
-               RetrieveIndexResult     indexRes;
-               Buffer                          buffer;
-               Form_pg_trigger         pg_trigger;
-               Oid                                     constr_oid;
-
-               /* ----------
+               char       *cname = strVal(lfirst(l));
+               ScanKeyData skey;
+               SysScanDesc     tgscan;
+               HeapTuple       htup;
+
+               /*
                 * Check that only named constraints are set explicitly
-                * ----------
                 */
-               if (strcmp((char *)lfirst(l), "") == 0)
+               if (strlen(cname) == 0)
                        elog(ERROR, "unnamed constraints cannot be set explicitly");
 
-               /* ----------
+               /*
                 * Setup to scan pg_trigger by tgconstrname ...
-                * ----------
                 */
                ScanKeyEntryInitialize(&skey,
                                                           (bits16) 0x0,
-                                                          (AttrNumber) 1,
+                                                          (AttrNumber) Anum_pg_trigger_tgconstrname,
                                                           (RegProcedure) F_NAMEEQ,
-                                                          PointerGetDatum((char *)lfirst(l)));
+                                                          PointerGetDatum(cname));
 
-               sd = index_beginscan(irel, false, 1, &skey);
+               tgscan = systable_beginscan(tgrel, TriggerConstrNameIndex, true,
+                                                                       SnapshotNow, 1, &skey);
 
-               /* ----------
+               /*
                 * ... and search for the constraint trigger row
-                * ----------
                 */
                found = false;
-               for (;;)
-               {
-                       indexRes = index_getnext(sd, ForwardScanDirection);
-                       if (!indexRes)
-                               break;
 
-                       tuple.t_self = indexRes->heap_iptr;
-                       heap_fetch(tgrel, SnapshotNow, &tuple, &buffer);
-                       pfree(indexRes);
-                       if (!tuple.t_data)
-                       {
-                               ReleaseBuffer(buffer);
-                               continue;
-                       }
+               while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
+               {
+                       Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
+                       Oid                     constr_oid;
 
-                       /* ----------
-                        * If we found some, check that they fit the deferrability
-                        * ----------
+                       /*
+                        * If we found some, check that they fit the deferrability but
+                        * skip ON <event> RESTRICT ones, since they are silently
+                        * never deferrable.
                         */
-                       pg_trigger = (Form_pg_trigger) GETSTRUCT(&tuple);
-                       if (stmt->deferred & !pg_trigger->tgdeferrable)
-                               elog(ERROR, "Constraint '%s' is not deferrable", 
-                                                                       (char *)lfirst(l));
-
-                       constr_oid = tuple.t_data->t_oid;
-                       loid = lappend(loid, (Node *)constr_oid);
+                       if (stmt->deferred && !pg_trigger->tgdeferrable &&
+                               pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_UPD &&
+                               pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_DEL)
+                               elog(ERROR, "Constraint '%s' is not deferrable",
+                                        cname);
+
+                       constr_oid = htup->t_data->t_oid;
+                       loid = lappendi(loid, constr_oid);
                        found = true;
-
-                       ReleaseBuffer(buffer);
                }
 
-               /* ----------
+               systable_endscan(tgscan);
+
+               /*
                 * Not found ?
-                * ----------
                 */
                if (!found)
-                       elog(ERROR, "Constraint '%s' does not exist", (char *)lfirst(l));
-
-               index_endscan(sd);
+                       elog(ERROR, "Constraint '%s' does not exist", cname);
        }
-       index_close(irel);
        heap_close(tgrel, AccessShareLock);
 
-
        if (!IsTransactionBlock())
        {
-               /* ----------
-                * Outside of a transaction block set the trigger
-                * states of individual triggers on session level.
-                * ----------
+               /*
+                * Outside of a transaction block set the trigger states of
+                * individual triggers on session level.
                 */
-               oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_gcxt);
+               oldcxt = MemoryContextSwitchTo(deftrig_gcxt);
 
-               foreach (l, loid)
+               foreach(l, loid)
                {
                        found = false;
-                       foreach (ls, deftrig_dfl_trigstates)
+                       foreach(ls, deftrig_dfl_trigstates)
                        {
                                state = (DeferredTriggerStatus) lfirst(ls);
-                               if (state->dts_tgoid == (Oid) lfirst(l))
+                               if (state->dts_tgoid == (Oid) lfirsti(l))
                                {
                                        state->dts_tgisdeferred = stmt->deferred;
                                        found = true;
@@ -1578,33 +1971,34 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
                        if (!found)
                        {
                                state = (DeferredTriggerStatus)
-                                                                       palloc(sizeof(DeferredTriggerStatusData));
-                               state->dts_tgoid        = (Oid) lfirst(l);
+                                       palloc(sizeof(DeferredTriggerStatusData));
+                               state->dts_tgoid = (Oid) lfirsti(l);
                                state->dts_tgisdeferred = stmt->deferred;
 
-                               deftrig_dfl_trigstates = 
-                                                                       lappend(deftrig_dfl_trigstates, state);
+                               deftrig_dfl_trigstates =
+                                       lappend(deftrig_dfl_trigstates, state);
                        }
                }
 
                MemoryContextSwitchTo(oldcxt);
 
                return;
-       } else {
-               /* ----------
-                * Inside of a transaction block set the trigger
-                * states of individual triggers on transaction level.
-                * ----------
+       }
+       else
+       {
+               /*
+                * Inside of a transaction block set the trigger states of
+                * individual triggers on transaction level.
                 */
-               oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
+               oldcxt = MemoryContextSwitchTo(deftrig_cxt);
 
-               foreach (l, loid)
+               foreach(l, loid)
                {
                        found = false;
-                       foreach (ls, deftrig_trigstates)
+                       foreach(ls, deftrig_trigstates)
                        {
                                state = (DeferredTriggerStatus) lfirst(ls);
-                               if (state->dts_tgoid == (Oid) lfirst(l))
+                               if (state->dts_tgoid == (Oid) lfirsti(l))
                                {
                                        state->dts_tgisdeferred = stmt->deferred;
                                        found = true;
@@ -1614,12 +2008,12 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
                        if (!found)
                        {
                                state = (DeferredTriggerStatus)
-                                                                       palloc(sizeof(DeferredTriggerStatusData));
-                               state->dts_tgoid        = (Oid) lfirst(l);
+                                       palloc(sizeof(DeferredTriggerStatusData));
+                               state->dts_tgoid = (Oid) lfirsti(l);
                                state->dts_tgisdeferred = stmt->deferred;
 
-                               deftrig_trigstates = 
-                                                                       lappend(deftrig_trigstates, state);
+                               deftrig_trigstates =
+                                       lappend(deftrig_trigstates, state);
                        }
                }
 
@@ -1634,42 +2028,33 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
  * DeferredTriggerSaveEvent()
  *
  *     Called by ExecAR...Triggers() to add the event to the queue.
+ *
+ *     NOTE: should be called only if we've determined that an event must
+ *     be added to the queue.
  * ----------
  */
-void
-DeferredTriggerSaveEvent(Relation rel, int event,
-                                       HeapTuple oldtup, HeapTuple newtup)
+static void
+DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
+                                                HeapTuple oldtup, HeapTuple newtup)
 {
-       MemoryContext                   oldcxt;
-       DeferredTriggerEvent    new_event;
-       DeferredTriggerEvent    prev_event;
-       int                                             new_size;
-       int                                             i;
-       int                                             ntriggers;
-       Trigger                           **triggers;
-       ItemPointerData                 oldctid;
-       ItemPointerData                 newctid;
-       TriggerData                             SaveTriggerData;
+       Relation        rel = relinfo->ri_RelationDesc;
+       TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+       MemoryContext oldcxt;
+       DeferredTriggerEvent new_event;
+       int                     new_size;
+       int                     i;
+       int                     ntriggers;
+       int                *tgindx;
+       ItemPointerData oldctid;
+       ItemPointerData newctid;
+       TriggerData LocTriggerData;
 
        if (deftrig_cxt == NULL)
-               elog(ERROR, 
-                       "DeferredTriggerSaveEvent() called outside of transaction");
-
-       /* ----------
-        * Check if we're interested in this row at all
-        * ----------
-        */
-       if (rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] == 0 &&
-                               rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] == 0 &&
-                               rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] == 0 &&
-                               rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] == 0 &&
-                               rel->trigdesc->n_before_row[TRIGGER_EVENT_UPDATE] == 0 &&
-                               rel->trigdesc->n_before_row[TRIGGER_EVENT_DELETE] == 0)
-               return;
+               elog(ERROR,
+                        "DeferredTriggerSaveEvent() called outside of transaction");
 
-       /* ----------
+       /*
         * Get the CTID's of OLD and NEW
-        * ----------
         */
        if (oldtup != NULL)
                ItemPointerCopy(&(oldtup->t_self), &(oldctid));
@@ -1680,86 +2065,68 @@ DeferredTriggerSaveEvent(Relation rel, int event,
        else
                ItemPointerSetInvalid(&(newctid));
 
-       /* ----------
-        * Create a new event 
-        * ----------
+       /*
+        * Create a new event
         */
-       oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
+       oldcxt = MemoryContextSwitchTo(deftrig_cxt);
 
-       ntriggers = rel->trigdesc->n_after_row[event];
-       triggers  = rel->trigdesc->tg_after_row[event];
+       ntriggers = trigdesc->n_after_row[event];
+       tgindx = trigdesc->tg_after_row[event];
+       new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
+               ntriggers * sizeof(DeferredTriggerEventItem);
 
-       new_size  = sizeof(DeferredTriggerEventData) +
-                               ntriggers * sizeof(DeferredTriggerEventItem);
-                               
        new_event = (DeferredTriggerEvent) palloc(new_size);
-       new_event->dte_event    = event & TRIGGER_EVENT_OPMASK;
-       new_event->dte_relid    = rel->rd_id;
+       new_event->dte_next = NULL;
+       new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
+       new_event->dte_relid = rel->rd_id;
        ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
        ItemPointerCopy(&newctid, &(new_event->dte_newctid));
        new_event->dte_n_items = ntriggers;
-       new_event->dte_item[ntriggers].dti_state = new_size;
        for (i = 0; i < ntriggers; i++)
        {
-               new_event->dte_item[i].dti_tgoid = triggers[i]->tgoid;
-               new_event->dte_item[i].dti_state = 
-                       ((triggers[i]->tgdeferrable) ? 
-                                               TRIGGER_DEFERRED_DEFERRABLE : 0)        |
-                       ((triggers[i]->tginitdeferred) ? 
-                                               TRIGGER_DEFERRED_INITDEFERRED : 0)      |
-                       ((rel->trigdesc->n_before_row[event] > 0) ?
-                                               TRIGGER_DEFERRED_HAS_BEFORE : 0);
+               Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+
+               new_event->dte_item[i].dti_tgoid = trigger->tgoid;
+               new_event->dte_item[i].dti_state =
+                       ((trigger->tgdeferrable) ?
+                        TRIGGER_DEFERRED_DEFERRABLE : 0) |
+                       ((trigger->tginitdeferred) ?
+                        TRIGGER_DEFERRED_INITDEFERRED : 0) |
+                       ((trigdesc->n_before_row[event] > 0) ?
+                        TRIGGER_DEFERRED_HAS_BEFORE : 0);
        }
+
        MemoryContextSwitchTo(oldcxt);
 
        switch (event & TRIGGER_EVENT_OPMASK)
        {
                case TRIGGER_EVENT_INSERT:
-                       new_event->dte_event |= TRIGGER_DEFERRED_ROW_INSERTED;
-                       new_event->dte_event |= TRIGGER_DEFERRED_KEY_CHANGED;
+                       /* nothing to do */
                        break;
 
                case TRIGGER_EVENT_UPDATE:
-                       /* ----------
-                        * On UPDATE check if the tuple updated has been inserted
-                        * or a foreign referenced key value that's changing now
-                        * has been updated once before in this transaction.
-                        * ----------
-                        */
-                       if (oldtup->t_data->t_xmin != GetCurrentTransactionId())
-                               prev_event = NULL;
-                       else
-                               prev_event = 
-                                       deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid);
-
-                       /* ----------
-                        * Now check if one of the referenced keys is changed.
-                        * ----------
+                       /*
+                        * Check if one of the referenced keys is changed.
                         */
                        for (i = 0; i < ntriggers; i++)
                        {
-                               bool    is_ri_trigger;
-                               bool    key_unchanged;
+                               Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+                               bool            is_ri_trigger;
+                               bool            key_unchanged;
 
-                               /* ----------
+                               /*
                                 * We are interested in RI_FKEY triggers only.
-                                * ----------
                                 */
-                               switch (triggers[i]->tgfoid)
+                               switch (trigger->tgfoid)
                                {
                                        case F_RI_FKEY_NOACTION_UPD:
-                                               is_ri_trigger = true;
-                                               new_event->dte_item[i].dti_state |=
-                                                                                       TRIGGER_DEFERRED_DONE;
-                                               break;
-
                                        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;
                                                break;
-                                       
+
                                        default:
                                                is_ri_trigger = false;
                                                break;
@@ -1767,129 +2134,34 @@ DeferredTriggerSaveEvent(Relation rel, int event,
                                if (!is_ri_trigger)
                                        continue;
 
-                               SaveTriggerData.tg_event     = TRIGGER_EVENT_UPDATE;
-                               SaveTriggerData.tg_relation  = rel;
-                               SaveTriggerData.tg_trigtuple = oldtup;
-                               SaveTriggerData.tg_newtuple  = newtup;
-                               SaveTriggerData.tg_trigger   = triggers[i];
+                               LocTriggerData.type = T_TriggerData;
+                               LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE;
+                               LocTriggerData.tg_relation = rel;
+                               LocTriggerData.tg_trigtuple = oldtup;
+                               LocTriggerData.tg_newtuple = newtup;
+                               LocTriggerData.tg_trigger = trigger;
+
+                               key_unchanged = RI_FKey_keyequal_upd(&LocTriggerData);
 
-                               CurrentTriggerData = &SaveTriggerData;
-                               key_unchanged = RI_FKey_keyequal_upd();
-                               CurrentTriggerData = NULL;
-                               
                                if (key_unchanged)
                                {
-                                       /* ----------
+                                       /*
                                         * The key hasn't changed, so no need later to invoke
-                                        * the trigger at all. But remember other states from
-                                        * the possible earlier event.
-                                        * ----------
+                                        * the trigger at all.
                                         */
                                        new_event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
-
-                                       if (prev_event)
-                                       {
-                                               if (prev_event->dte_event & 
-                                                                       TRIGGER_DEFERRED_ROW_INSERTED)
-                                               {
-                                                       /* ----------
-                                                        * This is a row inserted during our transaction.
-                                                        * So any key value is considered changed.
-                                                        * ----------
-                                                        */
-                                                       new_event->dte_event |= 
-                                                                                       TRIGGER_DEFERRED_ROW_INSERTED;
-                                                       new_event->dte_event |= 
-                                                                                       TRIGGER_DEFERRED_KEY_CHANGED;
-                                                       new_event->dte_item[i].dti_state |=
-                                                                                       TRIGGER_DEFERRED_KEY_CHANGED;
-                                               }
-                                               else
-                                               {
-                                                       /* ----------
-                                                        * This is a row, previously updated. So
-                                                        * if this key has been changed before, we
-                                                        * still remember that it happened.
-                                                        * ----------
-                                                        */
-                                                       if (prev_event->dte_item[i].dti_state &
-                                                                                       TRIGGER_DEFERRED_KEY_CHANGED)
-                                                       {
-                                                               new_event->dte_item[i].dti_state |=
-                                                                                       TRIGGER_DEFERRED_KEY_CHANGED;
-                                                               new_event->dte_event |=
-                                                                                       TRIGGER_DEFERRED_KEY_CHANGED;
-                                                       }
-                                               }
-                                       }
-                               }
-                               else
-                               {
-                                       /* ----------
-                                        * Bomb out if this key has been changed before.
-                                        * Otherwise remember that we do so.
-                                        * ----------
-                                        */
-                                       if (prev_event)
-                                       {
-                                               if (prev_event->dte_event &
-                                                                                       TRIGGER_DEFERRED_ROW_INSERTED)
-                                                       elog(ERROR, "triggered data change violation "
-                                                                       "on relation \"%s\"",
-                                                                       nameout(&(rel->rd_rel->relname)));
-
-                                               if (prev_event->dte_item[i].dti_state &
-                                                                                       TRIGGER_DEFERRED_KEY_CHANGED)
-                                                       elog(ERROR, "triggered data change violation "
-                                                                       "on relation \"%s\"",
-                                                                       nameout(&(rel->rd_rel->relname)));
-                                       }
-
-                                       /* ----------
-                                        * This is the first change to this key, so let
-                                        * it happen.
-                                        * ----------
-                                        */
-                                       new_event->dte_item[i].dti_state |=
-                                                                                       TRIGGER_DEFERRED_KEY_CHANGED;
-                                       new_event->dte_event |= TRIGGER_DEFERRED_KEY_CHANGED;
                                }
                        }
 
                        break;
 
                case TRIGGER_EVENT_DELETE:
-                       /* ----------
-                        * On DELETE check if the tuple deleted has been inserted
-                        * or a possibly referenced key value has changed in this
-                        * transaction.
-                        * ----------
-                        */
-                       if (oldtup->t_data->t_xmin != GetCurrentTransactionId())
-                               break;
-
-                       /* ----------
-                        * Look at the previous event to the same tuple.
-                        * ----------
-                        */
-                       prev_event = deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid);
-                       if (prev_event->dte_event & TRIGGER_DEFERRED_KEY_CHANGED)
-                               elog(ERROR, "triggered data change violation "
-                                               "on relation \"%s\"",
-                                               nameout(&(rel->rd_rel->relname)));
-
+                       /* nothing to do */
                        break;
        }
 
-       /* ----------
-        * Anything's fine up to here. Add the new event to the queue.
-        * ----------
+       /*
+        * Add the new event to the queue.
         */
-       oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt);
        deferredTriggerAddEvent(new_event);
-       MemoryContextSwitchTo(oldcxt);
-
-       return;
 }
-
-