postgresql/src/backend/commands/trigger.c
PostgreSQL Daemon 2ff501590b Tag appropriate files for rc3
Also performed an initial run through of upgrading our Copyright date to
extend to 2005 ... first run here was very simple ... change everything
where: grep 1996-2004 && the word 'Copyright' ... scanned through the
generated list with 'less' first, and after, to make sure that I only
picked up the right entries ...
2004-12-31 22:04:05 +00:00

2984 lines
80 KiB
C

/*-------------------------------------------------------------------------
*
* trigger.c
* PostgreSQL TRIGGERs support code.
*
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.177 2004/12/31 21:59:41 pgsql Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/xact.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 "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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"
static void InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx);
static HeapTuple GetTupleForTrigger(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tid,
CommandId cid,
TupleTableSlot **newSlot);
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
FmgrInfo *finfo,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
/*
* Create a trigger. Returns the OID of the created trigger.
*
* forConstraint, if true, says that this trigger is being created to
* implement a constraint. The caller will then be expected to make
* a pg_depend entry linking the trigger to that constraint (and thereby
* to the owning relation(s)).
*/
Oid
CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
{
int16 tgtype;
int16 tgattr[FUNC_MAX_ARGS];
Datum values[Natts_pg_trigger];
char nulls[Natts_pg_trigger];
Relation rel;
AclResult aclresult;
Relation tgrel;
SysScanDesc tgscan;
ScanKeyData key;
Relation pgrel;
HeapTuple tuple;
Oid fargtypes[FUNC_MAX_ARGS];
Oid funcoid;
Oid funcrettype;
Oid trigoid;
int found = 0;
int i;
char constrtrigname[NAMEDATALEN];
char *trigname;
char *constrname;
Oid constrrelid = InvalidOid;
ObjectAddress myself,
referenced;
rel = heap_openrv(stmt->relation, AccessExclusiveLock);
if (stmt->constrrel != NULL)
constrrelid = RangeVarGetRelid(stmt->constrrel, false);
else if (stmt->isconstraint)
{
/*
* If this trigger is a constraint (and a foreign key one) then we
* really need a constrrelid. Since we don't have one, we'll try
* to generate one from the argument information.
*
* This is really just a workaround for a long-ago pg_dump bug that
* omitted the FROM clause in dumped CREATE CONSTRAINT TRIGGER
* commands. We don't want to bomb out completely here if we
* can't determine the correct relation, because that would
* prevent loading the dump file. Instead, NOTICE here and ERROR
* in the trigger.
*/
bool needconstrrelid = false;
void *elem = NULL;
if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_check_", 14) == 0)
{
/* A trigger on FK table. */
needconstrrelid = true;
if (list_length(stmt->args) > RI_PK_RELNAME_ARGNO)
elem = list_nth(stmt->args, RI_PK_RELNAME_ARGNO);
}
else if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_", 8) == 0)
{
/* A trigger on PK table. */
needconstrrelid = true;
if (list_length(stmt->args) > RI_FK_RELNAME_ARGNO)
elem = list_nth(stmt->args, RI_FK_RELNAME_ARGNO);
}
if (elem != NULL)
{
RangeVar *rel = makeRangeVar(NULL, strVal(elem));
constrrelid = RangeVarGetRelid(rel, true);
}
if (needconstrrelid && constrrelid == InvalidOid)
ereport(NOTICE,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("could not determine referenced table for constraint \"%s\"",
stmt->trigname)));
}
if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
if (!allowSystemTableMods && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
/* permission checks */
if (stmt->isconstraint)
{
/* foreign key constraint trigger */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (constrrelid != InvalidOid)
{
aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
ACL_REFERENCES);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
get_rel_name(constrrelid));
}
}
else
{
/* real trigger */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_TRIGGER);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
}
/*
* Generate the trigger's OID now, so that we can use it in the name
* if needed.
*/
trigoid = newoid();
/*
* 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", trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
else
{
trigname = stmt->trigname;
constrname = "";
}
TRIGGER_CLEAR_TYPE(tgtype);
if (stmt->before)
TRIGGER_SETT_BEFORE(tgtype);
if (stmt->row)
TRIGGER_SETT_ROW(tgtype);
for (i = 0; stmt->actions[i]; i++)
{
switch (stmt->actions[i])
{
case 'i':
if (TRIGGER_FOR_INSERT(tgtype))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INSERT events specified")));
TRIGGER_SETT_INSERT(tgtype);
break;
case 'd':
if (TRIGGER_FOR_DELETE(tgtype))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DELETE events specified")));
TRIGGER_SETT_DELETE(tgtype);
break;
case 'u':
if (TRIGGER_FOR_UPDATE(tgtype))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple UPDATE events specified")));
TRIGGER_SETT_UPDATE(tgtype);
break;
default:
elog(ERROR, "unrecognized trigger event: %d",
(int) stmt->actions[i]);
break;
}
}
/*
* 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);
ScanKeyInit(&key,
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, 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), trigname) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" already exists",
trigname, stmt->relation->relname)));
found++;
}
systable_endscan(tgscan);
/*
* Find and validate the trigger function.
*/
MemSet(fargtypes, 0, FUNC_MAX_ARGS * sizeof(Oid));
funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false);
funcrettype = get_func_rettype(funcoid);
if (funcrettype != TRIGGEROID)
{
/*
* We allow OPAQUE just so we can load old dump files. When we
* see a trigger function declared OPAQUE, change it to TRIGGER.
*/
if (funcrettype == OPAQUEOID)
{
ereport(WARNING,
(errmsg("changing return type of function %s from \"opaque\" to \"trigger\"",
NameListToString(stmt->funcname))));
SetFunctionReturnType(funcoid, TRIGGEROID);
}
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("function %s must return type \"trigger\"",
NameListToString(stmt->funcname))));
}
/*
* 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] = 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] = 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)
{
ListCell *le;
char *args;
int16 nargs = list_length(stmt->args);
int len = 0;
foreach(le, stmt->args)
{
char *ar = strVal(lfirst(le));
len += strlen(ar) + 4;
for (; *ar; ar++)
{
if (*ar == '\\')
len++;
}
}
args = (char *) palloc(len + 1);
args[0] = '\0';
foreach(le, stmt->args)
{
char *s = strVal(lfirst(le));
char *d = args + strlen(args);
while (*s)
{
if (*s == '\\')
*d++ = '\\';
*d++ = *s++;
}
strcpy(d, "\\000");
}
values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(nargs);
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] = 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);
/* force tuple to have the desired OID */
HeapTupleSetOid(tuple, trigoid);
/*
* Insert tuple into pg_trigger.
*/
simple_heap_insert(tgrel, tuple);
CatalogUpdateIndexes(tgrel, tuple);
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 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 = SearchSysCacheCopy(RELOID,
ObjectIdGetDatum(RelationGetRelid(rel)),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u",
RelationGetRelid(rel));
((Form_pg_class) GETSTRUCT(tuple))->reltriggers = found + 1;
simple_heap_update(pgrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(pgrel, tuple);
heap_freetuple(tuple);
heap_close(pgrel, RowExclusiveLock);
/*
* 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(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);
ScanKeyInit(&skey[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
ScanKeyInit(&skey[1],
Anum_pg_trigger_tgname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(trigname));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
SnapshotNow, 2, skey);
tup = systable_getnext(tgscan);
if (!HeapTupleIsValid(tup))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("trigger \"%s\" for table \"%s\" does not exist",
trigname, get_rel_name(relid))));
if (!pg_class_ownercheck(relid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
get_rel_name(relid));
object.classId = RelationGetRelid(tgrel);
object.objectId = HeapTupleGetOid(tup);
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 tgrel;
SysScanDesc tgscan;
ScanKeyData skey[1];
HeapTuple tup;
Oid relid;
Relation rel;
Relation pgrel;
HeapTuple tuple;
Form_pg_class classForm;
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
/*
* Find the trigger to delete.
*/
ScanKeyInit(&skey[0],
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(trigOid));
tgscan = systable_beginscan(tgrel, TriggerOidIndex, true,
SnapshotNow, 1, skey);
tup = systable_getnext(tgscan);
if (!HeapTupleIsValid(tup))
elog(ERROR, "could not find tuple for trigger %u", trigOid);
/*
* Open and exclusive-lock the relation the trigger belongs to.
*/
relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
if (!allowSystemTableMods && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
/*
* Delete the pg_trigger tuple.
*/
simple_heap_delete(tgrel, &tup->t_self);
systable_endscan(tgscan);
heap_close(tgrel, RowExclusiveLock);
/*
* 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 = SearchSysCacheCopy(RELOID,
ObjectIdGetDatum(relid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
classForm = (Form_pg_class) GETSTRUCT(tuple);
if (classForm->reltriggers == 0) /* should not happen */
elog(ERROR, "relation \"%s\" has reltriggers = 0",
RelationGetRelationName(rel));
classForm->reltriggers--;
simple_heap_update(pgrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(pgrel, tuple);
heap_freetuple(tuple);
heap_close(pgrel, RowExclusiveLock);
/* 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
renametrig(Oid relid,
const char *oldname,
const char *newname)
{
Relation targetrel;
Relation tgrel;
HeapTuple tuple;
SysScanDesc tgscan;
ScanKeyData key[2];
/*
* 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);
/*
* First pass -- look for name conflict
*/
ScanKeyInit(&key[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
ScanKeyInit(&key[1],
Anum_pg_trigger_tgname,
BTEqualStrategyNumber, F_NAMEEQ,
PointerGetDatum(newname));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
SnapshotNow, 2, key);
if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" already exists",
newname, RelationGetRelationName(targetrel))));
systable_endscan(tgscan);
/*
* Second pass -- look for trigger existing with oldname and update
*/
ScanKeyInit(&key[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
ScanKeyInit(&key[1],
Anum_pg_trigger_tgname,
BTEqualStrategyNumber, F_NAMEEQ,
PointerGetDatum(oldname));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
SnapshotNow, 2, key);
if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
{
/*
* Update pg_trigger tuple with new tgname.
*/
tuple = heap_copytuple(tuple); /* need a modifiable copy */
namestrcpy(&((Form_pg_trigger) GETSTRUCT(tuple))->tgname, newname);
simple_heap_update(tgrel, &tuple->t_self, tuple);
/* keep system catalog indexes current */
CatalogUpdateIndexes(tgrel, tuple);
/*
* 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(targetrel);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("trigger \"%s\" for table \"%s\" does not exist",
oldname, RelationGetRelationName(targetrel))));
}
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 attached to a relcache entry must be stored in
* CacheMemoryContext to ensure it survives as long as the relcache entry.
* But we should be running in a less long-lived working context. To avoid
* leaking cache memory if this routine fails partway through, we build a
* temporary TriggerDesc in working memory and then copy the completed
* structure into cache memory.
*/
void
RelationBuildTriggers(Relation relation)
{
TriggerDesc *trigdesc;
int ntrigs = relation->rd_rel->reltriggers;
Trigger *triggers;
int found = 0;
Relation tgrel;
ScanKeyData skey;
SysScanDesc tgscan;
HeapTuple htup;
MemoryContext oldContext;
Assert(ntrigs > 0); /* else I should not have been called */
triggers = (Trigger *) palloc(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.
*/
ScanKeyInit(&skey,
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
tgrel = heap_openr(TriggerRelationName, AccessShareLock);
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
SnapshotNow, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
{
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
Trigger *build;
if (found >= ntrigs)
elog(ERROR, "too many trigger records found for relation \"%s\"",
RelationGetRelationName(relation));
build = &(triggers[found]);
build->tgoid = HeapTupleGetOid(htup);
build->tgname = DatumGetCString(DirectFunctionCall1(nameout,
NameGetDatum(&pg_trigger->tgname)));
build->tgfoid = pg_trigger->tgfoid;
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),
FUNC_MAX_ARGS * sizeof(int16));
if (build->tgnargs > 0)
{
bytea *val;
bool isnull;
char *p;
int i;
val = (bytea *) fastgetattr(htup,
Anum_pg_trigger_tgargs,
tgrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "tgargs is null in trigger for relation \"%s\"",
RelationGetRelationName(relation));
p = (char *) VARDATA(val);
build->tgargs = (char **) palloc(build->tgnargs * sizeof(char *));
for (i = 0; i < build->tgnargs; i++)
{
build->tgargs[i] = pstrdup(p);
p += strlen(p) + 1;
}
}
else
build->tgargs = NULL;
found++;
}
systable_endscan(tgscan);
heap_close(tgrel, AccessShareLock);
if (found != ntrigs)
elog(ERROR, "%d trigger record(s) not found for relation \"%s\"",
ntrigs - found,
RelationGetRelationName(relation));
/* Build trigdesc */
trigdesc = (TriggerDesc *) palloc0(sizeof(TriggerDesc));
trigdesc->triggers = triggers;
trigdesc->numtriggers = ntrigs;
for (found = 0; found < ntrigs; found++)
InsertTrigger(trigdesc, &(triggers[found]), found);
/* Copy completed trigdesc into cache storage */
oldContext = MemoryContextSwitchTo(CacheMemoryContext);
relation->trigdesc = CopyTriggerDesc(trigdesc);
MemoryContextSwitchTo(oldContext);
/* Release working memory */
FreeTriggerDesc(trigdesc);
}
/*
* Insert the given trigger into the appropriate index list(s) for it
*
* To simplify storage management, we allocate each index list at the max
* possible size (trigdesc->numtriggers) if it's used at all. This does
* not waste space permanently since we're only building a temporary
* trigdesc at this point.
*/
static void
InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx)
{
uint16 *n;
int **t,
**tp;
if (TRIGGER_FOR_ROW(trigger->tgtype))
{
/* 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;
}
}
if (TRIGGER_FOR_INSERT(trigger->tgtype))
{
tp = &(t[TRIGGER_EVENT_INSERT]);
if (*tp == NULL)
*tp = (int *) palloc(trigdesc->numtriggers * 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 *) palloc(trigdesc->numtriggers * 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 *) palloc(trigdesc->numtriggers * sizeof(int));
(*tp)[n[TRIGGER_EVENT_UPDATE]] = indx;
(n[TRIGGER_EVENT_UPDATE])++;
}
}
/*
* Copy a TriggerDesc data structure.
*
* The copy is allocated in the current memory context.
*/
TriggerDesc *
CopyTriggerDesc(TriggerDesc *trigdesc)
{
TriggerDesc *newdesc;
uint16 *n;
int **t,
*tnew;
Trigger *trigger;
int i;
if (trigdesc == NULL || trigdesc->numtriggers <= 0)
return NULL;
newdesc = (TriggerDesc *) palloc(sizeof(TriggerDesc));
memcpy(newdesc, trigdesc, sizeof(TriggerDesc));
trigger = (Trigger *) palloc(trigdesc->numtriggers * sizeof(Trigger));
memcpy(trigger, trigdesc->triggers,
trigdesc->numtriggers * sizeof(Trigger));
newdesc->triggers = trigger;
for (i = 0; i < trigdesc->numtriggers; i++)
{
trigger->tgname = pstrdup(trigger->tgname);
if (trigger->tgnargs > 0)
{
char **newargs;
int16 j;
newargs = (char **) palloc(trigger->tgnargs * sizeof(char *));
for (j = 0; j < trigger->tgnargs; j++)
newargs[j] = pstrdup(trigger->tgargs[j]);
trigger->tgargs = newargs;
}
trigger++;
}
n = newdesc->n_before_statement;
t = newdesc->tg_before_statement;
for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
{
if (n[i] > 0)
{
tnew = (int *) palloc(n[i] * sizeof(int));
memcpy(tnew, t[i], n[i] * sizeof(int));
t[i] = tnew;
}
else
t[i] = NULL;
}
n = newdesc->n_before_row;
t = newdesc->tg_before_row;
for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
{
if (n[i] > 0)
{
tnew = (int *) palloc(n[i] * sizeof(int));
memcpy(tnew, t[i], n[i] * sizeof(int));
t[i] = tnew;
}
else
t[i] = NULL;
}
n = newdesc->n_after_row;
t = newdesc->tg_after_row;
for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
{
if (n[i] > 0)
{
tnew = (int *) palloc(n[i] * sizeof(int));
memcpy(tnew, t[i], n[i] * sizeof(int));
t[i] = tnew;
}
else
t[i] = NULL;
}
n = newdesc->n_after_statement;
t = newdesc->tg_after_statement;
for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
{
if (n[i] > 0)
{
tnew = (int *) palloc(n[i] * sizeof(int));
memcpy(tnew, t[i], n[i] * sizeof(int));
t[i] = tnew;
}
else
t[i] = NULL;
}
return newdesc;
}
/*
* Free a TriggerDesc data structure.
*/
void
FreeTriggerDesc(TriggerDesc *trigdesc)
{
int **t;
Trigger *trigger;
int i;
if (trigdesc == NULL)
return;
t = trigdesc->tg_before_statement;
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 < TRIGGER_NUM_EVENT_CLASSES; i++)
if (t[i] != NULL)
pfree(t[i]);
t = trigdesc->tg_after_row;
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 < TRIGGER_NUM_EVENT_CLASSES; i++)
if (t[i] != NULL)
pfree(t[i]);
trigger = trigdesc->triggers;
for (i = 0; i < trigdesc->numtriggers; i++)
{
pfree(trigger->tgname);
if (trigger->tgnargs > 0)
{
while (--(trigger->tgnargs) >= 0)
pfree(trigger->tgargs[trigger->tgnargs]);
pfree(trigger->tgargs);
}
trigger++;
}
pfree(trigdesc->triggers);
pfree(trigdesc);
}
/*
* Compare two TriggerDesc structures for logical equality.
*/
#ifdef NOT_USED
bool
equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
{
int i,
j;
/*
* 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 (trigdesc2 == NULL)
return false;
if (trigdesc1->numtriggers != trigdesc2->numtriggers)
return false;
for (i = 0; i < trigdesc1->numtriggers; i++)
{
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;
}
#endif /* NOT_USED */
/*
* 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;
/*
* 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);
/*
* Call the function, passing no arguments but setting a context.
*/
MemSet(&fcinfo, 0, sizeof(fcinfo));
fcinfo.flinfo = finfo;
fcinfo.context = (Node *) trigdata;
result = FunctionCallInvoke(&fcinfo);
MemoryContextSwitchTo(oldContext);
/*
* Trigger protocol allows function to return a null pointer, but NOT
* to set the isnull result flag.
*/
if (fcinfo.isnull)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("trigger function %u returned null value",
fcinfo.flinfo->fn_oid)));
return (HeapTuple) DatumGetPointer(result);
}
void
ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc;
int ntrigs;
int *tgindx;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_INSERT];
tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_INSERT];
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
GetPerTupleMemoryContext(estate));
if (newtuple)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("BEFORE STATEMENT trigger cannot return a value")));
}
}
void
ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
false, NULL, NULL);
}
HeapTuple
ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
HeapTuple trigtuple)
{
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;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(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;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
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;
}
return newtuple;
}
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
HeapTuple trigtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
true, NULL, trigtuple);
}
void
ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc;
int ntrigs;
int *tgindx;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_DELETE];
tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_DELETE];
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
GetPerTupleMemoryContext(estate));
if (newtuple)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("BEFORE STATEMENT trigger cannot return a value")));
}
}
void
ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
false, NULL, NULL);
}
bool
ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
ItemPointer tupleid,
CommandId cid)
{
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, relinfo, tupleid, cid, &newSlot);
if (trigtuple == NULL)
return false;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(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;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
GetPerTupleMemoryContext(estate));
if (newtuple == NULL)
break;
if (newtuple != trigtuple)
heap_freetuple(newtuple);
}
heap_freetuple(trigtuple);
return (newtuple == NULL) ? false : true;
}
void
ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
ItemPointer tupleid)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, relinfo,
tupleid,
(CommandId) 0,
NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
true, trigtuple, NULL);
heap_freetuple(trigtuple);
}
}
void
ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc;
int ntrigs;
int *tgindx;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_UPDATE];
tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_UPDATE];
if (ntrigs == 0)
return;
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < ntrigs; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
HeapTuple newtuple;
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
relinfo->ri_TrigFunctions + tgindx[i],
GetPerTupleMemoryContext(estate));
if (newtuple)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("BEFORE STATEMENT trigger cannot return a value")));
}
}
void
ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
false, NULL, NULL);
}
HeapTuple
ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
ItemPointer tupleid, HeapTuple newtuple,
CommandId cid)
{
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, relinfo, tupleid, cid, &newSlot);
if (trigtuple == NULL)
return NULL;
/*
* In READ COMMITTED isolation level it's possible that newtuple was
* changed due to concurrent update.
*/
if (newSlot != NULL)
intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
/* Allocate cache space for fmgr lookup info, if not done yet */
if (relinfo->ri_TrigFunctions == NULL)
relinfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(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++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
if (!trigger->tgenabled)
continue;
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
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;
}
heap_freetuple(trigtuple);
return newtuple;
}
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
ItemPointer tupleid, HeapTuple newtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, relinfo,
tupleid,
(CommandId) 0,
NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
true, trigtuple, newtuple);
heap_freetuple(trigtuple);
}
}
static HeapTuple
GetTupleForTrigger(EState *estate, ResultRelInfo *relinfo,
ItemPointer tid, CommandId cid,
TupleTableSlot **newSlot)
{
Relation relation = relinfo->ri_RelationDesc;
HeapTupleData tuple;
HeapTuple result;
Buffer buffer;
if (newSlot != NULL)
{
int test;
/*
* mark tuple for update
*/
*newSlot = NULL;
tuple.t_self = *tid;
ltrmark:;
test = heap_mark4update(relation, &tuple, &buffer, cid);
switch (test)
{
case HeapTupleSelfUpdated:
/* treat it as deleted; do not process */
ReleaseBuffer(buffer);
return NULL;
case HeapTupleMayBeUpdated:
break;
case HeapTupleUpdated:
ReleaseBuffer(buffer);
if (IsXactIsoLevelSerializable)
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to concurrent update")));
else if (!(ItemPointerEquals(&(tuple.t_self), tid)))
{
TupleTableSlot *epqslot = EvalPlanQual(estate,
relinfo->ri_RangeTableIndex,
&(tuple.t_self));
if (!(TupIsNull(epqslot)))
{
*tid = tuple.t_self;
*newSlot = epqslot;
goto ltrmark;
}
}
/*
* if tuple was deleted or PlanQual failed for updated
* tuple - we have not process this tuple!
*/
return NULL;
default:
ReleaseBuffer(buffer);
elog(ERROR, "unrecognized heap_mark4update status: %u",
test);
return NULL; /* keep compiler quiet */
}
}
else
{
PageHeader dp;
ItemId lp;
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
dp = (PageHeader) BufferGetPage(buffer);
lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(tid));
Assert(ItemIdIsUsed(lp));
tuple.t_datamcxt = NULL;
tuple.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
tuple.t_len = ItemIdGetLength(lp);
tuple.t_self = *tid;
}
result = heap_copytuple(&tuple);
ReleaseBuffer(buffer);
return result;
}
/* ----------
* After-trigger stuff
*
* The AfterTriggersData struct holds data about pending AFTER trigger events
* during the current transaction tree. (BEFORE triggers are fired
* immediately so we don't need any persistent state about them.) The struct
* and most of its subsidiary data are kept in TopTransactionContext; however
* the individual event records are kept in CurTransactionContext, so that
* they will easily go away during subtransaction abort.
*
* Because the list of pending events can grow large, we go to some effort
* to minimize memory consumption. We do not use the generic List mechanism
* but thread the events manually.
*
* XXX We need to be able to save the per-event data in a file if it grows too
* large.
* ----------
*/
/* Per-trigger SET CONSTRAINT status */
typedef struct SetConstraintTriggerData
{
Oid sct_tgoid;
bool sct_tgisdeferred;
} SetConstraintTriggerData;
typedef struct SetConstraintTriggerData *SetConstraintTrigger;
/*
* SET CONSTRAINT intra-transaction status.
*
* We make this a single palloc'd object so it can be copied and freed easily.
*
* all_isset and all_isdeferred are used to keep track
* of SET CONSTRAINTS ALL {DEFERRED, IMMEDIATE}.
*
* trigstates[] stores per-trigger tgisdeferred settings.
*/
typedef struct SetConstraintStateData
{
bool all_isset;
bool all_isdeferred;
int numstates; /* number of trigstates[] entries in use */
int numalloc; /* allocated size of trigstates[] */
SetConstraintTriggerData trigstates[1]; /* VARIABLE LENGTH ARRAY */
} SetConstraintStateData;
typedef SetConstraintStateData *SetConstraintState;
/*
* Per-trigger-event data
*
* Note: ate_firing_id is meaningful when either AFTER_TRIGGER_DONE
* or AFTER_TRIGGER_IN_PROGRESS is set. It indicates which trigger firing
* cycle the trigger was or will be fired in.
*/
typedef struct AfterTriggerEventData *AfterTriggerEvent;
typedef struct AfterTriggerEventData
{
AfterTriggerEvent ate_next; /* list link */
TriggerEvent ate_event; /* event type and status bits */
CommandId ate_firing_id; /* ID for firing cycle */
Oid ate_tgoid; /* the trigger's ID */
Oid ate_relid; /* the relation it's on */
ItemPointerData ate_oldctid; /* specific tuple(s) involved */
ItemPointerData ate_newctid;
} AfterTriggerEventData;
/* A list of events */
typedef struct AfterTriggerEventList
{
AfterTriggerEvent head;
AfterTriggerEvent tail;
} AfterTriggerEventList;
/*
* All per-transaction data for the AFTER TRIGGERS module.
*
* AfterTriggersData has the following fields:
*
* firing_counter is incremented for each call of afterTriggerInvokeEvents.
* We mark firable events with the current firing cycle's ID so that we can
* tell which ones to work on. This ensures sane behavior if a trigger
* function chooses to do SET CONSTRAINTS: the inner SET CONSTRAINTS will
* only fire those events that weren't already scheduled for firing.
*
* state keeps track of the transaction-local effects of SET CONSTRAINTS.
* This is saved and restored across failed subtransactions.
*
* events is the current list of deferred events. This is global across
* all subtransactions of the current transaction. In a subtransaction
* abort, we know that the events added by the subtransaction are at the
* end of the list, so it is relatively easy to discard them.
*
* query_depth is the current depth of nested AfterTriggerBeginQuery calls
* (-1 when the stack is empty).
*
* query_stack[query_depth] is a list of AFTER trigger events queued by the
* current query (and the query_stack entries below it are lists of trigger
* events queued by calling queries). None of these are valid until the
* matching AfterTriggerEndQuery call occurs. At that point we fire
* immediate-mode triggers, and append any deferred events to the main events
* list.
*
* maxquerydepth is just the allocated length of query_stack.
*
* state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS
* state data; each subtransaction level that modifies that state first
* saves a copy, which we use to restore the state if we abort.
*
* events_stack is a stack of copies of the events head/tail pointers,
* which we use to restore those values during subtransaction abort.
*
* depth_stack is a stack of copies of subtransaction-start-time query_depth,
* which we similarly use to clean up at subtransaction abort.
*
* firing_stack is a stack of copies of subtransaction-start-time
* firing_counter. We use this to recognize which deferred triggers were
* fired (or marked for firing) within an aborted subtransaction.
*
* We use GetCurrentTransactionNestLevel() to determine the correct array
* index in these stacks. maxtransdepth is the number of allocated entries in
* each stack. (By not keeping our own stack pointer, we can avoid trouble
* in cases where errors during subxact abort cause multiple invocations
* of AfterTriggerEndSubXact() at the same nesting depth.)
*/
typedef struct AfterTriggersData
{
CommandId firing_counter; /* next firing ID to assign */
SetConstraintState state; /* the active S C state */
AfterTriggerEventList events; /* deferred-event list */
int query_depth; /* current query list index */
AfterTriggerEventList *query_stack; /* events pending from each query */
int maxquerydepth; /* allocated len of above array */
/* these fields are just for resetting at subtrans abort: */
SetConstraintState *state_stack; /* stacked S C states */
AfterTriggerEventList *events_stack; /* stacked list pointers */
int *depth_stack; /* stacked query_depths */
CommandId *firing_stack; /* stacked firing_counters */
int maxtransdepth; /* allocated len of above arrays */
} AfterTriggersData;
typedef AfterTriggersData *AfterTriggers;
static AfterTriggers afterTriggers;
static void AfterTriggerExecute(AfterTriggerEvent event,
Relation rel, TriggerDesc *trigdesc,
FmgrInfo *finfo,
MemoryContext per_tuple_context);
static SetConstraintState SetConstraintStateCreate(int numalloc);
static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
Oid tgoid, bool tgisdeferred);
/* ----------
* afterTriggerCheckState()
*
* Returns true if the trigger identified by tgoid is actually
* in state DEFERRED.
* ----------
*/
static bool
afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate)
{
SetConstraintState state = afterTriggers->state;
int i;
/*
* For not-deferrable triggers (i.e. normal AFTER ROW triggers and
* constraints declared NOT DEFERRABLE), the state is always false.
*/
if ((eventstate & AFTER_TRIGGER_DEFERRABLE) == 0)
return false;
/*
* Check if SET CONSTRAINTS has been executed for this specific trigger.
*/
for (i = 0; i < state->numstates; i++)
{
if (state->trigstates[i].sct_tgoid == tgoid)
return state->trigstates[i].sct_tgisdeferred;
}
/*
* Check if SET CONSTRAINTS ALL has been executed; if so use that.
*/
if (state->all_isset)
return state->all_isdeferred;
/*
* Otherwise return the default state for the trigger.
*/
return ((eventstate & AFTER_TRIGGER_INITDEFERRED) != 0);
}
/* ----------
* afterTriggerAddEvent()
*
* Add a new trigger event to the current query's queue.
* ----------
*/
static void
afterTriggerAddEvent(AfterTriggerEvent event)
{
AfterTriggerEventList *events;
Assert(event->ate_next == NULL);
/* Must be inside a query */
Assert(afterTriggers->query_depth >= 0);
events = &afterTriggers->query_stack[afterTriggers->query_depth];
if (events->tail == NULL)
{
/* first list entry */
events->head = event;
events->tail = event;
}
else
{
events->tail->ate_next = event;
events->tail = event;
}
}
/* ----------
* AfterTriggerExecute()
*
* 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.
* rel: open relation for event.
* trigdesc: working copy of rel's trigger info.
* finfo: array of fmgr lookup cache entries (one per trigger in trigdesc).
* per_tuple_context: memory context to call trigger function in.
* ----------
*/
static void
AfterTriggerExecute(AfterTriggerEvent event,
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
MemoryContext per_tuple_context)
{
Oid tgoid = event->ate_tgoid;
TriggerData LocTriggerData;
HeapTupleData oldtuple;
HeapTupleData newtuple;
HeapTuple rettuple;
Buffer oldbuffer = InvalidBuffer;
Buffer newbuffer = InvalidBuffer;
int tgindx;
/*
* Fetch the required OLD and NEW tuples.
*/
if (ItemPointerIsValid(&(event->ate_oldctid)))
{
ItemPointerCopy(&(event->ate_oldctid), &(oldtuple.t_self));
if (!heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer, false, NULL))
elog(ERROR, "failed to fetch old tuple for AFTER trigger");
}
if (ItemPointerIsValid(&(event->ate_newctid)))
{
ItemPointerCopy(&(event->ate_newctid), &(newtuple.t_self));
if (!heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer, false, NULL))
elog(ERROR, "failed to fetch new tuple for AFTER trigger");
}
/*
* Setup the trigger information
*/
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event =
event->ate_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, "could not find trigger %u", tgoid);
switch (event->ate_event & TRIGGER_EVENT_OPMASK)
{
case TRIGGER_EVENT_INSERT:
LocTriggerData.tg_trigtuple = &newtuple;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = newbuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
break;
case TRIGGER_EVENT_UPDATE:
LocTriggerData.tg_trigtuple = &oldtuple;
LocTriggerData.tg_newtuple = &newtuple;
LocTriggerData.tg_trigtuplebuf = oldbuffer;
LocTriggerData.tg_newtuplebuf = newbuffer;
break;
case TRIGGER_EVENT_DELETE:
LocTriggerData.tg_trigtuple = &oldtuple;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = oldbuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
break;
}
MemoryContextReset(per_tuple_context);
/*
* Call the trigger and throw away any eventually returned updated
* tuple.
*/
rettuple = ExecCallTriggerFunc(&LocTriggerData,
finfo + tgindx,
per_tuple_context);
if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple)
heap_freetuple(rettuple);
/*
* Release buffers
*/
if (oldbuffer != InvalidBuffer)
ReleaseBuffer(oldbuffer);
if (newbuffer != InvalidBuffer)
ReleaseBuffer(newbuffer);
}
/*
* afterTriggerMarkEvents()
*
* Scan the given event list for not yet invoked events. Mark the ones
* that can be invoked now with the current firing ID.
*
* If move_list isn't NULL, events that are not to be invoked now are
* removed from the given list and appended to move_list.
*
* When immediate_only is TRUE, do not invoke currently-deferred triggers.
* (This will be FALSE only at main transaction exit.)
*
* Returns TRUE if any invokable events were found.
*/
static bool
afterTriggerMarkEvents(AfterTriggerEventList *events,
AfterTriggerEventList *move_list,
bool immediate_only)
{
bool found = false;
AfterTriggerEvent event,
prev_event;
prev_event = NULL;
event = events->head;
while (event != NULL)
{
bool defer_it = false;
AfterTriggerEvent next_event;
if (!(event->ate_event &
(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS)))
{
/*
* This trigger hasn't been called or scheduled yet. Check if we
* should call it now.
*/
if (immediate_only &&
afterTriggerCheckState(event->ate_tgoid, event->ate_event))
{
defer_it = true;
}
else
{
/*
* Mark it as to be fired in this firing cycle.
*/
event->ate_firing_id = afterTriggers->firing_counter;
event->ate_event |= AFTER_TRIGGER_IN_PROGRESS;
found = true;
}
}
/*
* If it's deferred, move it to move_list, if requested.
*/
next_event = event->ate_next;
if (defer_it && move_list != NULL)
{
/* Delink it from input list */
if (prev_event)
prev_event->ate_next = next_event;
else
events->head = next_event;
/* and add it to move_list */
event->ate_next = NULL;
if (move_list->tail == NULL)
{
/* first list entry */
move_list->head = event;
move_list->tail = event;
}
else
{
move_list->tail->ate_next = event;
move_list->tail = event;
}
}
else
{
/* Keep it in input list */
prev_event = event;
}
event = next_event;
}
/* Update list tail pointer in case we moved tail event */
events->tail = prev_event;
return found;
}
/* ----------
* afterTriggerInvokeEvents()
*
* Scan the given event list for events that are marked as to be fired
* in the current firing cycle, and fire them.
*
* When delete_ok is TRUE, it's okay to delete fully-processed events.
* The events list pointers are updated.
* ----------
*/
static void
afterTriggerInvokeEvents(AfterTriggerEventList *events,
CommandId firing_id,
bool delete_ok)
{
AfterTriggerEvent event,
prev_event;
MemoryContext per_tuple_context;
Relation rel = NULL;
TriggerDesc *trigdesc = NULL;
FmgrInfo *finfo = NULL;
/* Make a per-tuple memory context for trigger function calls */
per_tuple_context =
AllocSetContextCreate(CurrentMemoryContext,
"AfterTriggerTupleContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
prev_event = NULL;
event = events->head;
while (event != NULL)
{
AfterTriggerEvent next_event;
/*
* Is it one for me to fire?
*/
if ((event->ate_event & AFTER_TRIGGER_IN_PROGRESS) &&
event->ate_firing_id == firing_id)
{
/*
* 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->ate_relid)
{
if (rel)
heap_close(rel, NoLock);
if (trigdesc)
FreeTriggerDesc(trigdesc);
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->ate_relid, NoLock);
/*
* Copy relation's trigger info so that we have a
* stable copy no matter what the called triggers do.
*/
trigdesc = CopyTriggerDesc(rel->trigdesc);
if (trigdesc == NULL) /* should not happen */
elog(ERROR, "relation %u has no triggers",
event->ate_relid);
/*
* Allocate space to cache fmgr lookup info for
* triggers.
*/
finfo = (FmgrInfo *)
palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
}
/*
* Fire it. Note that the AFTER_TRIGGER_IN_PROGRESS flag is still
* set, so recursive examinations of the event list won't try
* to re-fire it.
*/
AfterTriggerExecute(event, rel, trigdesc, finfo,
per_tuple_context);
/*
* Mark the event as done.
*/
event->ate_event &= ~AFTER_TRIGGER_IN_PROGRESS;
event->ate_event |= AFTER_TRIGGER_DONE;
}
/*
* If it's now done, throw it away, if allowed.
*
* NB: it's possible the trigger call 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 at all
* points here.
*/
next_event = event->ate_next;
if ((event->ate_event & AFTER_TRIGGER_DONE) && delete_ok)
{
/* Delink it from list and free it */
if (prev_event)
prev_event->ate_next = next_event;
else
events->head = next_event;
pfree(event);
}
else
{
/* Keep it in list */
prev_event = event;
}
event = next_event;
}
/* Update list tail pointer in case we just deleted tail event */
events->tail = prev_event;
/* Release working resources */
if (rel)
heap_close(rel, NoLock);
if (trigdesc)
FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
MemoryContextDelete(per_tuple_context);
}
/* ----------
* AfterTriggerBeginXact()
*
* Called at transaction start (either BEGIN or implicit for single
* statement outside of transaction block).
* ----------
*/
void
AfterTriggerBeginXact(void)
{
Assert(afterTriggers == NULL);
/*
* Build empty after-trigger state structure
*/
afterTriggers = (AfterTriggers)
MemoryContextAlloc(TopTransactionContext,
sizeof(AfterTriggersData));
afterTriggers->firing_counter = FirstCommandId;
afterTriggers->state = SetConstraintStateCreate(8);
afterTriggers->events.head = NULL;
afterTriggers->events.tail = NULL;
afterTriggers->query_depth = -1;
/* We initialize the query stack to a reasonable size */
afterTriggers->query_stack = (AfterTriggerEventList *)
MemoryContextAlloc(TopTransactionContext,
8 * sizeof(AfterTriggerEventList));
afterTriggers->maxquerydepth = 8;
/* Subtransaction stack is empty until/unless needed */
afterTriggers->state_stack = NULL;
afterTriggers->events_stack = NULL;
afterTriggers->depth_stack = NULL;
afterTriggers->firing_stack = NULL;
afterTriggers->maxtransdepth = 0;
}
/* ----------
* AfterTriggerBeginQuery()
*
* Called just before we start processing a single query within a
* transaction (or subtransaction). Set up to record AFTER trigger
* events queued by the query. Note that it is allowed to have
* nested queries within a (sub)transaction.
* ----------
*/
void
AfterTriggerBeginQuery(void)
{
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* Increase the query stack depth */
afterTriggers->query_depth++;
/*
* Allocate more space in the query stack if needed.
*/
if (afterTriggers->query_depth >= afterTriggers->maxquerydepth)
{
/* repalloc will keep the stack in the same context */
int new_alloc = afterTriggers->maxquerydepth * 2;
afterTriggers->query_stack = (AfterTriggerEventList *)
repalloc(afterTriggers->query_stack,
new_alloc * sizeof(AfterTriggerEventList));
afterTriggers->maxquerydepth = new_alloc;
}
/* Initialize this query's list to empty */
afterTriggers->query_stack[afterTriggers->query_depth].head = NULL;
afterTriggers->query_stack[afterTriggers->query_depth].tail = NULL;
}
/* ----------
* AfterTriggerEndQuery()
*
* Called after one query has been completely processed. At this time
* we invoke all AFTER IMMEDIATE trigger events queued by the query, and
* transfer deferred trigger events to the global deferred-trigger list.
* ----------
*/
void
AfterTriggerEndQuery(void)
{
AfterTriggerEventList *events;
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* Must be inside a query, too */
Assert(afterTriggers->query_depth >= 0);
/*
* Process all immediate-mode triggers queued by the query, and move
* the deferred ones to the main list of deferred events.
*
* Notice that we decide which ones will be fired, and put the deferred
* ones on the main list, before anything is actually fired. This
* ensures reasonably sane behavior if a trigger function does
* SET CONSTRAINTS ... IMMEDIATE: all events we have decided to defer
* will be available for it to fire.
*
* If we find no firable events, we don't have to increment firing_counter.
*/
events = &afterTriggers->query_stack[afterTriggers->query_depth];
if (afterTriggerMarkEvents(events, &afterTriggers->events, true))
{
CommandId firing_id = afterTriggers->firing_counter++;
/* OK to delete the immediate events after processing them */
afterTriggerInvokeEvents(events, firing_id, true);
}
afterTriggers->query_depth--;
}
/* ----------
* AfterTriggerEndXact()
*
* Called just before the current transaction is committed. At this
* time we invoke all DEFERRED triggers and tidy up.
* ----------
*/
void
AfterTriggerEndXact(void)
{
AfterTriggerEventList *events;
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* ... but not inside a query */
Assert(afterTriggers->query_depth == -1);
/*
* If there are any triggers to fire, make sure we have set a snapshot
* for them to use. (Since PortalRunUtility doesn't set a snap for
* COMMIT, we can't assume ActiveSnapshot is valid on entry.)
*/
if (afterTriggers->events.head != NULL)
ActiveSnapshot = CopySnapshot(GetTransactionSnapshot());
/*
* Run all the remaining triggers. Loop until they are all gone,
* just in case some trigger queues more for us to do.
*/
events = &afterTriggers->events;
while (afterTriggerMarkEvents(events, NULL, false))
{
CommandId firing_id = afterTriggers->firing_counter++;
afterTriggerInvokeEvents(events, firing_id, true);
}
/*
* Forget everything we know about AFTER triggers.
*
* Since all the info is in TopTransactionContext or children thereof, we
* need do nothing special to reclaim memory.
*/
afterTriggers = NULL;
}
/* ----------
* AfterTriggerAbortXact()
*
* The current transaction has entered the abort state.
* All outstanding triggers are canceled so we simply throw
* away anything we know.
* ----------
*/
void
AfterTriggerAbortXact(void)
{
/*
* Ignore call if we aren't in a transaction. (Need this to survive
* repeat call in case of error during transaction abort.)
*/
if (afterTriggers == NULL)
return;
/*
* Forget everything we know about AFTER triggers.
*
* Since all the info is in TopTransactionContext or children thereof, we
* need do nothing special to reclaim memory.
*/
afterTriggers = NULL;
}
/*
* AfterTriggerBeginSubXact()
*
* Start a subtransaction.
*/
void
AfterTriggerBeginSubXact(void)
{
int my_level = GetCurrentTransactionNestLevel();
/*
* Ignore call if the transaction is in aborted state. (Probably
* shouldn't happen?)
*/
if (afterTriggers == NULL)
return;
/*
* Allocate more space in the stacks if needed.
*/
while (my_level >= afterTriggers->maxtransdepth)
{
if (afterTriggers->maxtransdepth == 0)
{
MemoryContext old_cxt;
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
#define DEFTRIG_INITALLOC 8
afterTriggers->state_stack = (SetConstraintState *)
palloc(DEFTRIG_INITALLOC * sizeof(SetConstraintState));
afterTriggers->events_stack = (AfterTriggerEventList *)
palloc(DEFTRIG_INITALLOC * sizeof(AfterTriggerEventList));
afterTriggers->depth_stack = (int *)
palloc(DEFTRIG_INITALLOC * sizeof(int));
afterTriggers->firing_stack = (CommandId *)
palloc(DEFTRIG_INITALLOC * sizeof(CommandId));
afterTriggers->maxtransdepth = DEFTRIG_INITALLOC;
MemoryContextSwitchTo(old_cxt);
}
else
{
/* repalloc will keep the stacks in the same context */
int new_alloc = afterTriggers->maxtransdepth * 2;
afterTriggers->state_stack = (SetConstraintState *)
repalloc(afterTriggers->state_stack,
new_alloc * sizeof(SetConstraintState));
afterTriggers->events_stack = (AfterTriggerEventList *)
repalloc(afterTriggers->events_stack,
new_alloc * sizeof(AfterTriggerEventList));
afterTriggers->depth_stack = (int *)
repalloc(afterTriggers->depth_stack,
new_alloc * sizeof(int));
afterTriggers->firing_stack = (CommandId *)
repalloc(afterTriggers->firing_stack,
new_alloc * sizeof(CommandId));
afterTriggers->maxtransdepth = new_alloc;
}
}
/*
* Push the current information into the stack. The SET CONSTRAINTS
* state is not saved until/unless changed.
*/
afterTriggers->state_stack[my_level] = NULL;
afterTriggers->events_stack[my_level] = afterTriggers->events;
afterTriggers->depth_stack[my_level] = afterTriggers->query_depth;
afterTriggers->firing_stack[my_level] = afterTriggers->firing_counter;
}
/*
* AfterTriggerEndSubXact()
*
* The current subtransaction is ending.
*/
void
AfterTriggerEndSubXact(bool isCommit)
{
int my_level = GetCurrentTransactionNestLevel();
SetConstraintState state;
AfterTriggerEvent event;
CommandId subxact_firing_id;
/*
* Ignore call if the transaction is in aborted state. (Probably unneeded)
*/
if (afterTriggers == NULL)
return;
/*
* Pop the prior state if needed.
*/
Assert(my_level < afterTriggers->maxtransdepth);
if (isCommit)
{
/* If we saved a prior state, we don't need it anymore */
state = afterTriggers->state_stack[my_level];
if (state != NULL)
pfree(state);
/* this avoids double pfree if error later: */
afterTriggers->state_stack[my_level] = NULL;
Assert(afterTriggers->query_depth ==
afterTriggers->depth_stack[my_level]);
}
else
{
/*
* Aborting --- restore the pointers from the stacks.
*/
afterTriggers->events = afterTriggers->events_stack[my_level];
afterTriggers->query_depth = afterTriggers->depth_stack[my_level];
/*
* Cleanup the tail of the list.
*/
if (afterTriggers->events.tail != NULL)
afterTriggers->events.tail->ate_next = NULL;
/*
* We don't need to free the subtransaction's items, since the
* CurTransactionContext will be reset shortly.
*/
/*
* Restore the trigger state. If the saved state is NULL, then
* this subxact didn't save it, so it doesn't need restoring.
*/
state = afterTriggers->state_stack[my_level];
if (state != NULL)
{
pfree(afterTriggers->state);
afterTriggers->state = state;
}
/* this avoids double pfree if error later: */
afterTriggers->state_stack[my_level] = NULL;
/*
* Scan for any remaining deferred events that were marked DONE
* or IN PROGRESS by this subxact or a child, and un-mark them.
* We can recognize such events because they have a firing ID
* greater than or equal to the firing_counter value we saved at
* subtransaction start. (This essentially assumes that the
* current subxact includes all subxacts started after it.)
*/
subxact_firing_id = afterTriggers->firing_stack[my_level];
for (event = afterTriggers->events.head;
event != NULL;
event = event->ate_next)
{
if (event->ate_event &
(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))
{
if (event->ate_firing_id >= subxact_firing_id)
event->ate_event &=
~(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS);
}
}
}
}
/*
* Create an empty SetConstraintState with room for numalloc trigstates
*/
static SetConstraintState
SetConstraintStateCreate(int numalloc)
{
SetConstraintState state;
/* Behave sanely with numalloc == 0 */
if (numalloc <= 0)
numalloc = 1;
/*
* We assume that zeroing will correctly initialize the state values.
*/
state = (SetConstraintState)
MemoryContextAllocZero(TopTransactionContext,
sizeof(SetConstraintStateData) +
(numalloc - 1) *sizeof(SetConstraintTriggerData));
state->numalloc = numalloc;
return state;
}
/*
* Copy a SetConstraintState
*/
static SetConstraintState
SetConstraintStateCopy(SetConstraintState origstate)
{
SetConstraintState state;
state = SetConstraintStateCreate(origstate->numstates);
state->all_isset = origstate->all_isset;
state->all_isdeferred = origstate->all_isdeferred;
state->numstates = origstate->numstates;
memcpy(state->trigstates, origstate->trigstates,
origstate->numstates * sizeof(SetConstraintTriggerData));
return state;
}
/*
* Add a per-trigger item to a SetConstraintState. Returns possibly-changed
* pointer to the state object (it will change if we have to repalloc).
*/
static SetConstraintState
SetConstraintStateAddItem(SetConstraintState state,
Oid tgoid, bool tgisdeferred)
{
if (state->numstates >= state->numalloc)
{
int newalloc = state->numalloc * 2;
newalloc = Max(newalloc, 8); /* in case original has size 0 */
state = (SetConstraintState)
repalloc(state,
sizeof(SetConstraintStateData) +
(newalloc - 1) *sizeof(SetConstraintTriggerData));
state->numalloc = newalloc;
Assert(state->numstates < state->numalloc);
}
state->trigstates[state->numstates].sct_tgoid = tgoid;
state->trigstates[state->numstates].sct_tgisdeferred = tgisdeferred;
state->numstates++;
return state;
}
/* ----------
* AfterTriggerSetState()
*
* Execute the SET CONSTRAINTS ... utility command.
* ----------
*/
void
AfterTriggerSetState(ConstraintsSetStmt *stmt)
{
int my_level = GetCurrentTransactionNestLevel();
/*
* Ignore call if we aren't in a transaction. (Shouldn't happen?)
*/
if (afterTriggers == NULL)
return;
/*
* If in a subtransaction, and we didn't save the current state
* already, save it so it can be restored if the subtransaction
* aborts.
*/
if (my_level > 1 &&
afterTriggers->state_stack[my_level] == NULL)
{
afterTriggers->state_stack[my_level] =
SetConstraintStateCopy(afterTriggers->state);
}
/*
* Handle SET CONSTRAINTS ALL ...
*/
if (stmt->constraints == NIL)
{
/*
* Forget any previous SET CONSTRAINTS commands in this transaction.
*/
afterTriggers->state->numstates = 0;
/*
* Set the per-transaction ALL state to known.
*/
afterTriggers->state->all_isset = true;
afterTriggers->state->all_isdeferred = stmt->deferred;
}
else
{
Relation tgrel;
ListCell *l;
List *oidlist = NIL;
/* ----------
* Handle SET CONSTRAINTS constraint-name [, ...]
* First lookup all trigger Oid's for the constraint names.
* ----------
*/
tgrel = heap_openr(TriggerRelationName, AccessShareLock);
foreach(l, stmt->constraints)
{
char *cname = strVal(lfirst(l));
ScanKeyData skey;
SysScanDesc tgscan;
HeapTuple htup;
bool found;
/*
* Check that only named constraints are set explicitly
*/
if (strlen(cname) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("unnamed constraints cannot be set explicitly")));
/*
* Setup to scan pg_trigger by tgconstrname ...
*/
ScanKeyInit(&skey,
Anum_pg_trigger_tgconstrname,
BTEqualStrategyNumber, F_NAMEEQ,
PointerGetDatum(cname));
tgscan = systable_beginscan(tgrel, TriggerConstrNameIndex, true,
SnapshotNow, 1, &skey);
/*
* ... and search for the constraint trigger row
*/
found = false;
while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
{
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
/*
* If we found some, check that they fit the deferrability
* but skip referential action ones, since they are
* silently never deferrable.
*/
if (pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_UPD &&
pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_DEL &&
pg_trigger->tgfoid != F_RI_FKEY_CASCADE_UPD &&
pg_trigger->tgfoid != F_RI_FKEY_CASCADE_DEL &&
pg_trigger->tgfoid != F_RI_FKEY_SETNULL_UPD &&
pg_trigger->tgfoid != F_RI_FKEY_SETNULL_DEL &&
pg_trigger->tgfoid != F_RI_FKEY_SETDEFAULT_UPD &&
pg_trigger->tgfoid != F_RI_FKEY_SETDEFAULT_DEL)
{
if (stmt->deferred && !pg_trigger->tgdeferrable)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" is not deferrable",
cname)));
oidlist = lappend_oid(oidlist, HeapTupleGetOid(htup));
}
found = true;
}
systable_endscan(tgscan);
/*
* Not found ?
*/
if (!found)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" does not exist",
cname)));
}
heap_close(tgrel, AccessShareLock);
/*
* Set the trigger states of individual triggers for this xact.
*/
foreach(l, oidlist)
{
Oid tgoid = lfirst_oid(l);
SetConstraintState state = afterTriggers->state;
bool found = false;
int i;
for (i = 0; i < state->numstates; i++)
{
if (state->trigstates[i].sct_tgoid == tgoid)
{
state->trigstates[i].sct_tgisdeferred = stmt->deferred;
found = true;
break;
}
}
if (!found)
{
afterTriggers->state =
SetConstraintStateAddItem(state, tgoid, stmt->deferred);
}
}
}
/*
* SQL99 requires that when a constraint is set to IMMEDIATE, any
* deferred checks against that constraint must be made when the SET
* CONSTRAINTS command is executed -- i.e. the effects of the SET
* CONSTRAINTS command apply retroactively. We've updated the
* constraints state, so scan the list of previously deferred events
* to fire any that have now become immediate.
*
* Obviously, if this was SET ... DEFERRED then it can't have converted
* any unfired events to immediate, so we need do nothing in that case.
*/
if (!stmt->deferred)
{
AfterTriggerEventList *events = &afterTriggers->events;
if (afterTriggerMarkEvents(events, NULL, true))
{
CommandId firing_id = afterTriggers->firing_counter++;
/*
* We can delete fired events if we are at top transaction
* level, but we'd better not if inside a subtransaction, since
* the subtransaction could later get rolled back.
*/
afterTriggerInvokeEvents(events, firing_id,
!IsSubTransaction());
}
}
}
/* ----------
* AfterTriggerSaveEvent()
*
* Called by ExecA[RS]...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.
* ----------
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
AfterTriggerEvent new_event;
int i;
int ntriggers;
int *tgindx;
ItemPointerData oldctid;
ItemPointerData newctid;
if (afterTriggers == NULL)
elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction");
/*
* Get the CTID's of OLD and NEW
*/
if (oldtup != NULL)
ItemPointerCopy(&(oldtup->t_self), &(oldctid));
else
ItemPointerSetInvalid(&(oldctid));
if (newtup != NULL)
ItemPointerCopy(&(newtup->t_self), &(newctid));
else
ItemPointerSetInvalid(&(newctid));
/*
* Scan the appropriate set of triggers
*/
if (row_trigger)
{
ntriggers = trigdesc->n_after_row[event];
tgindx = trigdesc->tg_after_row[event];
}
else
{
ntriggers = trigdesc->n_after_statement[event];
tgindx = trigdesc->tg_after_statement[event];
}
for (i = 0; i < ntriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
/* Ignore disabled triggers */
if (!trigger->tgenabled)
continue;
/*
* If it is an RI UPDATE trigger, and the referenced keys have
* not changed, short-circuit queuing of the event; there's no
* need to fire the trigger.
*/
if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
{
bool is_ri_trigger;
switch (trigger->tgfoid)
{
case F_RI_FKEY_NOACTION_UPD:
case F_RI_FKEY_CASCADE_UPD:
case F_RI_FKEY_RESTRICT_UPD:
case F_RI_FKEY_SETNULL_UPD:
case F_RI_FKEY_SETDEFAULT_UPD:
is_ri_trigger = true;
break;
default:
is_ri_trigger = false;
break;
}
if (is_ri_trigger)
{
TriggerData LocTriggerData;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event =
TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW;
LocTriggerData.tg_relation = rel;
LocTriggerData.tg_trigtuple = oldtup;
LocTriggerData.tg_newtuple = newtup;
LocTriggerData.tg_trigger = trigger;
/*
* We do not currently know which buffers the passed tuples
* are in, but it does not matter because RI_FKey_keyequal_upd
* does not care. We could expand the API of this function
* if it becomes necessary to set these fields accurately.
*/
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
if (RI_FKey_keyequal_upd(&LocTriggerData))
{
/* key unchanged, so skip queuing this event */
continue;
}
}
}
/*
* Create a new event. We use the CurTransactionContext so the event
* will automatically go away if the subtransaction aborts.
*/
new_event = (AfterTriggerEvent)
MemoryContextAlloc(CurTransactionContext,
sizeof(AfterTriggerEventData));
new_event->ate_next = NULL;
new_event->ate_event =
(event & TRIGGER_EVENT_OPMASK) |
(row_trigger ? TRIGGER_EVENT_ROW : 0) |
(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
new_event->ate_firing_id = 0;
new_event->ate_tgoid = trigger->tgoid;
new_event->ate_relid = rel->rd_id;
ItemPointerCopy(&oldctid, &(new_event->ate_oldctid));
ItemPointerCopy(&newctid, &(new_event->ate_newctid));
/*
* Add the new event to the queue.
*/
afterTriggerAddEvent(new_event);
}
}