| /*------------------------------------------------------------------------- |
| * |
| * trigger.c |
| * PostgreSQL TRIGGERs support code. |
| * |
| * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.210.2.4 2007/08/15 19:15:55 tgl Exp $ |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/genam.h" |
| #include "access/heapam.h" |
| #include "access/xact.h" |
| #include "catalog/catalog.h" |
| #include "catalog/catquery.h" |
| #include "catalog/dependency.h" |
| #include "catalog/indexing.h" |
| #include "catalog/pg_namespace.h" |
| #include "catalog/pg_proc.h" |
| #include "catalog/pg_trigger.h" |
| #include "catalog/pg_type.h" |
| #include "commands/dbcommands.h" |
| #include "commands/defrem.h" |
| #include "commands/trigger.h" |
| #include "executor/executor.h" |
| #include "executor/instrument.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/memutils.h" |
| #include "utils/syscache.h" |
| #include "cdb/cdbvars.h" |
| #include "cdb/cdbdisp.h" |
| #include "cdb/dispatcher.h" |
| |
| |
| static void InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx); |
| static HeapTuple GetTupleForTrigger(EState *estate, |
| ResultRelInfo *relinfo, |
| ItemPointer tid, |
| CommandId cid, |
| TupleTableSlot **newSlot); |
| |
| static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, |
| bool row_trigger, HeapTuple oldtup, HeapTuple newtup); |
| |
| |
| /* |
| * Create a trigger. Returns the OID of the created trigger. |
| * |
| * relOid, if nonzero, is the relation on which the trigger should be |
| * created. If zero, the name provided in the statement will be looked up. |
| * |
| * refRelOid, if nonzero, is the relation to which the constraint trigger |
| * refers. If zero, the constraint relation name provided in the statement |
| * will be looked up as needed. |
| * |
| * 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, Oid relOid, Oid refRelOid, |
| bool forConstraint) |
| { |
| int16 tgtype; |
| int2vector *tgattr; |
| Datum values[Natts_pg_trigger]; |
| bool nulls[Natts_pg_trigger]; |
| Relation rel; |
| AclResult aclresult; |
| Relation tgrel; |
| cqContext *pcqCtx; |
| cqContext cqc; |
| cqContext *pcqCtx2; |
| cqContext cqc2; |
| Relation pgrel; |
| HeapTuple tuple; |
| Oid fargtypes[1]; /* dummy */ |
| Oid funcoid; |
| Oid funcrettype; |
| Oid trigoid; |
| int found = 0; |
| int i; |
| char constrtrigname[NAMEDATALEN]; |
| char *trigname; |
| char *constrname; |
| Oid constrrelid = InvalidOid; |
| ObjectAddress myself, |
| referenced; |
| |
| if (OidIsValid(relOid)) |
| { |
| rel = heap_open(relOid, AccessExclusiveLock); |
| } |
| else |
| { |
| rel = heap_openrv(stmt->relation, AccessExclusiveLock); |
| } |
| |
| if (OidIsValid(refRelOid)) |
| { |
| constrrelid = refRelOid; |
| } |
| else if (stmt->constrrel != NULL) |
| { |
| constrrelid = RangeVarGetRelid(stmt->constrrel, false, false /*allowHcatalog*/); |
| } |
| 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 /*catalogname*/, NULL, strVal(elem), -1); |
| |
| constrrelid = RangeVarGetRelid(rel, true, false /*allowHcatalog*/); |
| } |
| if (needconstrrelid && constrrelid == InvalidOid) |
| if (Gp_role != GP_ROLE_EXECUTE) |
| 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 (!allowSystemTableModsDDL && 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. |
| */ |
| tgrel = heap_open(TriggerRelationId, RowExclusiveLock); |
| |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), tgrel), |
| cql("INSERT INTO pg_trigger ", |
| NULL)); |
| |
| if (OidIsValid(stmt->trigOid)) |
| trigoid = stmt->trigOid; |
| else |
| trigoid = GetNewOid(tgrel); |
| |
| /* |
| * 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. |
| */ |
| pcqCtx2 = caql_beginscan( |
| caql_addrel(cqclr(&cqc2), tgrel), |
| cql("SELECT * FROM pg_trigger " |
| " WHERE tgrelid = :1 ", |
| ObjectIdGetDatum(RelationGetRelid(rel)))); |
| |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx2))) |
| { |
| 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, RelationGetRelationName(rel)))); |
| found++; |
| } |
| caql_endscan(pcqCtx2); |
| |
| /* |
| * Find and validate the trigger function. |
| */ |
| funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false); |
| aclresult = pg_proc_aclcheck(funcoid, GetUserId(), ACL_EXECUTE); |
| if (aclresult != ACLCHECK_OK) |
| aclcheck_error(aclresult, ACL_KIND_PROC, |
| NameListToString(stmt->funcname)); |
| 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) |
| { |
| if (Gp_role != GP_ROLE_EXECUTE) |
| 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, false, Natts_pg_trigger * sizeof(bool)); |
| |
| 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); |
| |
| /* |
| * Special for Greenplum Database: Ignore foreign keys for now |
| */ |
| if ((stmt->isconstraint) && (Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_EXECUTE)) |
| { |
| /* |
| * Create the tigger as disabled |
| */ |
| values[Anum_pg_trigger_tgenabled - 1] = BoolGetDatum(false); |
| } |
| else |
| 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("")); |
| } |
| /* tgattr is currently always a zero-length array */ |
| tgattr = buildint2vector(NULL, 0); |
| values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr); |
| |
| tuple = caql_form_tuple(pcqCtx, values, nulls); |
| |
| /* force tuple to have the desired OID */ |
| HeapTupleSetOid(tuple, trigoid); |
| |
| /* |
| * Insert tuple into pg_trigger. |
| */ |
| caql_insert(pcqCtx, tuple); /* implicit update of index as well */ |
| |
| myself.classId = TriggerRelationId; |
| myself.objectId = trigoid; |
| myself.objectSubId = 0; |
| |
| heap_freetuple(tuple); |
| caql_endscan(pcqCtx); |
| 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_open(RelationRelationId, RowExclusiveLock); |
| |
| pcqCtx = caql_addrel(cqclr(&cqc), pgrel); |
| |
| tuple = caql_getfirst( |
| pcqCtx, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(RelationGetRelid(rel)))); |
| |
| if (!HeapTupleIsValid(tuple)) |
| elog(ERROR, "cache lookup failed for relation %u", |
| RelationGetRelid(rel)); |
| |
| ((Form_pg_class) GETSTRUCT(tuple))->reltriggers = found + 1; |
| |
| caql_update_current(pcqCtx, tuple); |
| /* and Update indexes (implicit) */ |
| |
| 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 = ProcedureRelationId; |
| referenced.objectId = funcoid; |
| referenced.objectSubId = 0; |
| recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
| |
| if (!forConstraint) |
| { |
| referenced.classId = RelationRelationId; |
| referenced.objectId = RelationGetRelid(rel); |
| referenced.objectSubId = 0; |
| recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); |
| if (constrrelid != InvalidOid) |
| { |
| referenced.classId = RelationRelationId; |
| 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, |
| bool missing_ok) |
| { |
| int fetchCount; |
| ObjectAddress object; |
| |
| /* |
| * Find the trigger, verify permissions, set up object address |
| */ |
| object.classId = TriggerRelationId; |
| object.objectSubId = 0; |
| |
| object.objectId = caql_getoid_plus( |
| NULL, |
| &fetchCount, |
| NULL, |
| cql("SELECT oid FROM pg_trigger " |
| " WHERE tgrelid = :1 " |
| " AND tgname = :2 ", |
| ObjectIdGetDatum(relid), |
| CStringGetDatum((char *) trigname))); |
| |
| if (0 == fetchCount) |
| { |
| if (!missing_ok) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("trigger \"%s\" for table \"%s\" does not exist", |
| trigname, get_rel_name(relid)))); |
| else |
| ereport(NOTICE, |
| (errmsg("trigger \"%s\" for table \"%s\" does not exist, skipping", |
| trigname, get_rel_name(relid)))); |
| /* cleanup */ |
| return; |
| } |
| |
| if (!pg_class_ownercheck(relid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, |
| get_rel_name(relid)); |
| |
| |
| /* |
| * Do the deletion |
| */ |
| performDeletion(&object, behavior); |
| } |
| |
| /* |
| * Guts of trigger deletion. |
| */ |
| void |
| RemoveTriggerById(Oid trigOid) |
| { |
| Relation tgrel; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| HeapTuple tup; |
| Oid relid; |
| Relation rel; |
| Relation pgrel; |
| HeapTuple tuple; |
| Form_pg_class classForm; |
| |
| tgrel = heap_open(TriggerRelationId, RowExclusiveLock); |
| |
| pcqCtx = caql_addrel(cqclr(&cqc), tgrel); |
| |
| /* |
| * Find the trigger to delete. |
| */ |
| tup = caql_getfirst( |
| pcqCtx, |
| cql("SELECT * FROM pg_trigger " |
| " WHERE oid = :1 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(trigOid))); |
| |
| 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 (!allowSystemTableModsDDL && IsSystemRelation(rel)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("permission denied: \"%s\" is a system catalog", |
| RelationGetRelationName(rel)))); |
| |
| /* |
| * Delete the pg_trigger tuple. |
| */ |
| caql_delete_current(pcqCtx); |
| |
| 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_open(RelationRelationId, RowExclusiveLock); |
| |
| pcqCtx = caql_addrel(cqclr(&cqc), pgrel); |
| |
| tuple = caql_getfirst( |
| pcqCtx, |
| cql("SELECT * FROM pg_class " |
| " WHERE oid = :1 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(relid))); |
| |
| 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--; |
| |
| caql_update_current(pcqCtx, tuple); |
| /* and Update indexes (implicit) */ |
| |
| 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; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| |
| /* |
| * 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_open(TriggerRelationId, RowExclusiveLock); |
| |
| /* |
| * First pass -- look for name conflict |
| */ |
| |
| if (caql_getcount( |
| caql_addrel(cqclr(&cqc), tgrel), |
| cql("SELECT count(*) FROM pg_trigger " |
| " WHERE tgrelid = :1 " |
| " AND tgname = :2 ", |
| ObjectIdGetDatum(relid), |
| PointerGetDatum((char *) newname)))) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_OBJECT), |
| errmsg("trigger \"%s\" for relation \"%s\" already exists", |
| newname, RelationGetRelationName(targetrel)))); |
| } |
| |
| |
| /* |
| * Second pass -- look for trigger existing with oldname and update |
| */ |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), tgrel), |
| cql("SELECT * FROM pg_trigger " |
| " WHERE tgrelid = :1 " |
| " AND tgname = :2 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(relid), |
| PointerGetDatum((char *) oldname))); |
| |
| if (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| /* |
| * Update pg_trigger tuple with new tgname. |
| */ |
| tuple = heap_copytuple(tuple); /* need a modifiable copy */ |
| |
| namestrcpy(&((Form_pg_trigger) GETSTRUCT(tuple))->tgname, newname); |
| |
| caql_update_current(pcqCtx, 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)))); |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| heap_close(tgrel, RowExclusiveLock); |
| |
| /* |
| * Close rel, but keep exclusive lock! |
| */ |
| heap_close(targetrel, NoLock); |
| } |
| |
| |
| /* |
| * EnableDisableTrigger() |
| * |
| * Called by ALTER TABLE ENABLE/DISABLE TRIGGER |
| * to change 'tgenabled' flag for the specified trigger(s) |
| * |
| * rel: relation to process (caller must hold suitable lock on it) |
| * tgname: trigger to process, or NULL to scan all triggers |
| * enable: new value for tgenabled flag |
| * skip_system: if true, skip "system" triggers (constraint triggers) |
| * |
| * Caller should have checked permissions for the table; here we also |
| * enforce that superuser privilege is required to alter the state of |
| * system triggers |
| */ |
| void |
| EnableDisableTrigger(Relation rel, const char *tgname, |
| bool enable, bool skip_system) |
| { |
| Relation tgrel; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| HeapTuple tuple; |
| bool found; |
| bool changed; |
| |
| /* Scan the relevant entries in pg_triggers */ |
| tgrel = heap_open(TriggerRelationId, RowExclusiveLock); |
| |
| if (tgname) |
| { |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), tgrel), |
| cql("SELECT * FROM pg_trigger " |
| " WHERE tgrelid = :1 " |
| " AND tgname = :2 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(RelationGetRelid(rel)), |
| CStringGetDatum((char *) tgname))); |
| } |
| else |
| { |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), tgrel), |
| cql("SELECT * FROM pg_trigger " |
| " WHERE tgrelid = :1 " |
| " FOR UPDATE ", |
| ObjectIdGetDatum(RelationGetRelid(rel)))); |
| } |
| |
| found = changed = false; |
| |
| while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx))) |
| { |
| Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple); |
| |
| if (oldtrig->tgisconstraint) |
| { |
| /* system trigger ... ok to process? */ |
| if (skip_system) |
| continue; |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("permission denied: \"%s\" is a system trigger", |
| NameStr(oldtrig->tgname)))); |
| } |
| |
| found = true; |
| |
| if (oldtrig->tgenabled != enable) |
| { |
| /* need to change this one ... make a copy to scribble on */ |
| HeapTuple newtup = heap_copytuple(tuple); |
| Form_pg_trigger newtrig = (Form_pg_trigger) GETSTRUCT(newtup); |
| |
| newtrig->tgenabled = enable; |
| |
| caql_update_current(pcqCtx, newtup); |
| /* and Update indexes (implicit) */ |
| |
| heap_freetuple(newtup); |
| |
| changed = true; |
| } |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| heap_close(tgrel, RowExclusiveLock); |
| |
| if (tgname && !found) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("trigger \"%s\" for table \"%s\" does not exist", |
| tgname, RelationGetRelationName(rel)))); |
| |
| /* |
| * If we changed anything, broadcast a SI inval message to force each |
| * backend (including our own!) to rebuild relation's relcache entry. |
| * Otherwise they will fail to apply the change promptly. |
| */ |
| if (changed) |
| CacheInvalidateRelcache(rel); |
| } |
| |
| |
| /* |
| * 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; |
| cqContext cqc; |
| cqContext *pcqCtx; |
| 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 TriggerRelidNameIndexId, we will |
| * be reading the triggers in name order, except possibly during |
| * emergency-recovery operations (ie, IgnoreSystemIndexes). This in turn |
| * ensures that triggers will be fired in name order. |
| */ |
| |
| /* XXX XXX: ORDER BY */ |
| tgrel = heap_open(TriggerRelationId, AccessShareLock); |
| |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), tgrel), |
| cql("SELECT * FROM pg_trigger " |
| " WHERE tgrelid = :1 " |
| " ORDER BY tgrelid, tgname", |
| ObjectIdGetDatum(RelationGetRelid(relation)))); |
| |
| while (HeapTupleIsValid(htup = caql_getnext(pcqCtx))) |
| { |
| 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; |
| /* tgattr is first var-width field, so OK to access directly */ |
| build->tgnattr = pg_trigger->tgattr.dim1; |
| if (build->tgnattr > 0) |
| { |
| build->tgattr = (int2 *) palloc(build->tgnattr * sizeof(int2)); |
| memcpy(build->tgattr, &(pg_trigger->tgattr.values), |
| build->tgnattr * sizeof(int2)); |
| } |
| else |
| build->tgattr = NULL; |
| if (build->tgnargs > 0) |
| { |
| bytea *val; |
| bool isnull; |
| char *p; |
| int i; |
| |
| val = DatumGetByteaP(fastgetattr(htup, |
| Anum_pg_trigger_tgargs, |
| RelationGetDescr(tgrel), &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++; |
| } |
| |
| caql_endscan(pcqCtx); |
| 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->tgnattr > 0) |
| { |
| int2 *newattr; |
| |
| newattr = (int2 *) palloc(trigger->tgnattr * sizeof(int2)); |
| memcpy(newattr, trigger->tgattr, |
| trigger->tgnattr * sizeof(int2)); |
| trigger->tgattr = newattr; |
| } |
| 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->tgnattr > 0) |
| pfree(trigger->tgattr); |
| 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 (trig1->tgnattr != trig2->tgnattr) |
| return false; |
| if (trig1->tgnattr > 0 && |
| memcmp(trig1->tgattr, trig2->tgattr, |
| trig1->tgnattr * sizeof(int2)) != 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. |
| * tgindx: trigger's index in finfo and instr arrays. |
| * finfo: array of cached trigger function call information. |
| * instr: optional array of EXPLAIN ANALYZE instrumentation state. |
| * per_tuple_context: memory context to execute the function in. |
| * |
| * Returns the tuple (or NULL) as returned by the function. |
| */ |
| HeapTuple |
| ExecCallTriggerFunc(TriggerData *trigdata, |
| int tgindx, |
| FmgrInfo *finfo, |
| Instrumentation *instr, |
| MemoryContext per_tuple_context) |
| { |
| FunctionCallInfoData fcinfo; |
| Datum result; |
| MemoryContext oldContext; |
| |
| finfo += tgindx; |
| |
| /* |
| * 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); |
| |
| /* |
| * If doing EXPLAIN ANALYZE, start charging time to this trigger. |
| */ |
| if (instr) |
| InstrStartNode(instr + tgindx); |
| |
| /* |
| * 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. |
| */ |
| InitFunctionCallInfoData(fcinfo, finfo, 0, (Node *) trigdata, NULL); |
| |
| 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))); |
| |
| /* |
| * If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count |
| * one "tuple returned" (really the number of firings). |
| */ |
| if (instr) |
| InstrStopNode(instr + tgindx, 1); |
| |
| 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; |
| |
| 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, |
| tgindx[i], |
| relinfo->ri_TrigFunctions, |
| relinfo->ri_TrigInstrument, |
| 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; |
| |
| 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, |
| tgindx[i], |
| relinfo->ri_TrigFunctions, |
| relinfo->ri_TrigInstrument, |
| 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) |
| { |
| if(RelationIsParquet(relinfo->ri_RelationDesc)) |
| elog(ERROR, "Trigger is not supported on Parquet yet"); |
| |
| 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; |
| |
| 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, |
| tgindx[i], |
| relinfo->ri_TrigFunctions, |
| relinfo->ri_TrigInstrument, |
| 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]; |
| bool result = true; |
| TriggerData LocTriggerData; |
| HeapTuple trigtuple; |
| HeapTuple newtuple; |
| TupleTableSlot *newSlot; |
| int i; |
| |
| trigtuple = GetTupleForTrigger(estate, relinfo, tupleid, cid, &newSlot); |
| if (trigtuple == NULL) |
| return false; |
| |
| 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, |
| tgindx[i], |
| relinfo->ri_TrigFunctions, |
| relinfo->ri_TrigInstrument, |
| GetPerTupleMemoryContext(estate)); |
| if (newtuple == NULL) |
| { |
| result = false; /* tell caller to suppress delete */ |
| break; |
| } |
| if (newtuple != trigtuple) |
| heap_freetuple(newtuple); |
| } |
| heap_freetuple(trigtuple); |
| |
| return result; |
| } |
| |
| 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; |
| |
| 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, |
| tgindx[i], |
| relinfo->ri_TrigFunctions, |
| relinfo->ri_TrigInstrument, |
| 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); |
| |
| 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, |
| tgindx[i], |
| relinfo->ri_TrigFunctions, |
| relinfo->ri_TrigInstrument, |
| 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) |
| { |
| MIRROREDLOCK_BUFMGR_DECLARE; |
| |
| Relation relation = relinfo->ri_RelationDesc; |
| HeapTupleData tuple; |
| HeapTuple result; |
| Buffer buffer; |
| |
| if (newSlot != NULL) |
| { |
| HTSU_Result test; |
| ItemPointerData update_ctid; |
| TransactionId update_xmax; |
| |
| *newSlot = NULL; |
| |
| /* |
| * lock tuple for update |
| */ |
| ltrmark:; |
| tuple.t_self = *tid; |
| test = heap_lock_tuple(relation, &tuple, &buffer, |
| &update_ctid, &update_xmax, cid, |
| LockTupleExclusive, LockTupleWait); |
| 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(&update_ctid, &tuple.t_self)) |
| { |
| /* it was updated, so look at the updated version */ |
| TupleTableSlot *epqslot; |
| |
| epqslot = EvalPlanQual(estate, |
| relinfo->ri_RangeTableIndex, |
| &update_ctid, |
| update_xmax, |
| cid); |
| if (!TupIsNull(epqslot)) |
| { |
| *tid = update_ctid; |
| *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_lock_tuple status: %u", test); |
| return NULL; /* keep compiler quiet */ |
| } |
| } |
| else |
| { |
| PageHeader dp; |
| ItemId lp; |
| |
| // -------- MirroredLock ---------- |
| MIRROREDLOCK_BUFMGR_LOCK; |
| |
| buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); |
| |
| MIRROREDLOCK_BUFMGR_UNLOCK; |
| // -------- MirroredLock ---------- |
| |
| dp = (PageHeader) BufferGetPage(buffer); |
| lp = PageGetItemId(dp, ItemPointerGetOffsetNumber(tid)); |
| |
| Assert(ItemIdIsUsed(lp)); |
| |
| 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 separate contexts, to make them |
| * easy to delete 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. The event |
| * structs themselves are stored in event_cxt if generated by the top-level |
| * transaction, else in per-subtransaction contexts identified by the |
| * entries in cxt_stack. |
| * |
| * 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 */ |
| MemoryContext event_cxt; /* top transaction's event context, if any */ |
| MemoryContext *cxt_stack; /* per-subtransaction event contexts */ |
| |
| /* 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, |
| Instrumentation *instr, |
| 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. (For triggers fired at |
| * the end of a query, we can even piggyback on the executor's state.) |
| * |
| * 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). |
| * instr: array of EXPLAIN ANALYZE instrumentation nodes (one per trigger), |
| * or NULL if no instrumentation is wanted. |
| * per_tuple_context: memory context to call trigger function in. |
| * ---------- |
| */ |
| static void |
| AfterTriggerExecute(AfterTriggerEvent event, |
| Relation rel, TriggerDesc *trigdesc, |
| FmgrInfo *finfo, Instrumentation *instr, |
| 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; |
| |
| /* |
| * Locate trigger in trigdesc. |
| */ |
| 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); |
| |
| /* |
| * If doing EXPLAIN ANALYZE, start charging time to this trigger. We want |
| * to include time spent re-fetching tuples in the trigger cost. |
| */ |
| if (instr) |
| InstrStartNode(instr + tgindx); |
| |
| /* |
| * Fetch the required OLD and NEW tuples. |
| */ |
| LocTriggerData.tg_trigtuple = NULL; |
| LocTriggerData.tg_newtuple = NULL; |
| LocTriggerData.tg_trigtuplebuf = InvalidBuffer; |
| LocTriggerData.tg_newtuplebuf = InvalidBuffer; |
| |
| 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"); |
| LocTriggerData.tg_trigtuple = &oldtuple; |
| LocTriggerData.tg_trigtuplebuf = oldbuffer; |
| } |
| |
| 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"); |
| if (LocTriggerData.tg_trigtuple != NULL) |
| { |
| LocTriggerData.tg_newtuple = &newtuple; |
| LocTriggerData.tg_newtuplebuf = newbuffer; |
| } |
| else |
| { |
| LocTriggerData.tg_trigtuple = &newtuple; |
| LocTriggerData.tg_trigtuplebuf = newbuffer; |
| } |
| } |
| |
| /* |
| * Setup the remaining trigger information |
| */ |
| LocTriggerData.type = T_TriggerData; |
| LocTriggerData.tg_event = |
| event->ate_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW); |
| LocTriggerData.tg_relation = rel; |
| |
| MemoryContextReset(per_tuple_context); |
| |
| /* |
| * Call the trigger and throw away any possibly returned updated tuple. |
| * (Don't let ExecCallTriggerFunc measure EXPLAIN time.) |
| */ |
| rettuple = ExecCallTriggerFunc(&LocTriggerData, |
| tgindx, |
| finfo, |
| NULL, |
| 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); |
| |
| /* |
| * If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count |
| * one "tuple returned" (really the number of firings). |
| */ |
| if (instr) |
| InstrStopNode(instr + tgindx, 1); |
| } |
| |
| |
| /* |
| * 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. |
| * |
| * If estate isn't NULL, then we expect that all the firable events are |
| * for triggers of the relations included in the estate's result relation |
| * array. This allows us to re-use the estate's open relations and |
| * trigger cache info. When estate is NULL, we have to find the relations |
| * the hard way. |
| * |
| * 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, |
| EState *estate, |
| bool delete_ok) |
| { |
| AfterTriggerEvent event, |
| prev_event; |
| MemoryContext per_tuple_context; |
| bool locally_opened = false; |
| Relation rel = NULL; |
| TriggerDesc *trigdesc = NULL; |
| FmgrInfo *finfo = NULL; |
| Instrumentation *instr = 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 (locally_opened) |
| { |
| /* close prior rel if any */ |
| if (rel) |
| heap_close(rel, NoLock); |
| if (trigdesc) |
| FreeTriggerDesc(trigdesc); |
| if (finfo) |
| pfree(finfo); |
| Assert(instr == NULL); /* never used in this case */ |
| } |
| locally_opened = true; |
| |
| if (estate) |
| { |
| /* Find target relation among estate's result rels */ |
| ResultRelInfo *rInfo; |
| int nr; |
| |
| rInfo = estate->es_result_relations; |
| nr = estate->es_num_result_relations; |
| while (nr > 0) |
| { |
| if (rInfo->ri_RelationDesc->rd_id == event->ate_relid) |
| { |
| rel = rInfo->ri_RelationDesc; |
| trigdesc = rInfo->ri_TrigDesc; |
| finfo = rInfo->ri_TrigFunctions; |
| instr = rInfo->ri_TrigInstrument; |
| locally_opened = false; |
| break; |
| } |
| rInfo++; |
| nr--; |
| } |
| } |
| |
| if (locally_opened) |
| { |
| /* Hard way: open target relation for ourselves */ |
| |
| /* |
| * 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)); |
| |
| /* Never any EXPLAIN info in this case */ |
| instr = NULL; |
| } |
| } |
| |
| /* |
| * 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, instr, |
| 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 (locally_opened) |
| { |
| if (rel) |
| heap_close(rel, NoLock); |
| if (trigdesc) |
| FreeTriggerDesc(trigdesc); |
| if (finfo) |
| pfree(finfo); |
| Assert(instr == NULL); /* never used in this case */ |
| } |
| 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; |
| |
| /* Context for events is created only when needed */ |
| afterTriggers->event_cxt = NULL; |
| |
| /* Subtransaction stack is empty until/unless needed */ |
| afterTriggers->cxt_stack = NULL; |
| 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. |
| * |
| * Note that this should be called just BEFORE closing down the executor |
| * with ExecutorEnd, because we make use of the EState's info about |
| * target relations. |
| * ---------- |
| */ |
| void |
| AfterTriggerEndQuery(EState *estate) |
| { |
| 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. |
| * |
| * We loop in case a trigger queues more events. |
| * |
| * If we find no firable events, we don't have to increment |
| * firing_counter. |
| */ |
| events = &afterTriggers->query_stack[afterTriggers->query_depth]; |
| while (afterTriggerMarkEvents(events, &afterTriggers->events, true)) |
| { |
| CommandId firing_id = afterTriggers->firing_counter++; |
| |
| /* OK to delete the immediate events after processing them */ |
| afterTriggerInvokeEvents(events, firing_id, estate, true); |
| } |
| |
| afterTriggers->query_depth--; |
| } |
| |
| |
| /* ---------- |
| * AfterTriggerFireDeferred() |
| * |
| * Called just before the current transaction is committed. At this |
| * time we invoke all pending DEFERRED triggers. |
| * |
| * It is possible for other modules to queue additional deferred triggers |
| * during pre-commit processing; therefore xact.c may have to call this |
| * multiple times. |
| * ---------- |
| */ |
| void |
| AfterTriggerFireDeferred(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.) |
| */ |
| events = &afterTriggers->events; |
| if (events->head != NULL) |
| ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); |
| |
| /* |
| * Run all the remaining triggers. Loop until they are all gone, in |
| * case some trigger queues more for us to do. |
| */ |
| while (afterTriggerMarkEvents(events, NULL, false)) |
| { |
| CommandId firing_id = afterTriggers->firing_counter++; |
| |
| afterTriggerInvokeEvents(events, firing_id, NULL, true); |
| } |
| |
| Assert(events->head == NULL); |
| } |
| |
| |
| /* ---------- |
| * AfterTriggerEndXact() |
| * |
| * The current transaction is finishing. |
| * |
| * Any unfired triggers are canceled so we simply throw |
| * away anything we know. |
| * |
| * Note: it is possible for this to be called repeatedly in case of |
| * error during transaction abort; therefore, do not complain if |
| * already closed down. |
| * ---------- |
| */ |
| void |
| AfterTriggerEndXact(bool isCommit) |
| { |
| /* |
| * Forget everything we know about AFTER triggers. |
| * |
| * Since all the info is in TopTransactionContext or children thereof, we |
| * don't really need to do anything to reclaim memory. However, the |
| * pending-events list could be large, and so it's useful to discard |
| * it as soon as possible --- especially if we are aborting because we |
| * ran out of memory for the list! |
| * |
| * (Note: any event_cxts of child subtransactions could also be |
| * deleted here, but we have no convenient way to find them, so we |
| * leave it to TopTransactionContext reset to clean them up.) |
| */ |
| if (afterTriggers && afterTriggers->event_cxt) |
| MemoryContextDelete(afterTriggers->event_cxt); |
| |
| 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. (Note: because the |
| * minimum nest level of a subtransaction is 2, we waste the first |
| * couple entries of each array; not worth the notational effort to |
| * avoid it.) |
| */ |
| while (my_level >= afterTriggers->maxtransdepth) |
| { |
| if (afterTriggers->maxtransdepth == 0) |
| { |
| MemoryContext old_cxt; |
| |
| old_cxt = MemoryContextSwitchTo(TopTransactionContext); |
| |
| #define DEFTRIG_INITALLOC 8 |
| afterTriggers->cxt_stack = (MemoryContext *) |
| palloc(DEFTRIG_INITALLOC * sizeof(MemoryContext)); |
| 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->cxt_stack = (MemoryContext *) |
| repalloc(afterTriggers->cxt_stack, |
| new_alloc * sizeof(MemoryContext)); |
| 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. Likewise, we don't make a |
| * per-subtransaction event context until needed. |
| */ |
| afterTriggers->cxt_stack[my_level] = NULL; |
| 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]); |
| /* |
| * It's entirely possible that the subxact created an event_cxt but |
| * there is not anything left in it (because all the triggers were |
| * fired at end-of-statement). If so, we should release the context |
| * to prevent memory leakage in a long sequence of subtransactions. |
| * We can detect whether there's anything of use in the context by |
| * seeing if anything was added to the global events list since |
| * subxact start. (This test doesn't catch every case where the |
| * context is deletable; for instance maybe the only additions were |
| * from a sub-sub-xact. But it handles the common case.) |
| */ |
| if (afterTriggers->cxt_stack[my_level] && |
| afterTriggers->events.tail == afterTriggers->events_stack[my_level].tail) |
| { |
| MemoryContextDelete(afterTriggers->cxt_stack[my_level]); |
| /* avoid double delete if abort later */ |
| afterTriggers->cxt_stack[my_level] = NULL; |
| } |
| } |
| else |
| { |
| /* |
| * Aborting. We don't really need to release the subxact's event_cxt, |
| * since it will go away anyway when CurTransactionContext gets reset, |
| * but doing so early in subxact abort helps free space we might need. |
| * |
| * (Note: any event_cxts of child subtransactions could also be |
| * deleted here, but we have no convenient way to find them, so we |
| * leave it to CurTransactionContext reset to clean them up.) |
| */ |
| if (afterTriggers->cxt_stack[my_level]) |
| { |
| MemoryContextDelete(afterTriggers->cxt_stack[my_level]); |
| /* avoid double delete if repeated aborts */ |
| afterTriggers->cxt_stack[my_level] = NULL; |
| } |
| |
| /* |
| * 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; |
| |
| /* |
| * 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_open(TriggerRelationId, AccessShareLock); |
| |
| foreach(l, stmt->constraints) |
| { |
| RangeVar *constraint = lfirst(l); |
| cqContext cqc; |
| cqContext *pcqCtx; |
| HeapTuple htup; |
| bool found; |
| List *namespaceSearchList; |
| ListCell *namespaceSearchCell; |
| |
| if (constraint->catalogname) |
| { |
| if (strcmp(constraint->catalogname, get_database_name(MyDatabaseId)) != 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("cross-database references are not implemented: \"%s.%s.%s\"", |
| constraint->catalogname, constraint->schemaname, |
| constraint->relname))); |
| } |
| |
| /* |
| * If we're given the schema name with the constraint, look only |
| * in that schema. If given a bare constraint name, use the |
| * search path to find the first matching constraint. |
| */ |
| if (constraint->schemaname) |
| { |
| Oid namespaceId = LookupExplicitNamespace(constraint->schemaname, NSPDBOID_CURRENT); |
| |
| namespaceSearchList = list_make1_oid(namespaceId); |
| } |
| else |
| { |
| namespaceSearchList = fetch_search_path(true); |
| } |
| |
| found = false; |
| foreach(namespaceSearchCell, namespaceSearchList) |
| { |
| Oid searchNamespaceId = lfirst_oid(namespaceSearchCell); |
| |
| /* |
| * Setup to scan pg_trigger by tgconstrname ... |
| */ |
| pcqCtx = caql_beginscan( |
| caql_addrel(cqclr(&cqc), tgrel), |
| cql("SELECT * FROM pg_trigger " |
| " WHERE tgconstrname = :1 ", |
| PointerGetDatum(constraint->relname))); |
| |
| /* |
| * ... and search for the constraint trigger row |
| */ |
| while (HeapTupleIsValid(htup = caql_getnext(pcqCtx))) |
| { |
| Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup); |
| Oid constraintNamespaceId; |
| |
| /* |
| * Foreign key constraints have triggers on both the |
| * parent and child tables. Since these tables may be in |
| * different schemas we must pick the child table because |
| * that table "owns" the constraint. |
| * |
| * Referential triggers on the parent table other than |
| * NOACTION_DEL and NOACTION_UPD are ignored below, so it |
| * is possible to not check them here, but it seems safer |
| * to always check. |
| */ |
| if (pg_trigger->tgfoid == F_RI_FKEY_NOACTION_DEL || |
| pg_trigger->tgfoid == F_RI_FKEY_NOACTION_UPD || |
| 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) |
| constraintNamespaceId = get_rel_namespace(pg_trigger->tgconstrrelid); |
| else |
| constraintNamespaceId = get_rel_namespace(pg_trigger->tgrelid); |
| |
| /* |
| * If this constraint is not in the schema we're currently |
| * searching for, keep looking. |
| */ |
| if (constraintNamespaceId != searchNamespaceId) |
| continue; |
| |
| /* |
| * 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", |
| constraint->relname))); |
| oidlist = lappend_oid(oidlist, HeapTupleGetOid(htup)); |
| } |
| found = true; |
| } |
| |
| caql_endscan(pcqCtx); |
| |
| /* |
| * Once we've found a matching constraint we do not search |
| * later parts of the search path. |
| */ |
| if (found) |
| break; |
| |
| } |
| |
| list_free(namespaceSearchList); |
| |
| /* |
| * Not found ? |
| */ |
| if (!found) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("constraint \"%s\" does not exist", |
| constraint->relname))); |
| } |
| 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; |
| |
| while (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, NULL, |
| !IsSubTransaction()); |
| } |
| } |
| |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| dispatch_statement_node((Node *) stmt, NULL, NULL, NULL); |
| } |
| } |
| |
| /* ---------- |
| * AfterTriggerPendingOnRel() |
| * Test to see if there are any pending after-trigger events for rel. |
| * |
| * This is used by TRUNCATE, CLUSTER, ALTER TABLE, etc to detect whether |
| * it is unsafe to perform major surgery on a relation. Note that only |
| * local pending events are examined. We assume that having exclusive lock |
| * on a rel guarantees there are no unserviced events in other backends --- |
| * but having a lock does not prevent there being such events in our own. |
| * |
| * In some scenarios it'd be reasonable to remove pending events (more |
| * specifically, mark them DONE by the current subxact) but without a lot |
| * of knowledge of the trigger semantics we can't do this in general. |
| * ---------- |
| */ |
| bool |
| AfterTriggerPendingOnRel(Oid relid) |
| { |
| AfterTriggerEvent event; |
| int depth; |
| |
| /* No-op if we aren't in a transaction. (Shouldn't happen?) */ |
| if (afterTriggers == NULL) |
| return false; |
| |
| /* Scan queued events */ |
| for (event = afterTriggers->events.head; |
| event != NULL; |
| event = event->ate_next) |
| { |
| /* |
| * We can ignore completed events. (Even if a DONE flag is rolled |
| * back by subxact abort, it's OK because the effects of the TRUNCATE |
| * or whatever must get rolled back too.) |
| */ |
| if (event->ate_event & AFTER_TRIGGER_DONE) |
| continue; |
| |
| if (event->ate_relid == relid) |
| return true; |
| } |
| |
| /* |
| * Also scan events queued by incomplete queries. This could only matter |
| * if TRUNCATE/etc is executed by a function or trigger within an updating |
| * query on the same relation, which is pretty perverse, but let's check. |
| */ |
| for (depth = 0; depth <= afterTriggers->query_depth; depth++) |
| { |
| for (event = afterTriggers->query_stack[depth].head; |
| event != NULL; |
| event = event->ate_next) |
| { |
| if (event->ate_event & AFTER_TRIGGER_DONE) |
| continue; |
| |
| if (event->ate_relid == relid) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| /* ---------- |
| * 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; |
| int my_level = GetCurrentTransactionNestLevel(); |
| MemoryContext *cxtptr; |
| 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 this is an UPDATE of a PK table or FK table that does not change |
| * the PK or FK respectively, we can skip queuing the event: there is |
| * no need to fire the trigger. |
| */ |
| if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE) |
| { |
| switch (RI_FKey_trigger_type(trigger->tgfoid)) |
| { |
| case RI_TRIGGER_PK: |
| /* Update on PK table */ |
| if (RI_FKey_keyequal_upd_pk(trigger, rel, oldtup, newtup)) |
| { |
| /* key unchanged, so skip queuing this event */ |
| continue; |
| } |
| break; |
| |
| case RI_TRIGGER_FK: |
| |
| /* |
| * Update on FK table |
| * |
| * There is one exception when updating FK tables: if the |
| * updated row was inserted by our own transaction and the |
| * FK is deferred, we still need to fire the trigger. This |
| * is because our UPDATE will invalidate the INSERT so the |
| * end-of-transaction INSERT RI trigger will not do |
| * anything, so we have to do the check for the UPDATE |
| * anyway. |
| */ |
| if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(oldtup->t_data)) && |
| RI_FKey_keyequal_upd_fk(trigger, rel, oldtup, newtup)) |
| { |
| continue; |
| } |
| break; |
| |
| case RI_TRIGGER_NONE: |
| /* Not an FK trigger */ |
| break; |
| } |
| } |
| |
| /* |
| * If we don't yet have an event context for the current (sub)xact, |
| * create one. Make it a child of CurTransactionContext to ensure it |
| * will go away if the subtransaction aborts. |
| */ |
| if (my_level > 1) /* subtransaction? */ |
| { |
| Assert(my_level < afterTriggers->maxtransdepth); |
| cxtptr = &afterTriggers->cxt_stack[my_level]; |
| } |
| else |
| cxtptr = &afterTriggers->event_cxt; |
| if (*cxtptr == NULL) |
| *cxtptr = AllocSetContextCreate(CurTransactionContext, |
| "AfterTriggerEvents", |
| ALLOCSET_DEFAULT_MINSIZE, |
| ALLOCSET_DEFAULT_INITSIZE, |
| ALLOCSET_DEFAULT_MAXSIZE); |
| |
| /* |
| * Create a new event. |
| */ |
| new_event = (AfterTriggerEvent) |
| MemoryContextAlloc(*cxtptr, 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); |
| } |
| } |