From 1547ee017c897725221d0752af4477121524c05b Mon Sep 17 00:00:00 2001 From: Jan Wieck Date: Wed, 29 Sep 1999 16:06:40 +0000 Subject: [PATCH] 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 --- src/backend/access/transam/xact.c | 24 +- src/backend/catalog/heap.c | 5 +- src/backend/catalog/indexing.c | 6 +- src/backend/commands/trigger.c | 1139 ++++++++++++++++++++++-- src/backend/executor/execMain.c | 11 +- src/backend/parser/gram.y | 159 +++- src/backend/parser/keywords.c | 9 +- src/backend/tcop/postgres.c | 18 +- src/backend/tcop/utility.c | 9 +- src/include/catalog/indexing.h | 36 +- src/include/catalog/pg_attribute.h | 15 +- src/include/catalog/pg_class.h | 4 +- src/include/catalog/pg_trigger.h | 21 +- src/include/commands/trigger.h | 49 + src/include/nodes/nodes.h | 3 +- src/include/nodes/parsenodes.h | 22 +- src/include/utils/rel.h | 7 +- src/include/utils/tqual.h | 24 +- src/test/regress/expected/triggers.out | 95 -- src/test/regress/sql/triggers.sql | 62 +- 20 files changed, 1473 insertions(+), 245 deletions(-) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index dc5bbcd32e..f468e2916b 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.54 1999/09/28 11:41:03 vadim Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.55 1999/09/29 16:05:55 wieck Exp $ * * NOTES * Transaction aborts can now occur two ways: @@ -149,6 +149,7 @@ #include "commands/async.h" #include "commands/sequence.h" #include "commands/vacuum.h" +#include "commands/trigger.h" #include "libpq/be-fsstubs.h" #include "storage/proc.h" #include "storage/sinval.h" @@ -865,6 +866,12 @@ StartTransaction() */ InitNoNameRelList(); + /* ---------------- + * Tell the trigger manager to we're starting a transaction + * ---------------- + */ + DeferredTriggerBeginXact(); + /* ---------------- * done with start processing, set current transaction * state to "in progress" @@ -904,6 +911,14 @@ CommitTransaction() if (s->state != TRANS_INPROGRESS) elog(NOTICE, "CommitTransaction and not in in-progress state "); + /* ---------------- + * Tell the trigger manager that this transaction is about to be + * committed. He'll invoke all trigger deferred until XACT before + * we really start on committing the transaction. + * ---------------- + */ + DeferredTriggerEndXact(); + /* ---------------- * set the current transaction state information * appropriately during the abort processing @@ -992,6 +1007,13 @@ AbortTransaction() if (s->state != TRANS_INPROGRESS) elog(NOTICE, "AbortTransaction and not in in-progress state "); + /* ---------------- + * Tell the trigger manager that this transaction is about to be + * aborted. + * ---------------- + */ + DeferredTriggerAbortXact(); + /* ---------------- * set the current transaction state information * appropriately during the abort processing diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index f1051cb784..5ad0f7c413 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.98 1999/09/24 00:24:11 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.99 1999/09/29 16:05:56 wieck Exp $ * * * INTERFACE ROUTINES @@ -1468,8 +1468,7 @@ heap_destroy_with_catalog(char *relname) RelationRemoveRules(rid); /* triggers */ - if (rel->rd_rel->reltriggers > 0) - RelationRemoveTriggers(rel); + RelationRemoveTriggers(rel); /* ---------------- * delete attribute tuples diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c index 75799e5557..3ac2ecc4d6 100644 --- a/src/backend/catalog/indexing.c +++ b/src/backend/catalog/indexing.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/indexing.c,v 1.45 1999/09/18 19:06:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/indexing.c,v 1.46 1999/09/29 16:05:56 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -52,7 +52,9 @@ char *Name_pg_attrdef_indices[Num_pg_attrdef_indices] = {AttrDefaultIndex}; char *Name_pg_relcheck_indices[Num_pg_relcheck_indices] = {RelCheckIndex}; -char *Name_pg_trigger_indices[Num_pg_trigger_indices] = {TriggerRelidIndex}; +char *Name_pg_trigger_indices[Num_pg_trigger_indices] = {TriggerRelidIndex, + TriggerConstrNameIndex, + TriggerConstrRelidIndex}; static HeapTuple CatalogIndexFetchTuple(Relation heapRelation, diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index aa7a0b56c1..ce7d6f5a2d 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; +} + + diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index f07f8777a2..0943e4085b 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.95 1999/09/24 00:24:23 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.96 1999/09/29 16:06:02 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -1190,8 +1190,7 @@ ExecAppend(TupleTableSlot *slot, estate->es_lastoid = newId; /* AFTER ROW INSERT Triggers */ - if (resultRelationDesc->trigdesc && - resultRelationDesc->trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0) + if (resultRelationDesc->trigdesc) ExecARInsertTriggers(resultRelationDesc, tuple); } @@ -1277,8 +1276,7 @@ ldelete:; */ /* AFTER ROW DELETE Triggers */ - if (resultRelationDesc->trigdesc && - resultRelationDesc->trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0) + if (resultRelationDesc->trigdesc) ExecARDeleteTriggers(estate, tupleid); } @@ -1420,8 +1418,7 @@ lreplace:; ExecInsertIndexTuples(slot, &(tuple->t_self), estate, true); /* AFTER ROW UPDATE Triggers */ - if (resultRelationDesc->trigdesc && - resultRelationDesc->trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0) + if (resultRelationDesc->trigdesc) ExecARUpdateTriggers(estate, tupleid, tuple); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c4355de20b..b6583197be 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.103 1999/09/28 14:49:36 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.104 1999/09/29 16:06:06 wieck Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -132,7 +132,8 @@ Oid param_type(int t); /* used in parse_expr.c */ CreatedbStmt, DestroydbStmt, VacuumStmt, CursorStmt, SubSelect, UpdateStmt, InsertStmt, select_clause, SelectStmt, NotifyStmt, DeleteStmt, ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt, - CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt + CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt, + ConstraintsSetStmt, %type opt_database1, opt_database2, location, encoding @@ -146,6 +147,9 @@ Oid param_type(int t); /* used in parse_expr.c */ %type TriggerActionTime, TriggerForSpec, PLangTrusted +%type OptConstrTrigDeferrable, OptConstrTrigInitdeferred +%type OptConstrFromTable + %type TriggerEvents, TriggerFuncArg %type relation_name, copy_file_name, copy_delimiter, def_name, @@ -254,6 +258,10 @@ Oid param_type(int t); /* used in parse_expr.c */ %type key_actions, key_action %type key_match, key_reference +%type constraints_set_list +%type constraints_set_namelist +%type constraints_set_mode + /* * If you make any token changes, remember to: * - use "yacc -d" and update parse.h @@ -274,8 +282,8 @@ Oid param_type(int t); /* used in parse_expr.c */ BEGIN_TRANS, BETWEEN, BOTH, BY, CASCADE, CASE, CAST, CHAR, CHARACTER, CHECK, CLOSE, COALESCE, COLLATE, COLUMN, COMMIT, - CONSTRAINT, CREATE, CROSS, CURRENT, CURRENT_DATE, CURRENT_TIME, - CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, + CONSTRAINT, CONSTRAINTS, CREATE, CROSS, CURRENT, CURRENT_DATE, + CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, DAY_P, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DISTINCT, DOUBLE, DROP, ELSE, END_TRANS, EXCEPT, EXECUTE, EXISTS, EXTRACT, FALSE_P, FETCH, FLOAT, FOR, FOREIGN, FROM, FULL, @@ -295,7 +303,11 @@ Oid param_type(int t); /* used in parse_expr.c */ WHEN, WHERE, WITH, WORK, YEAR_P, ZONE /* Keywords (in SQL3 reserved words) */ -%token TRIGGER +%token DEFERRABLE, DEFERRED, + IMMEDIATE, INITIALLY, + PENDANT, + RESTRICT, + TRIGGER /* Keywords (in SQL92 non-reserved words) */ %token COMMITTED, SERIALIZABLE, TYPE_P @@ -415,6 +427,7 @@ stmt : AddAttrStmt | VariableSetStmt | VariableShowStmt | VariableResetStmt + | ConstraintsSetStmt ; /***************************************************************************** @@ -630,6 +643,49 @@ VariableResetStmt: RESET ColId ; +ConstraintsSetStmt: SET CONSTRAINTS constraints_set_list constraints_set_mode + { + ConstraintsSetStmt *n = makeNode(ConstraintsSetStmt); + n->constraints = $3; + n->deferred = $4; + $$ = (Node *) n; + } + ; + + +constraints_set_list: ALL + { + $$ = NIL; + } + | constraints_set_namelist + { + $$ = $1; + } + ; + + +constraints_set_namelist: IDENT + { + $$ = lappend(NIL, $1); + } + | constraints_set_namelist ',' IDENT + { + $$ = lappend($1, $3); + } + ; + + +constraints_set_mode: DEFERRED + { + $$ = true; + } + | IMMEDIATE + { + $$ = false; + } + ; + + /***************************************************************************** * * QUERY : @@ -1434,6 +1490,54 @@ CreateTrigStmt: CREATE TRIGGER name TriggerActionTime TriggerEvents ON n->before = $4; n->row = $8; memcpy (n->actions, $5, 4); + n->lang = NULL; /* unused */ + n->text = NULL; /* unused */ + n->attr = NULL; /* unused */ + n->when = NULL; /* unused */ + + n->isconstraint = false; + n->deferrable = false; + n->initdeferred = false; + n->constrrelname = NULL; + $$ = (Node *)n; + } + | CREATE CONSTRAINT TRIGGER name AFTER TriggerOneEvent ON + relation_name OptConstrFromTable + OptConstrTrigDeferrable OptConstrTrigInitdeferred + FOR EACH ROW EXECUTE PROCEDURE name '(' TriggerFuncArgs ')' + { + CreateTrigStmt *n = makeNode(CreateTrigStmt); + n->trigname = $4; + n->relname = $8; + n->funcname = $17; + n->args = $19; + n->before = false; + n->row = true; + n->actions[0] = $6; + n->actions[1] = '\0'; + n->lang = NULL; /* unused */ + n->text = NULL; /* unused */ + n->attr = NULL; /* unused */ + n->when = NULL; /* unused */ + + /* + * Check that the DEFERRABLE and INITIALLY combination + * makes sense + */ + n->isconstraint = true; + if ($11 == 1) + { + if ($10 == 0) + elog(ERROR, "INITIALLY DEFERRED constraint " + "cannot be NOT DEFERRABLE"); + n->deferrable = true; + n->initdeferred = true; + } else { + n->deferrable = ($10 == 1); + n->initdeferred = false; + } + + n->constrrelname = $9; $$ = (Node *)n; } ; @@ -1503,6 +1607,44 @@ TriggerFuncArg: ICONST | IDENT { $$ = $1; } ; +OptConstrFromTable: /* Empty */ + { + $$ = ""; + } + | FROM relation_name + { + $$ = $2; + } + ; + +OptConstrTrigDeferrable: /* Empty */ + { + $$ = -1; + } + | DEFERRABLE + { + $$ = 1; + } + | NOT DEFERRABLE + { + $$ = 0; + } + ; + +OptConstrTrigInitdeferred: /* Empty */ + { + $$ = -1; + } + | INITIALLY DEFERRED + { + $$ = 1; + } + | INITIALLY IMMEDIATE + { + $$ = 0; + } + ; + DropTrigStmt: DROP TRIGGER name ON relation_name { DropTrigStmt *n = makeNode(DropTrigStmt); @@ -5080,10 +5222,13 @@ ColId: IDENT { $$ = $1; } | BEFORE { $$ = "before"; } | CACHE { $$ = "cache"; } | COMMITTED { $$ = "committed"; } + | CONSTRAINTS { $$ = "constraints"; } | CREATEDB { $$ = "createdb"; } | CREATEUSER { $$ = "createuser"; } | CYCLE { $$ = "cycle"; } | DATABASE { $$ = "database"; } + | DEFERRABLE { $$ = "deferrable"; } + | DEFERRED { $$ = "deferred"; } | DELIMITERS { $$ = "delimiters"; } | DOUBLE { $$ = "double"; } | EACH { $$ = "each"; } @@ -5092,9 +5237,11 @@ ColId: IDENT { $$ = $1; } | FORWARD { $$ = "forward"; } | FUNCTION { $$ = "function"; } | HANDLER { $$ = "handler"; } + | IMMEDIATE { $$ = "immediate"; } | INCREMENT { $$ = "increment"; } | INDEX { $$ = "index"; } | INHERITS { $$ = "inherits"; } + | INITIALLY { $$ = "initially"; } | INSENSITIVE { $$ = "insensitive"; } | INSTEAD { $$ = "instead"; } | ISNULL { $$ = "isnull"; } @@ -5119,12 +5266,14 @@ ColId: IDENT { $$ = $1; } | OPERATOR { $$ = "operator"; } | OPTION { $$ = "option"; } | PASSWORD { $$ = "password"; } + | PENDANT { $$ = "pendant"; } | PRIOR { $$ = "prior"; } | PRIVILEGES { $$ = "privileges"; } | PROCEDURAL { $$ = "procedural"; } | READ { $$ = "read"; } | RELATIVE { $$ = "relative"; } | RENAME { $$ = "rename"; } + | RESTRICT { $$ = "restrict"; } | RETURNS { $$ = "returns"; } | ROW { $$ = "row"; } | RULE { $$ = "rule"; } diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 17a521337e..3e30a47d12 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.61 1999/09/23 17:02:46 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.62 1999/09/29 16:06:08 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -63,6 +63,7 @@ static ScanKeyword ScanKeywords[] = { {"commit", COMMIT}, {"committed", COMMITTED}, {"constraint", CONSTRAINT}, + {"constraints", CONSTRAINTS}, {"copy", COPY}, {"create", CREATE}, {"createdb", CREATEDB}, @@ -79,6 +80,8 @@ static ScanKeyword ScanKeywords[] = { {"decimal", DECIMAL}, {"declare", DECLARE}, {"default", DEFAULT}, + {"deferrable", DEFERRABLE}, + {"deferred", DEFERRED}, {"delete", DELETE}, {"delimiters", DELIMITERS}, {"desc", DESC}, @@ -112,10 +115,12 @@ static ScanKeyword ScanKeywords[] = { {"handler", HANDLER}, {"having", HAVING}, {"hour", HOUR_P}, + {"immediate", IMMEDIATE}, {"in", IN}, {"increment", INCREMENT}, {"index", INDEX}, {"inherits", INHERITS}, + {"initially", INITIALLY}, {"inner", INNER_P}, {"insensitive", INSENSITIVE}, {"insert", INSERT}, @@ -177,6 +182,7 @@ static ScanKeyword ScanKeywords[] = { {"outer", OUTER_P}, {"partial", PARTIAL}, {"password", PASSWORD}, + {"pendant", PENDANT}, {"position", POSITION}, {"precision", PRECISION}, {"primary", PRIMARY}, @@ -190,6 +196,7 @@ static ScanKeyword ScanKeywords[] = { {"relative", RELATIVE}, {"rename", RENAME}, {"reset", RESET}, + {"restrict", RESTRICT}, {"returns", RETURNS}, {"revoke", REVOKE}, {"right", RIGHT}, diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 4947b29137..2a3e7e7730 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.129 1999/09/24 00:24:52 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.130 1999/09/29 16:06:10 wieck Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -44,6 +44,7 @@ #endif #include "commands/async.h" +#include "commands/trigger.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "libpq/pqsignal.h" @@ -1484,9 +1485,16 @@ PostgresMain(int argc, char *argv[], int real_argc, char *real_argv[]) if (!IsUnderPostmaster) { puts("\nPOSTGRES backend interactive interface "); - puts("$Revision: 1.129 $ $Date: 1999/09/24 00:24:52 $\n"); + puts("$Revision: 1.130 $ $Date: 1999/09/29 16:06:10 $\n"); } + /* ---------------- + * Initialize the deferred trigger manager + * ---------------- + */ + if (DeferredTriggerInit() != 0) + ExitPostgres(1); + /* ---------------- * POSTGRES main processing loop begins here * @@ -1609,6 +1617,12 @@ PostgresMain(int argc, char *argv[], int real_argc, char *real_argv[]) pg_exec_query(parser_input->data); + /* + * Invoke IMMEDIATE constraint triggers + * + */ + DeferredTriggerEndQuery(); + if (ShowStats) ShowUsage(); } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 9a5b7935db..1860920116 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.67 1999/09/27 15:47:54 vadim Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.68 1999/09/29 16:06:11 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -785,6 +785,13 @@ ProcessUtility(Node *parsetree, LockTableCommand((LockStmt *) parsetree); break; + case T_ConstraintsSetStmt: + PS_SET_STATUS(commandTag = "SET CONSTRAINTS"); + CHECK_IF_ABORTED(); + + DeferredTriggerSetState((ConstraintsSetStmt *) parsetree); + break; + /* * ******************************** default ******************************** diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 698342d081..39bc8193da 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -7,7 +7,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: indexing.h,v 1.23 1999/07/20 17:14:07 momjian Exp $ + * $Id: indexing.h,v 1.24 1999/09/29 16:06:14 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -25,27 +25,29 @@ #define Num_pg_class_indices 2 #define Num_pg_attrdef_indices 1 #define Num_pg_relcheck_indices 1 -#define Num_pg_trigger_indices 1 +#define Num_pg_trigger_indices 3 #define Num_pg_description_indices 1 /* * Names of indices on system catalogs */ -#define AttributeNameIndex "pg_attribute_relid_attnam_index" -#define AttributeNumIndex "pg_attribute_relid_attnum_index" -#define AttributeRelidIndex "pg_attribute_attrelid_index" -#define ProcedureOidIndex "pg_proc_oid_index" -#define ProcedureNameIndex "pg_proc_proname_narg_type_index" -#define ProcedureSrcIndex "pg_proc_prosrc_index" -#define TypeOidIndex "pg_type_oid_index" -#define TypeNameIndex "pg_type_typname_index" -#define ClassOidIndex "pg_class_oid_index" -#define ClassNameIndex "pg_class_relname_index" -#define AttrDefaultIndex "pg_attrdef_adrelid_index" -#define RelCheckIndex "pg_relcheck_rcrelid_index" -#define TriggerRelidIndex "pg_trigger_tgrelid_index" -#define DescriptionObjIndex "pg_description_objoid_index" +#define AttributeNameIndex "pg_attribute_relid_attnam_index" +#define AttributeNumIndex "pg_attribute_relid_attnum_index" +#define AttributeRelidIndex "pg_attribute_attrelid_index" +#define ProcedureOidIndex "pg_proc_oid_index" +#define ProcedureNameIndex "pg_proc_proname_narg_type_index" +#define ProcedureSrcIndex "pg_proc_prosrc_index" +#define TypeOidIndex "pg_type_oid_index" +#define TypeNameIndex "pg_type_typname_index" +#define ClassOidIndex "pg_class_oid_index" +#define ClassNameIndex "pg_class_relname_index" +#define AttrDefaultIndex "pg_attrdef_adrelid_index" +#define RelCheckIndex "pg_relcheck_rcrelid_index" +#define TriggerRelidIndex "pg_trigger_tgrelid_index" +#define TriggerConstrNameIndex "pg_trigger_tgconstrname_index" +#define TriggerConstrRelidIndex "pg_trigger_tgconstrrelid_index" +#define DescriptionObjIndex "pg_description_objoid_index" extern char *Name_pg_attr_indices[]; extern char *Name_pg_proc_indices[]; @@ -114,6 +116,8 @@ DECLARE_INDEX(pg_attrdef_adrelid_index on pg_attrdef using btree(adrelid oid_ops DECLARE_INDEX(pg_relcheck_rcrelid_index on pg_relcheck using btree(rcrelid oid_ops)); DECLARE_INDEX(pg_trigger_tgrelid_index on pg_trigger using btree(tgrelid oid_ops)); +DECLARE_INDEX(pg_trigger_tgconstrname_index on pg_trigger using btree(tgconstrname name_ops)); +DECLARE_INDEX(pg_trigger_tgconstrrelid_index on pg_trigger using btree(tgconstrrelid oid_ops)); DECLARE_INDEX(pg_description_objoid_index on pg_description using btree(objoid oid_ops)); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index a02cc20033..ae2067988d 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -7,7 +7,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: pg_attribute.h,v 1.49 1999/08/09 02:45:56 tgl Exp $ + * $Id: pg_attribute.h,v 1.50 1999/09/29 16:06:16 wieck Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -447,9 +447,16 @@ DATA(insert OID = 0 ( 1219 tgrelid 26 0 4 1 0 -1 -1 t f i f f)); DATA(insert OID = 0 ( 1219 tgname 19 0 NAMEDATALEN 2 0 -1 -1 f f i f f)); DATA(insert OID = 0 ( 1219 tgfoid 26 0 4 3 0 -1 -1 t f i f f)); DATA(insert OID = 0 ( 1219 tgtype 21 0 2 4 0 -1 -1 t f s f f)); -DATA(insert OID = 0 ( 1219 tgnargs 21 0 2 5 0 -1 -1 t f s f f)); -DATA(insert OID = 0 ( 1219 tgattr 22 0 16 6 0 -1 -1 f f i f f)); -DATA(insert OID = 0 ( 1219 tgargs 17 0 -1 7 0 -1 -1 f f i f f)); +DATA(insert OID = 0 ( 1219 tgenabled 16 0 1 5 0 -1 -1 t f c f f)); +DATA(insert OID = 0 ( 1219 tgisconstraint 16 0 1 6 0 -1 -1 t f c f f)); +DATA(insert OID = 0 ( 1219 tgconstrname 19 0 NAMEDATALEN 7 0 -1 -1 f f i f f)); +DATA(insert OID = 0 ( 1219 tgconstrrelid 26 0 4 8 0 -1 -1 t f i f f)); + +DATA(insert OID = 0 ( 1219 tgdeferrable 16 0 1 9 0 -1 -1 t f c f f)); +DATA(insert OID = 0 ( 1219 tginitdeferred 16 0 1 10 0 -1 -1 t f c f f)); +DATA(insert OID = 0 ( 1219 tgnargs 21 0 2 11 0 -1 -1 t f s f f)); +DATA(insert OID = 0 ( 1219 tgattr 22 0 16 12 0 -1 -1 f f i f f)); +DATA(insert OID = 0 ( 1219 tgargs 17 0 -1 13 0 -1 -1 f f i f f)); DATA(insert OID = 0 ( 1219 ctid 27 0 6 -1 0 -1 -1 f f i f f)); DATA(insert OID = 0 ( 1219 oid 26 0 4 -2 0 -1 -1 t f i f f)); DATA(insert OID = 0 ( 1219 xmin 28 0 4 -3 0 -1 -1 t f i f f)); diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 5647707a20..7bf6929d6c 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -7,7 +7,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: pg_class.h,v 1.29 1999/05/25 16:13:44 momjian Exp $ + * $Id: pg_class.h,v 1.30 1999/09/29 16:06:16 wieck Exp $ * * NOTES * ``pg_relation'' is being replaced by ``pg_class''. currently @@ -150,7 +150,7 @@ DATA(insert OID = 1215 ( pg_attrdef 109 PGUID 0 0 0 t t r 4 0 0 0 0 0 f f _n DESCR(""); DATA(insert OID = 1216 ( pg_relcheck 110 PGUID 0 0 0 t t r 4 0 0 0 0 0 f f _null_ )); DESCR(""); -DATA(insert OID = 1219 ( pg_trigger 111 PGUID 0 0 0 t t r 7 0 0 0 0 0 f f _null_ )); +DATA(insert OID = 1219 ( pg_trigger 111 PGUID 0 0 0 t t r 13 0 0 0 0 0 f f _null_ )); DESCR(""); #define RelOid_pg_type 1247 diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h index 2d12e73da5..be0633eb98 100644 --- a/src/include/catalog/pg_trigger.h +++ b/src/include/catalog/pg_trigger.h @@ -33,6 +33,13 @@ CATALOG(pg_trigger) BOOTSTRAP Oid tgfoid; /* OID of function to be called */ int2 tgtype; /* BEFORE/AFTER UPDATE/DELETE/INSERT * ROW/STATEMENT */ + bool tgenabled; /* trigger is enabled/disabled */ + bool tgisconstraint; /* trigger is a RI constraint */ + NameData tgconstrname; /* RI constraint name */ + Oid tgconstrrelid; /* RI table of foreign key definition */ + /* in the case of ON DELETE or ON UPDATE */ + bool tgdeferrable; /* RI trigger is deferrable */ + bool tginitdeferred; /* RI trigger is deferred initially */ int2 tgnargs; /* # of extra arguments in tgargs */ int28 tgattr; /* UPDATE of attr1, attr2 ... (NI) */ bytea tgargs; /* first\000second\000tgnargs\000 */ @@ -49,14 +56,20 @@ typedef FormData_pg_trigger *Form_pg_trigger; * compiler constants for pg_trigger * ---------------- */ -#define Natts_pg_trigger 7 +#define Natts_pg_trigger 13 #define Anum_pg_trigger_tgrelid 1 #define Anum_pg_trigger_tgname 2 #define Anum_pg_trigger_tgfoid 3 #define Anum_pg_trigger_tgtype 4 -#define Anum_pg_trigger_tgnargs 5 -#define Anum_pg_trigger_tgattr 6 -#define Anum_pg_trigger_tgargs 7 +#define Anum_pg_trigger_tgenabled 5 +#define Anum_pg_trigger_tgisconstraint 6 +#define Anum_pg_trigger_tgconstrname 7 +#define Anum_pg_trigger_tgconstrrelid 8 +#define Anum_pg_trigger_tgdeferrable 9 +#define Anum_pg_trigger_tginitdeferred 10 +#define Anum_pg_trigger_tgnargs 11 +#define Anum_pg_trigger_tgattr 12 +#define Anum_pg_trigger_tgargs 13 #define TRIGGER_TYPE_ROW (1 << 0) #define TRIGGER_TYPE_BEFORE (1 << 1) diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 99350d55d1..8ab4342fa7 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -32,6 +32,13 @@ extern DLLIMPORT TriggerData *CurrentTriggerData; #define TRIGGER_EVENT_ROW 0x00000004 #define TRIGGER_EVENT_BEFORE 0x00000008 +#define TRIGGER_DEFERRED_DONE 0x00000010 +#define TRIGGER_DEFERRED_CANCELED 0x00000020 +#define TRIGGER_DEFERRED_DEFERRABLE 0x00000040 +#define TRIGGER_DEFERRED_INITDEFERRED 0x00000080 +#define TRIGGER_DEFERRED_HAS_BEFORE 0x00000100 +#define TRIGGER_DEFERRED_MASK 0x000001F0 + #define TRIGGER_FIRED_BY_INSERT(event) \ (((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \ TRIGGER_EVENT_INSERT) @@ -68,4 +75,46 @@ extern void ExecARDeleteTriggers(EState *estate, ItemPointer tupleid); extern HeapTuple ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple tuple); extern void ExecARUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple tuple); + + +/* ---------- + * Deferred trigger stuff + * ---------- + */ +typedef struct DeferredTriggerStatusData { + Oid dts_tgoid; + bool dts_tgisdeferred; +} DeferredTriggerStatusData; +typedef struct DeferredTriggerStatusData *DeferredTriggerStatus; + + +typedef struct DeferredTriggerEventItem { + Oid dti_tgoid; + int32 dti_state; +} DeferredTriggerEventItem; + + +typedef struct DeferredTriggerEventData { + int32 dte_event; + Oid dte_relid; + ItemPointerData dte_oldctid; + ItemPointerData dte_newctid; + int32 dte_n_items; + DeferredTriggerEventItem dte_item[1]; +} DeferredTriggerEventData; +typedef struct DeferredTriggerEventData *DeferredTriggerEvent; + + +extern int DeferredTriggerInit(void); +extern void DeferredTriggerBeginXact(void); +extern void DeferredTriggerEndQuery(void); +extern void DeferredTriggerEndXact(void); +extern void DeferredTriggerAbortXact(void); + +extern void DeferredTriggerSetState(ConstraintsSetStmt *stmt); + +extern void DeferredTriggerSaveEvent(Relation rel, int event, + HeapTuple oldtup, HeapTuple newtup); + + #endif /* TRIGGER_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index da8a06af42..68d8247324 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: nodes.h,v 1.52 1999/09/23 17:03:21 momjian Exp $ + * $Id: nodes.h,v 1.53 1999/09/29 16:06:23 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -184,6 +184,7 @@ typedef enum NodeTag T_AlterUserStmt, T_DropUserStmt, T_LockStmt, + T_ConstraintsSetStmt, T_A_Expr = 700, T_Attr, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4bf879137a..a736c1af67 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.80 1999/09/28 04:34:50 momjian Exp $ + * $Id: parsenodes.h,v 1.81 1999/09/29 16:06:23 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -180,6 +180,13 @@ typedef struct CreateTrigStmt char *text; /* AS 'text' */ List *attr; /* UPDATE OF a, b,... (NI) or NULL */ char *when; /* WHEN 'a > 10 ...' (NI) or NULL */ + + /* The following are used for referential */ + /* integrity constraint triggers */ + bool isconstraint; /* This is an RI trigger */ + bool deferrable; /* [NOT] DEFERRABLE */ + bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */ + char *constrrelname; /* opposite relation */ } CreateTrigStmt; typedef struct DropTrigStmt @@ -602,6 +609,19 @@ typedef struct LockStmt int mode; /* lock mode */ } LockStmt; + +/* ---------------------- + * SET CONSTRAINTS Statement + * ---------------------- + */ +typedef struct ConstraintsSetStmt +{ + NodeTag type; + List *constraints; + bool deferred; +} ConstraintsSetStmt; + + /***************************************************************************** * Optimizable Statements *****************************************************************************/ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index f2789b39e0..308f9a6d64 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: rel.h,v 1.26 1999/09/18 19:08:25 tgl Exp $ + * $Id: rel.h,v 1.27 1999/09/29 16:06:28 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -42,10 +42,15 @@ typedef LockInfoData *LockInfo; typedef struct Trigger { + Oid tgoid; char *tgname; Oid tgfoid; FmgrInfo tgfunc; int16 tgtype; + bool tgenabled; + bool tgisconstraint; + bool tgdeferrable; + bool tginitdeferred; int16 tgnargs; int16 tgattr[8]; char **tgargs; diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h index 4cb685968b..d8aa6638f6 100644 --- a/src/include/utils/tqual.h +++ b/src/include/utils/tqual.h @@ -7,7 +7,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: tqual.h,v 1.24 1999/07/15 23:04:24 momjian Exp $ + * $Id: tqual.h,v 1.25 1999/09/29 16:06:28 wieck Exp $ * *------------------------------------------------------------------------- */ @@ -30,6 +30,7 @@ typedef SnapshotData *Snapshot; #define SnapshotNow ((Snapshot) 0x0) #define SnapshotSelf ((Snapshot) 0x1) +#define SnapshotAny ((Snapshot) 0x2) extern Snapshot SnapshotDirty; extern Snapshot QuerySnapshot; @@ -37,6 +38,7 @@ extern Snapshot SerializableSnapshot; #define IsSnapshotNow(snapshot) ((Snapshot) snapshot == SnapshotNow) #define IsSnapshotSelf(snapshot) ((Snapshot) snapshot == SnapshotSelf) +#define IsSnapshotAny(snapshot) ((Snapshot) snapshot == SnapshotAny) #define IsSnapshotDirty(snapshot) ((Snapshot) snapshot == SnapshotDirty) extern TransactionId HeapSpecialTransactionId; @@ -55,18 +57,22 @@ extern CommandId HeapSpecialCommandId; false \ : \ ( \ - (IsSnapshotSelf(snapshot) || heapisoverride()) ? \ - HeapTupleSatisfiesItself((tuple)->t_data) \ + (IsSnapshotAny(snapshot) || heapisoverride()) ? \ + true \ : \ - ((IsSnapshotDirty(snapshot)) ? \ - HeapTupleSatisfiesDirty((tuple)->t_data) \ + ((IsSnapshotSelf(snapshot) || heapisoverride()) ? \ + HeapTupleSatisfiesItself((tuple)->t_data) \ : \ - ((IsSnapshotNow(snapshot)) ? \ - HeapTupleSatisfiesNow((tuple)->t_data) \ + ((IsSnapshotDirty(snapshot)) ? \ + HeapTupleSatisfiesDirty((tuple)->t_data) \ : \ - HeapTupleSatisfiesSnapshot((tuple)->t_data, snapshot) \ - ) \ + ((IsSnapshotNow(snapshot)) ? \ + HeapTupleSatisfiesNow((tuple)->t_data) \ + : \ + HeapTupleSatisfiesSnapshot((tuple)->t_data, snapshot) \ + ) \ ) \ + ) \ ) \ ) diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index c4cf5b12b0..ddb2d795a5 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -64,101 +64,6 @@ NOTICE: check_pkeys_fkey_cascade: 1 tuple(s) of fkeys2 are deleted QUERY: DROP TABLE pkeys; QUERY: DROP TABLE fkeys; QUERY: DROP TABLE fkeys2; -QUERY: create table dup17 (x int4); -QUERY: create trigger dup17_before - before insert on dup17 - for each row - execute procedure - funny_dup17 () -; -QUERY: insert into dup17 values (17); -NOTICE: funny_dup17 (fired BEFORE) on level 1: 0/0 tuples inserted/selected -QUERY: select count(*) from dup17; -count ------ - 1 -(1 row) - -QUERY: insert into dup17 values (17); -NOTICE: funny_dup17 (fired BEFORE) on level 17: 1/2 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 16: 1/3 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 15: 1/4 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 14: 1/5 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 13: 1/6 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 12: 1/7 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 11: 1/8 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 10: 1/9 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 9: 1/10 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 8: 1/11 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 7: 1/12 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 6: 1/13 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 5: 1/14 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 4: 1/15 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 3: 1/16 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 2: 1/17 tuples inserted/selected -NOTICE: funny_dup17 (fired BEFORE) on level 1: 1/18 tuples inserted/selected -QUERY: select count(*) from dup17; -count ------ - 19 -(1 row) - -QUERY: drop trigger dup17_before on dup17; -QUERY: create trigger dup17_after - after insert on dup17 - for each row - execute procedure - funny_dup17 () -; -QUERY: insert into dup17 values (13); -NOTICE: funny_dup17 (fired AFTER ) on level 17: 17/34 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 16: 16/49 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 15: 15/63 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 14: 14/76 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 13: 13/88 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 12: 12/99 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 11: 11/109 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 10: 10/118 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 9: 9/126 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 8: 8/133 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 7: 7/139 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 6: 6/144 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 5: 5/148 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 4: 4/151 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 3: 3/153 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 2: 2/154 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 1: 1/154 tuples inserted/selected -QUERY: select count(*) from dup17 where x = 13; -count ------ - 154 -(1 row) - -QUERY: insert into dup17 values (13); -NOTICE: funny_dup17 (fired AFTER ) on level 17: 171/342 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 16: 170/511 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 15: 169/679 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 14: 168/846 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 13: 167/1012 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 12: 166/1177 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 11: 165/1341 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 10: 164/1504 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 9: 163/1666 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 8: 162/1827 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 7: 161/1987 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 6: 160/2146 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 5: 159/2304 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 4: 158/2461 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 3: 157/2617 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 2: 156/2772 tuples inserted/selected -NOTICE: funny_dup17 (fired AFTER ) on level 1: 155/2926 tuples inserted/selected -QUERY: select count(*) from dup17 where x = 13; -count ------ - 2926 -(1 row) - -QUERY: DROP TABLE dup17; QUERY: create sequence ttdummy_seq increment 10 start 0 minvalue 0; QUERY: create table tttest ( price_id int4, diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index f7d2c23a5a..5740dcac9f 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -88,34 +88,40 @@ DROP TABLE pkeys; DROP TABLE fkeys; DROP TABLE fkeys2; -create table dup17 (x int4); - -create trigger dup17_before - before insert on dup17 - for each row - execute procedure - funny_dup17 () -; - -insert into dup17 values (17); -select count(*) from dup17; -insert into dup17 values (17); -select count(*) from dup17; - -drop trigger dup17_before on dup17; - -create trigger dup17_after - after insert on dup17 - for each row - execute procedure - funny_dup17 () -; -insert into dup17 values (13); -select count(*) from dup17 where x = 13; -insert into dup17 values (13); -select count(*) from dup17 where x = 13; - -DROP TABLE dup17; +-- -- I've disabled the funny_dup17 test because the new semantics +-- -- of AFTER ROW triggers, which get now fired at the end of a +-- -- query allways, cause funny_dup17 to enter an endless loop. +-- -- +-- -- Jan +-- +-- create table dup17 (x int4); +-- +-- create trigger dup17_before +-- before insert on dup17 +-- for each row +-- execute procedure +-- funny_dup17 () +-- ; +-- +-- insert into dup17 values (17); +-- select count(*) from dup17; +-- insert into dup17 values (17); +-- select count(*) from dup17; +-- +-- drop trigger dup17_before on dup17; +-- +-- create trigger dup17_after +-- after insert on dup17 +-- for each row +-- execute procedure +-- funny_dup17 () +-- ; +-- insert into dup17 values (13); +-- select count(*) from dup17 where x = 13; +-- insert into dup17 values (13); +-- select count(*) from dup17 where x = 13; +-- +-- DROP TABLE dup17; create sequence ttdummy_seq increment 10 start 0 minvalue 0; -- 2.40.0