* 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)
}
}
- /* 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)
{
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 == '\\')
}
}
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)
*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;
}
}
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;
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)
}
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;
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;
*newSlot = NULL;
tuple.t_self = *tid;
ltrmark:;
- test = heap_mark4update(relation, &tuple, &buffer);
+ test = heap_mark4update(relation, &tuple, &buffer,
+ GetCurrentCommandId());
switch (test)
{
case HeapTupleSelfUpdated:
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)))
/* ----------
* 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;
/* ----------
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);
* 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;
}
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);
}
* 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);
}
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);
MemoryContextSwitchTo(oldcxt);
- deftrig_n_events = 0;
- deftrig_events = NIL;
+ deftrig_events = NULL;
+ deftrig_event_tail = NULL;
}
void
DeferredTriggerEndQuery(void)
{
- /* ----------
+ /*
* Ignore call if we aren't in a transaction.
- * ----------
*/
if (deftrig_cxt == NULL)
return;
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;
}
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;
}
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;
}
}
* ----------
*/
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;
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;
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);
}
}
* 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));
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;
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;
}
-
-