/*------------------------------------------------------------------------- * * 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); } }