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);
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);
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;
HeapScanDesc tgscan;
ScanKeyData key;
HeapTuple tup;
+ Form_pg_trigger pg_trigger;
tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
ScanKeyEntryInitialize(&key, 0, Anum_pg_trigger_tgrelid,
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);
}
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,
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];
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;
}
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];
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;
}
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;
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;
}
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;
+}
+
+
*
*
* 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
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 <str> opt_database1, opt_database2, location, encoding
%type <boolean> TriggerActionTime, TriggerForSpec, PLangTrusted
+%type <ival> OptConstrTrigDeferrable, OptConstrTrigInitdeferred
+%type <str> OptConstrFromTable
+
%type <str> TriggerEvents, TriggerFuncArg
%type <str> relation_name, copy_file_name, copy_delimiter, def_name,
%type <list> key_actions, key_action
%type <str> key_match, key_reference
+%type <list> constraints_set_list
+%type <list> constraints_set_namelist
+%type <boolean> constraints_set_mode
+
/*
* If you make any token changes, remember to:
* - use "yacc -d" and update parse.h
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,
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
| VariableSetStmt
| VariableShowStmt
| VariableResetStmt
+ | ConstraintsSetStmt
;
/*****************************************************************************
;
+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 :
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;
}
;
| 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);
| 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"; }
| FORWARD { $$ = "forward"; }
| FUNCTION { $$ = "function"; }
| HANDLER { $$ = "handler"; }
+ | IMMEDIATE { $$ = "immediate"; }
| INCREMENT { $$ = "increment"; }
| INDEX { $$ = "index"; }
| INHERITS { $$ = "inherits"; }
+ | INITIALLY { $$ = "initially"; }
| INSENSITIVE { $$ = "insensitive"; }
| INSTEAD { $$ = "instead"; }
| ISNULL { $$ = "isnull"; }
| 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"; }