diff options
author | Jan Wieck <JanWieck@Yahoo.com> | 1999-09-29 16:06:40 +0000 |
---|---|---|
committer | Jan Wieck <JanWieck@Yahoo.com> | 1999-09-29 16:06:40 +0000 |
commit | 1547ee017c897725221d0752af4477121524c05b (patch) | |
tree | 775616278865cba3e01e521812d045489e40fd9b /src/backend/commands/trigger.c | |
parent | d810338d29bff178101e72c810c9dcfa3223c6c0 (diff) | |
download | postgresql-1547ee017c897725221d0752af4477121524c05b.tar.gz postgresql-1547ee017c897725221d0752af4477121524c05b.zip |
This is part #1 for of the DEFERRED CONSTRAINT TRIGGER support.
Implements the CREATE CONSTRAINT TRIGGER and SET CONSTRAINTS commands.
TODO:
Generic builtin trigger procedures
Automatic execution of appropriate CREATE CONSTRAINT... at CREATE TABLE
Support of new trigger type in pg_dump
Swapping of huge # of events to disk
Jan
Diffstat (limited to 'src/backend/commands/trigger.c')
-rw-r--r-- | src/backend/commands/trigger.c | 1139 |
1 files changed, 1077 insertions, 62 deletions
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index aa7a0b56c1d..ce7d6f5a2d6 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -54,6 +54,9 @@ CreateTrigger(CreateTrigStmt *stmt) Oid fargtypes[8]; int found = 0; int i; + char constrtrigname[NAMEDATALEN]; + char *constrname = ""; + Oid constrrelid = 0; if (!allowSystemTableMods && IsSystemRelationName(stmt->relname)) elog(ERROR, "CreateTrigger: can't create trigger for system relation %s", stmt->relname); @@ -63,6 +66,30 @@ CreateTrigger(CreateTrigStmt *stmt) elog(ERROR, "%s: %s", stmt->relname, aclcheck_error_strings[ACLCHECK_NOT_OWNER]); #endif + /* ---------- + * If trigger is a constraint, user trigger name as constraint + * name and build a unique trigger name instead. + * ---------- + */ + if (stmt->isconstraint) + { + constrname = stmt->trigname; + stmt->trigname = constrtrigname; + sprintf(constrtrigname, "RI_ConstraintTrigger_%d", newoid()); + + if (strcmp(stmt->constrrelname, "") == 0) + constrrelid = 0; + else + { + rel = heap_openr(stmt->constrrelname, NoLock); + if (rel == NULL) + elog(ERROR, "table \"%s\" does not exist", + stmt->constrrelname); + constrrelid = rel->rd_id; + heap_close(rel, NoLock); + } + } + rel = heap_openr(stmt->relname, AccessExclusiveLock); TRIGGER_CLEAR_TYPE(tgtype); @@ -148,6 +175,14 @@ CreateTrigger(CreateTrigStmt *stmt) values[Anum_pg_trigger_tgname - 1] = NameGetDatum(namein(stmt->trigname)); values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(tuple->t_data->t_oid); values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype); + + values[Anum_pg_trigger_tgenabled - 1] = true; + values[Anum_pg_trigger_tgisconstraint - 1] = stmt->isconstraint; + values[Anum_pg_trigger_tgconstrname - 1] = PointerGetDatum(constrname);; + values[Anum_pg_trigger_tgconstrrelid - 1] = constrrelid; + values[Anum_pg_trigger_tgdeferrable - 1] = stmt->deferrable; + values[Anum_pg_trigger_tginitdeferred - 1] = stmt->initdeferred; + if (stmt->args) { List *le; @@ -311,6 +346,7 @@ RelationRemoveTriggers(Relation rel) HeapScanDesc tgscan; ScanKeyData key; HeapTuple tup; + Form_pg_trigger pg_trigger; tgrel = heap_openr(TriggerRelationName, RowExclusiveLock); ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid, @@ -322,6 +358,36 @@ RelationRemoveTriggers(Relation rel) heap_delete(tgrel, &tup->t_self, NULL); heap_endscan(tgscan); + + + /* ---------- + * Also drop all constraint triggers referencing this relation + * ---------- + */ + ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgconstrrelid, + F_OIDEQ, RelationGetRelid(rel)); + + tgscan = heap_beginscan(tgrel, 0, SnapshotNow, 1, &key); + while (HeapTupleIsValid(tup = heap_getnext(tgscan, 0))) + { + Relation refrel; + DropTrigStmt stmt; + + pg_trigger = (Form_pg_trigger) GETSTRUCT(tup); + refrel = heap_open(pg_trigger->tgrelid, NoLock); + + stmt.relname = nameout(&(refrel->rd_rel->relname)); + stmt.trigname = nameout(&(pg_trigger->tgname)); + + DropTrigger(&stmt); + + pfree(stmt.relname); + pfree(stmt.trigname); + + heap_close(refrel, NoLock); + } + heap_endscan(tgscan); + heap_close(tgrel, RowExclusiveLock); } @@ -379,10 +445,15 @@ RelationBuildTriggers(Relation relation) triggers = (Trigger *) repalloc(triggers, (found + 1) * sizeof(Trigger)); build = &(triggers[found]); + build->tgoid = tuple.t_data->t_oid; build->tgname = nameout(&(pg_trigger->tgname)); build->tgfoid = pg_trigger->tgfoid; build->tgfunc.fn_addr = NULL; build->tgtype = pg_trigger->tgtype; + build->tgenabled = pg_trigger->tgenabled; + build->tgisconstraint = pg_trigger->tgisconstraint; + build->tgdeferrable = pg_trigger->tgdeferrable; + build->tginitdeferred = pg_trigger->tginitdeferred; build->tgnargs = pg_trigger->tgnargs; memcpy(build->tgattr, &(pg_trigger->tgattr), 8 * sizeof(int16)); val = (struct varlena *) fastgetattr(&tuple, @@ -592,6 +663,8 @@ ExecBRInsertTriggers(Relation rel, HeapTuple trigtuple) SaveTriggerData->tg_newtuple = NULL; for (i = 0; i < ntrigs; i++) { + if (!trigger[i]->tgenabled) + continue; CurrentTriggerData = SaveTriggerData; CurrentTriggerData->tg_trigtuple = oldtuple = newtuple; CurrentTriggerData->tg_trigger = trigger[i]; @@ -609,24 +682,7 @@ ExecBRInsertTriggers(Relation rel, HeapTuple trigtuple) void ExecARInsertTriggers(Relation rel, HeapTuple trigtuple) { - TriggerData *SaveTriggerData; - int ntrigs = rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT]; - Trigger **trigger = rel->trigdesc->tg_after_row[TRIGGER_EVENT_INSERT]; - int i; - - SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData)); - SaveTriggerData->tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW; - SaveTriggerData->tg_relation = rel; - SaveTriggerData->tg_newtuple = NULL; - for (i = 0; i < ntrigs; i++) - { - CurrentTriggerData = SaveTriggerData; - CurrentTriggerData->tg_trigtuple = trigtuple; - CurrentTriggerData->tg_trigger = trigger[i]; - ExecCallTriggerFunc(trigger[i]); - } - CurrentTriggerData = NULL; - pfree(SaveTriggerData); + DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_INSERT, NULL, trigtuple); return; } @@ -652,6 +708,8 @@ ExecBRDeleteTriggers(EState *estate, ItemPointer tupleid) SaveTriggerData->tg_newtuple = NULL; for (i = 0; i < ntrigs; i++) { + if (!trigger[i]->tgenabled) + continue; CurrentTriggerData = SaveTriggerData; CurrentTriggerData->tg_trigtuple = trigtuple; CurrentTriggerData->tg_trigger = trigger[i]; @@ -672,29 +730,9 @@ void ExecARDeleteTriggers(EState *estate, ItemPointer tupleid) { Relation rel = estate->es_result_relation_info->ri_RelationDesc; - TriggerData *SaveTriggerData; - int ntrigs = rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE]; - Trigger **trigger = rel->trigdesc->tg_after_row[TRIGGER_EVENT_DELETE]; - HeapTuple trigtuple; - int i; - - trigtuple = GetTupleForTrigger(estate, tupleid, NULL); - Assert(trigtuple != NULL); + HeapTuple trigtuple = GetTupleForTrigger(estate, tupleid, NULL); - SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData)); - SaveTriggerData->tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW; - SaveTriggerData->tg_relation = rel; - SaveTriggerData->tg_newtuple = NULL; - for (i = 0; i < ntrigs; i++) - { - CurrentTriggerData = SaveTriggerData; - CurrentTriggerData->tg_trigtuple = trigtuple; - CurrentTriggerData->tg_trigger = trigger[i]; - ExecCallTriggerFunc(trigger[i]); - } - CurrentTriggerData = NULL; - pfree(SaveTriggerData); - pfree(trigtuple); + DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_DELETE, trigtuple, NULL); return; } @@ -727,6 +765,8 @@ ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple) SaveTriggerData->tg_relation = rel; for (i = 0; i < ntrigs; i++) { + if (!trigger[i]->tgenabled) + continue; CurrentTriggerData = SaveTriggerData; CurrentTriggerData->tg_trigtuple = trigtuple; CurrentTriggerData->tg_newtuple = oldtuple = newtuple; @@ -747,29 +787,9 @@ void ExecARUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple newtuple) { Relation rel = estate->es_result_relation_info->ri_RelationDesc; - TriggerData *SaveTriggerData; - int ntrigs = rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE]; - Trigger **trigger = rel->trigdesc->tg_after_row[TRIGGER_EVENT_UPDATE]; - HeapTuple trigtuple; - int i; - - trigtuple = GetTupleForTrigger(estate, tupleid, NULL); - Assert(trigtuple != NULL); + HeapTuple trigtuple = GetTupleForTrigger(estate, tupleid, NULL); - SaveTriggerData = (TriggerData *) palloc(sizeof(TriggerData)); - SaveTriggerData->tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW; - SaveTriggerData->tg_relation = rel; - for (i = 0; i < ntrigs; i++) - { - CurrentTriggerData = SaveTriggerData; - CurrentTriggerData->tg_trigtuple = trigtuple; - CurrentTriggerData->tg_newtuple = newtuple; - CurrentTriggerData->tg_trigger = trigger[i]; - ExecCallTriggerFunc(trigger[i]); - } - CurrentTriggerData = NULL; - pfree(SaveTriggerData); - pfree(trigtuple); + DeferredTriggerSaveEvent(rel, TRIGGER_EVENT_UPDATE, trigtuple, newtuple); return; } @@ -858,3 +878,998 @@ ltrmark:; return result; } + + +/* ---------- + * Deferred trigger stuff + * ---------- + */ + + +/* ---------- + * Internal data to the deferred trigger mechanism is held + * during entire session in a global memor created at startup and + * over statements/commands in a separate global memory which + * is created at transaction start and destroyed at transaction + * end. + * ---------- + */ +static GlobalMemory deftrig_gcxt = NULL; +static GlobalMemory deftrig_cxt = NULL; + +/* ---------- + * Global data that tells which triggers are actually in + * state IMMEDIATE or DEFERRED. + * ---------- + */ +static bool deftrig_dfl_all_isset = false; +static bool deftrig_dfl_all_isdeferred = false; +static List *deftrig_dfl_trigstates = NIL; + +static bool deftrig_all_isset; +static bool deftrig_all_isdeferred; +static List *deftrig_trigstates; + +/* ---------- + * The list of events during the entire transaction. + * + * XXX This must finally be held in a file because of the huge + * number of events that could occur in the real world. + * ---------- + */ +static int deftrig_n_events; +static List *deftrig_events; + + +/* ---------- + * deferredTriggerCheckState() + * + * Returns true if the trigger identified by tgoid is actually + * in state DEFERRED. + * ---------- + */ +static bool +deferredTriggerCheckState(Oid tgoid, int32 itemstate) +{ + MemoryContext oldcxt; + List *sl; + DeferredTriggerStatus trigstate; + + /* ---------- + * Not deferrable triggers (i.e. normal AFTER ROW triggers + * and constraints declared NOT DEFERRABLE, the state is + * allways false. + * ---------- + */ + if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0) + return false; + + /* ---------- + * Lookup if we know an individual state for this trigger + * ---------- + */ + foreach (sl, deftrig_trigstates) + { + trigstate = (DeferredTriggerStatus) lfirst(sl); + if (trigstate->dts_tgoid == tgoid) + return trigstate->dts_tgisdeferred; + } + + /* ---------- + * No individual state known - so if the user issued a + * SET CONSTRAINT ALL ..., we return that instead of the + * triggers default state. + * ---------- + */ + if (deftrig_all_isset) + return deftrig_all_isdeferred; + + /* ---------- + * No ALL state known either, remember the default state + * as the current and return that. + * ---------- + */ + oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt); + + trigstate = (DeferredTriggerStatus) + palloc(sizeof(DeferredTriggerStatusData)); + trigstate->dts_tgoid = tgoid; + trigstate->dts_tgisdeferred = + ((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0); + deftrig_trigstates = lappend(deftrig_trigstates, trigstate); + + MemoryContextSwitchTo(oldcxt); + + return trigstate->dts_tgisdeferred; +} + + +/* ---------- + * deferredTriggerAddEvent() + * + * Add a new trigger event to the queue. + * ---------- + */ +static void +deferredTriggerAddEvent(DeferredTriggerEvent event) +{ + deftrig_events = lappend(deftrig_events, event); + deftrig_n_events++; + + return; +} + + +/* ---------- + * deferredTriggerGetPreviousEvent() + * + * Backward scan the eventlist to find the event a given OLD tuple + * resulted from in the same transaction. + * ---------- + */ +static DeferredTriggerEvent +deferredTriggerGetPreviousEvent(Oid relid, ItemPointer ctid) +{ + DeferredTriggerEvent previous; + int n; + + for (n = deftrig_n_events - 1; n >= 0; n--) + { + previous = (DeferredTriggerEvent) nth(n, deftrig_events); + + if (previous->dte_relid != relid) + continue; + if (previous->dte_event & TRIGGER_DEFERRED_CANCELED) + continue; + + if (ItemPointerGetBlockNumber(ctid) == + ItemPointerGetBlockNumber(&(previous->dte_newctid)) && + ItemPointerGetOffsetNumber(ctid) == + ItemPointerGetOffsetNumber(&(previous->dte_newctid))) + return previous; + } + + elog(ERROR, + "deferredTriggerGetPreviousEvent(): event for tuple %s not found", + tidout(ctid)); + return NULL; +} + + +/* ---------- + * deferredTriggerCancelEvent() + * + * Mark an event in the eventlist as cancelled because it isn't + * required anymore (replaced by anoter event). + * ---------- + */ +static void +deferredTriggerCancelEvent(DeferredTriggerEvent event) +{ + event->dte_event |= TRIGGER_DEFERRED_CANCELED; +} + + +/* ---------- + * deferredTriggerExecute() + * + * Fetch the required tuples back from the heap and fire one + * single trigger function. + * ---------- + */ +static void +deferredTriggerExecute(DeferredTriggerEvent event, int itemno) +{ + Relation rel; + TriggerData SaveTriggerData; + HeapTupleData oldtuple; + HeapTupleData newtuple; + HeapTuple rettuple; + Buffer oldbuffer; + Buffer newbuffer; + + /* ---------- + * Open the heap and fetch the required OLD and NEW tuples. + * ---------- + */ + rel = heap_open(event->dte_relid, NoLock); + + if (ItemPointerIsValid(&(event->dte_oldctid))) + { + ItemPointerCopy(&(event->dte_oldctid), &(oldtuple.t_self)); + heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer); + if (!oldtuple.t_data) + elog(ERROR, "deferredTriggerExecute(): failed to fetch old tuple"); + } + + if (ItemPointerIsValid(&(event->dte_newctid))) + { + ItemPointerCopy(&(event->dte_newctid), &(newtuple.t_self)); + heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer); + if (!newtuple.t_data) + elog(ERROR, "deferredTriggerExecute(): failed to fetch new tuple"); + } + + /* ---------- + * Setup the trigger information + * ---------- + */ + SaveTriggerData.tg_event = event->dte_event | TRIGGER_EVENT_ROW; + SaveTriggerData.tg_relation = rel; + + switch (event->dte_event) + { + case TRIGGER_EVENT_INSERT: + SaveTriggerData.tg_trigtuple = &newtuple; + SaveTriggerData.tg_newtuple = NULL; + SaveTriggerData.tg_trigger = + rel->trigdesc->tg_after_row[TRIGGER_EVENT_INSERT][itemno]; + break; + + case TRIGGER_EVENT_UPDATE: + SaveTriggerData.tg_trigtuple = &oldtuple; + SaveTriggerData.tg_newtuple = &newtuple; + SaveTriggerData.tg_trigger = + rel->trigdesc->tg_after_row[TRIGGER_EVENT_UPDATE][itemno]; + break; + + case TRIGGER_EVENT_DELETE: + SaveTriggerData.tg_trigtuple = &oldtuple; + SaveTriggerData.tg_newtuple = NULL; + SaveTriggerData.tg_trigger = + rel->trigdesc->tg_after_row[TRIGGER_EVENT_DELETE][itemno]; + break; + + default: + } + + /* ---------- + * Call the trigger and throw away an eventually returned + * updated tuple. + * ---------- + */ + CurrentTriggerData = &SaveTriggerData; + rettuple = ExecCallTriggerFunc(SaveTriggerData.tg_trigger); + CurrentTriggerData = NULL; + if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple) + pfree(rettuple); + + /* ---------- + * Release buffers and close the relation + * ---------- + */ + if (ItemPointerIsValid(&(event->dte_oldctid))) + ReleaseBuffer(oldbuffer); + if (ItemPointerIsValid(&(event->dte_newctid))) + ReleaseBuffer(newbuffer); + + heap_close(rel, NoLock); + + return; +} + + +/* ---------- + * deferredTriggerInvokeEvents() + * + * Scan the event queue for not yet invoked triggers. Check if they + * should be invoked now and do so. + * ---------- + */ +static void +deferredTriggerInvokeEvents(bool immediate_only) +{ + List *el; + DeferredTriggerEvent event; + int still_deferred_ones; + int eventno = -1; + int i; + + /* ---------- + * For now we process all events - to speedup transaction blocks + * we need to remember the actual end of the queue at EndQuery + * and process only events that are newer. On state changes we + * simply reset the position to the beginning of the queue and + * process all events once with the new states when the + * SET CONSTRAINTS ... command finishes and calls EndQuery. + * ---------- + */ + foreach (el, deftrig_events) + { + eventno++; + + /* ---------- + * Get the event and check if it is completely done. + * ---------- + */ + event = (DeferredTriggerEvent) lfirst(el); + if (event->dte_event & (TRIGGER_DEFERRED_DONE | + TRIGGER_DEFERRED_CANCELED)) + continue; + + /* ---------- + * Check each trigger item in the event. + * ---------- + */ + still_deferred_ones = false; + for (i = 0; i < event->dte_n_items; i++) + { + if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE) + continue; + + /* ---------- + * This trigger item hasn't been called yet. Check if + * we should call it now. + * ---------- + */ + if (immediate_only && deferredTriggerCheckState( + event->dte_item[i].dti_tgoid, + event->dte_item[i].dti_state)) + { + still_deferred_ones = true; + continue; + } + + /* ---------- + * So let's fire it... + * ---------- + */ + deferredTriggerExecute(event, i); + event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE; + } + + /* ---------- + * Remember in the event itself if all trigger items are + * done. + * ---------- + */ + if (!still_deferred_ones) + event->dte_event |= TRIGGER_DEFERRED_DONE; + } +} + + +/* ---------- + * DeferredTriggerInit() + * + * Initialize the deferred trigger mechanism. This is called during + * backend startup and is guaranteed to be before the first of all + * transactions. + * ---------- + */ +int +DeferredTriggerInit(void) +{ + deftrig_gcxt = CreateGlobalMemory("DeferredTriggerSession"); + return 0; +} + + +/* ---------- + * DeferredTriggerBeginXact() + * + * Called at transaction start (either BEGIN or implicit for single + * statement outside of transaction block). + * ---------- + */ +void +DeferredTriggerBeginXact(void) +{ + MemoryContext oldcxt; + List *l; + DeferredTriggerStatus dflstat; + DeferredTriggerStatus stat; + + if (deftrig_cxt != NULL) + elog(FATAL, + "DeferredTriggerBeginXact() called while inside transaction"); + + /* ---------- + * Create the per transaction memory context and copy all states + * from the per session context to here. + * ---------- + */ + deftrig_cxt = CreateGlobalMemory("DeferredTriggerXact"); + oldcxt = MemoryContextSwitchTo((MemoryContext)deftrig_cxt); + + deftrig_all_isset = deftrig_dfl_all_isset; + deftrig_all_isdeferred = deftrig_dfl_all_isdeferred; + + deftrig_trigstates = NIL; + foreach (l, deftrig_dfl_trigstates) + { + dflstat = (DeferredTriggerStatus) lfirst(l); + stat = (DeferredTriggerStatus) + palloc(sizeof(DeferredTriggerStatusData)); + + stat->dts_tgoid = dflstat->dts_tgoid; + stat->dts_tgisdeferred = dflstat->dts_tgisdeferred; + + deftrig_trigstates = lappend(deftrig_trigstates, stat); + } + + MemoryContextSwitchTo(oldcxt); + + deftrig_n_events = 0; + deftrig_events = NIL; +} + + +/* ---------- + * DeferredTriggerEndQuery() + * + * Called after one query sent down by the user has completely been + * processed. At this time we invoke all outstanding IMMEDIATE triggers. + * ---------- + */ +void +DeferredTriggerEndQuery(void) +{ + /* ---------- + * Ignore call if we aren't in a transaction. + * ---------- + */ + if (deftrig_cxt == NULL) + return; + + deferredTriggerInvokeEvents(true); +} + + +/* ---------- + * DeferredTriggerEndXact() + * + * Called just before the current transaction is committed. At this + * time we invoke all DEFERRED triggers and tidy up. + * ---------- + */ +void +DeferredTriggerEndXact(void) +{ + /* ---------- + * Ignore call if we aren't in a transaction. + * ---------- + */ + if (deftrig_cxt == NULL) + return; + + deferredTriggerInvokeEvents(false); + + GlobalMemoryDestroy(deftrig_cxt); + deftrig_cxt = NULL; +} + + +/* ---------- + * DeferredTriggerAbortXact() + * + * The current transaction has entered the abort state. + * All outstanding triggers are canceled so we simply throw + * away anything we know. + * ---------- + */ +void +DeferredTriggerAbortXact(void) +{ + /* ---------- + * Ignore call if we aren't in a transaction. + * ---------- + */ + if (deftrig_cxt == NULL) + return; + + GlobalMemoryDestroy(deftrig_cxt); + deftrig_cxt = NULL; +} + + +/* ---------- + * DeferredTriggerSetState() + * + * Called for the users SET CONSTRAINTS ... utility command. + * ---------- + */ +void +DeferredTriggerSetState(ConstraintsSetStmt *stmt) +{ + Relation tgrel; + Relation irel; + List *l; + List *ls; + List *lnext; + List *loid = NIL; + MemoryContext oldcxt; + bool found; + DeferredTriggerStatus state; + + /* ---------- + * Handle SET CONSTRAINTS ALL ... + * ---------- + */ + if (stmt->constraints == NIL) { + if (!IsTransactionBlock()) + { + /* ---------- + * ... outside of a transaction block + * ---------- + */ + oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_gcxt); + + /* ---------- + * Drop all information about individual trigger states per + * session. + * ---------- + */ + l = deftrig_dfl_trigstates; + while (l != NIL) + { + lnext = lnext(l); + pfree(lfirst(l)); + pfree(l); + l = lnext; + } + deftrig_dfl_trigstates = NIL; + + /* ---------- + * Set the session ALL state to known. + * ---------- + */ + deftrig_dfl_all_isset = true; + deftrig_dfl_all_isdeferred = stmt->deferred; + + MemoryContextSwitchTo(oldcxt); + + return; + } else { + /* ---------- + * ... inside of a transaction block + * ---------- + */ + oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt); + + /* ---------- + * Drop all information about individual trigger states per + * transaction. + * ---------- + */ + l = deftrig_trigstates; + while (l != NIL) + { + lnext = lnext(l); + pfree(lfirst(l)); + pfree(l); + l = lnext; + } + deftrig_trigstates = NIL; + + /* ---------- + * Set the per transaction ALL state to known. + * ---------- + */ + deftrig_all_isset = true; + deftrig_all_isdeferred = stmt->deferred; + + MemoryContextSwitchTo(oldcxt); + + return; + } + } + + /* ---------- + * Handle SET CONSTRAINTS constraint-name [, ...] + * First lookup all trigger Oid's for the constraint names. + * ---------- + */ + tgrel = heap_openr(TriggerRelationName, AccessShareLock); + irel = index_openr(TriggerConstrNameIndex); + + foreach (l, stmt->constraints) + { + ScanKeyData skey; + HeapTupleData tuple; + IndexScanDesc sd; + RetrieveIndexResult indexRes; + Buffer buffer; + Form_pg_trigger pg_trigger; + Oid constr_oid; + + /* ---------- + * Check that only named constraints are set explicitly + * ---------- + */ + if (strcmp((char *)lfirst(l), "") == 0) + elog(ERROR, "unnamed constraints cannot be set explicitly"); + + /* ---------- + * Setup to scan pg_trigger by tgconstrname ... + * ---------- + */ + ScanKeyEntryInitialize(&skey, + (bits16) 0x0, + (AttrNumber) 1, + (RegProcedure) F_NAMEEQ, + PointerGetDatum((char *)lfirst(l))); + + sd = index_beginscan(irel, false, 1, &skey); + + /* ---------- + * ... and search for the constraint trigger row + * ---------- + */ + found = false; + for (;;) + { + indexRes = index_getnext(sd, ForwardScanDirection); + if (!indexRes) + break; + + tuple.t_self = indexRes->heap_iptr; + heap_fetch(tgrel, SnapshotNow, &tuple, &buffer); + pfree(indexRes); + if (!tuple.t_data) + { + ReleaseBuffer(buffer); + continue; + } + + /* ---------- + * If we found some, check that they fit the deferrability + * ---------- + */ + pg_trigger = (Form_pg_trigger) GETSTRUCT(&tuple); + if (stmt->deferred & !pg_trigger->tgdeferrable) + elog(ERROR, "Constraint '%s' is not deferrable", + (char *)lfirst(l)); + + constr_oid = tuple.t_data->t_oid; + loid = lappend(loid, (Node *)constr_oid); + found = true; + + ReleaseBuffer(buffer); + } + + /* ---------- + * Not found ? + * ---------- + */ + if (!found) + elog(ERROR, "Constraint '%s' does not exist", (char *)lfirst(l)); + + index_endscan(sd); + + } + index_close(irel); + heap_close(tgrel, AccessShareLock); + + + if (!IsTransactionBlock()) + { + /* ---------- + * Outside of a transaction block set the trigger + * states of individual triggers on session level. + * ---------- + */ + oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_gcxt); + + foreach (l, loid) + { + found = false; + foreach (ls, deftrig_dfl_trigstates) + { + state = (DeferredTriggerStatus) lfirst(ls); + if (state->dts_tgoid == (Oid) lfirst(l)) + { + state->dts_tgisdeferred = stmt->deferred; + found = true; + break; + } + } + if (!found) + { + state = (DeferredTriggerStatus) + palloc(sizeof(DeferredTriggerStatusData)); + state->dts_tgoid = (Oid) lfirst(l); + state->dts_tgisdeferred = stmt->deferred; + + deftrig_dfl_trigstates = + lappend(deftrig_dfl_trigstates, state); + } + } + + MemoryContextSwitchTo(oldcxt); + + return; + } else { + /* ---------- + * Inside of a transaction block set the trigger + * states of individual triggers on transaction level. + * ---------- + */ + oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt); + + foreach (l, loid) + { + found = false; + foreach (ls, deftrig_trigstates) + { + state = (DeferredTriggerStatus) lfirst(ls); + if (state->dts_tgoid == (Oid) lfirst(l)) + { + state->dts_tgisdeferred = stmt->deferred; + found = true; + break; + } + } + if (!found) + { + state = (DeferredTriggerStatus) + palloc(sizeof(DeferredTriggerStatusData)); + state->dts_tgoid = (Oid) lfirst(l); + state->dts_tgisdeferred = stmt->deferred; + + deftrig_trigstates = + lappend(deftrig_trigstates, state); + } + } + + MemoryContextSwitchTo(oldcxt); + + return; + } +} + + +/* ---------- + * DeferredTriggerSaveEvent() + * + * Called by ExecAR...Triggers() to add the event to the queue. + * ---------- + */ +void +DeferredTriggerSaveEvent(Relation rel, int event, + HeapTuple oldtup, HeapTuple newtup) +{ + MemoryContext oldcxt; + DeferredTriggerEvent new_event; + DeferredTriggerEvent prev_event; + bool prev_done = false; + int new_size; + int i; + int ntriggers; + Trigger **triggers; + ItemPointerData oldctid; + ItemPointerData newctid; + + if (deftrig_cxt == NULL) + elog(ERROR, + "DeferredTriggerSaveEvent() called outside of transaction"); + + /* ---------- + * Check if we're interested in this row at all + * ---------- + */ + if (rel->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] == 0 && + rel->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] == 0 && + rel->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] == 0 && + rel->trigdesc->n_before_row[TRIGGER_EVENT_INSERT] == 0 && + rel->trigdesc->n_before_row[TRIGGER_EVENT_UPDATE] == 0 && + rel->trigdesc->n_before_row[TRIGGER_EVENT_DELETE] == 0) + return; + + /* ---------- + * 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)); + + /* ---------- + * Eventually modify the event and do some general RI violation checks + * ---------- + */ + switch (event) + { + case TRIGGER_EVENT_INSERT: + /* ---------- + * Don't know how to (surely) check if another tuple with + * this meaning (from all FK's point of view) got deleted + * in the same transaction. Thus not handled yet. + * ---------- + */ + break; + + case TRIGGER_EVENT_UPDATE: + /* ---------- + * On UPDATE check if the tuple updated is a result + * of the same transaction. + * ---------- + */ + if (oldtup->t_data->t_xmin != GetCurrentTransactionId()) + break; + + /* ---------- + * Look at the previous event to the same tuple if + * any of it's triggers has already been executed. + * Such a situation would potentially violate RI + * so we abort the transaction. + * ---------- + */ + prev_event = deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid); + if (prev_event->dte_event & TRIGGER_DEFERRED_HAS_BEFORE || + (prev_event->dte_n_items != 0 && + prev_event->dte_event & TRIGGER_DEFERRED_DONE)) + prev_done = true; + else + for (i = 0; i < prev_event->dte_n_items; i++) + { + if (prev_event->dte_item[i].dti_state & + TRIGGER_DEFERRED_DONE) + { + prev_done = true; + break; + } + } + + if (prev_done) + { + elog(NOTICE, "UPDATE of row inserted/updated in same " + "transaction violates"); + elog(NOTICE, "referential integrity semantics. Other " + "triggers or IMMEDIATE "); + elog(ERROR, " constraints have already been executed."); + } + + /* ---------- + * Anything's fine so far - i.e. none of the previous + * events triggers has been executed up to now. Let's + * the REAL event that happened so far. + * ---------- + */ + switch (prev_event->dte_event & TRIGGER_EVENT_OPMASK) + { + case TRIGGER_EVENT_INSERT: + /* ---------- + * The previous operation was an insert. + * So the REAL new event is an INSERT of + * the new tuple. + * ---------- + */ + event = TRIGGER_EVENT_INSERT; + ItemPointerSetInvalid(&oldctid); + deferredTriggerCancelEvent(prev_event); + break; + + case TRIGGER_EVENT_UPDATE: + /* ---------- + * The previous operation was an UPDATE. + * So the REAL new event is still an UPDATE + * but from the original tuple to this new one. + * ---------- + */ + event = TRIGGER_EVENT_UPDATE; + ItemPointerCopy(&(prev_event->dte_oldctid), &oldctid); + deferredTriggerCancelEvent(prev_event); + break; + } + + break; + + case TRIGGER_EVENT_DELETE: + /* ---------- + * On DELETE check if the tuple updated is a result + * of the same transaction. + * ---------- + */ + if (oldtup->t_data->t_xmin != GetCurrentTransactionId()) + break; + + /* ---------- + * Look at the previous event to the same tuple if + * any of it's triggers has already been executed. + * Such a situation would potentially violate RI + * so we abort the transaction. + * ---------- + */ + prev_event = deferredTriggerGetPreviousEvent(rel->rd_id, &oldctid); + if (prev_event->dte_event & TRIGGER_DEFERRED_HAS_BEFORE || + (prev_event->dte_n_items != 0 && + prev_event->dte_event & TRIGGER_DEFERRED_DONE)) + prev_done = true; + else + for (i = 0; i < prev_event->dte_n_items; i++) + { + if (prev_event->dte_item[i].dti_state & + TRIGGER_DEFERRED_DONE) + { + prev_done = true; + break; + } + } + + if (prev_done) + { + elog(NOTICE, "DELETE of row inserted/updated in same " + "transaction violates"); + elog(NOTICE, "referential integrity semantics. Other " + "triggers or IMMEDIATE "); + elog(ERROR, " constraints have already been executed."); + } + + /* ---------- + * Anything's fine so far - i.e. none of the previous + * events triggers has been executed up to now. Let's + * the REAL event that happened so far. + * ---------- + */ + switch (prev_event->dte_event & TRIGGER_EVENT_OPMASK) + { + case TRIGGER_EVENT_INSERT: + /* ---------- + * The previous operation was an insert. + * So the REAL new event is nothing. + * ---------- + */ + deferredTriggerCancelEvent(prev_event); + return; + + case TRIGGER_EVENT_UPDATE: + /* ---------- + * The previous operation was an UPDATE. + * So the REAL new event is a DELETE + * but from the original tuple. + * ---------- + */ + event = TRIGGER_EVENT_DELETE; + ItemPointerCopy(&(prev_event->dte_oldctid), &oldctid); + deferredTriggerCancelEvent(prev_event); + break; + } + + break; + } + + /* ---------- + * Create a new event and save it. + * ---------- + */ + oldcxt = MemoryContextSwitchTo((MemoryContext) deftrig_cxt); + + ntriggers = rel->trigdesc->n_after_row[event]; + triggers = rel->trigdesc->tg_after_row[event]; + + new_size = sizeof(DeferredTriggerEventData) + + ntriggers * sizeof(DeferredTriggerEventItem); + + new_event = (DeferredTriggerEvent) palloc(new_size); + new_event->dte_event = event; + new_event->dte_relid = rel->rd_id; + ItemPointerCopy(&oldctid, &(new_event->dte_oldctid)); + ItemPointerCopy(&newctid, &(new_event->dte_newctid)); + new_event->dte_n_items = ntriggers; + new_event->dte_item[ntriggers].dti_state = new_size; + for (i = 0; i < ntriggers; i++) + { + new_event->dte_item[i].dti_tgoid = triggers[i]->tgoid; + new_event->dte_item[i].dti_state = + ((triggers[i]->tgdeferrable) ? + TRIGGER_DEFERRED_DEFERRABLE : 0) | + ((triggers[i]->tginitdeferred) ? + TRIGGER_DEFERRED_INITDEFERRED : 0) | + ((rel->trigdesc->n_before_row[event] > 0) ? + TRIGGER_DEFERRED_HAS_BEFORE : 0); + } + + deferredTriggerAddEvent(new_event); + + MemoryContextSwitchTo(oldcxt); + + return; +} + + |