#include "postgres.h"#include "access/genam.h"#include "access/heapam.h"#include "access/sysattr.h"#include "access/htup_details.h"#include "access/xact.h"#include "catalog/catalog.h"#include "catalog/dependency.h"#include "catalog/indexing.h"#include "catalog/objectaccess.h"#include "catalog/pg_constraint.h"#include "catalog/pg_proc.h"#include "catalog/pg_trigger.h"#include "catalog/pg_type.h"#include "commands/dbcommands.h"#include "commands/defrem.h"#include "commands/trigger.h"#include "executor/executor.h"#include "miscadmin.h"#include "nodes/bitmapset.h"#include "nodes/makefuncs.h"#include "optimizer/clauses.h"#include "optimizer/var.h"#include "parser/parse_clause.h"#include "parser/parse_collate.h"#include "parser/parse_func.h"#include "parser/parse_relation.h"#include "parser/parsetree.h"#include "pgstat.h"#include "rewrite/rewriteManip.h"#include "storage/bufmgr.h"#include "tcop/utility.h"#include "utils/acl.h"#include "utils/builtins.h"#include "utils/bytea.h"#include "utils/fmgroids.h"#include "utils/inval.h"#include "utils/lsyscache.h"#include "utils/memutils.h"#include "utils/rel.h"#include "utils/snapmgr.h"#include "utils/syscache.h"#include "utils/tqual.h"
Go to the source code of this file.
| #define AFTER_TRIGGER_2CTIDS 0x10000000 |
Definition at line 2939 of file trigger.c.
Referenced by AfterTriggerExecute().
| #define AFTER_TRIGGER_DONE 0x20000000 |
Definition at line 2940 of file trigger.c.
Referenced by AfterTriggerEndSubXact(), afterTriggerInvokeEvents(), afterTriggerMarkEvents(), and AfterTriggerPendingOnRel().
| #define AFTER_TRIGGER_IN_PROGRESS 0x40000000 |
Definition at line 2941 of file trigger.c.
Referenced by AfterTriggerEndSubXact(), afterTriggerInvokeEvents(), and afterTriggerMarkEvents().
| #define CHUNK_DATA_START | ( | cptr | ) | ((char *) (cptr) + MAXALIGN(sizeof(AfterTriggerEventChunk))) |
Definition at line 2992 of file trigger.c.
Referenced by afterTriggerAddEvent(), and afterTriggerInvokeEvents().
| #define DEFTRIG_INITALLOC 8 |
Referenced by AfterTriggerBeginSubXact().
| #define for_each_chunk | ( | cptr, | ||
| evtlist | ||||
| ) | for (cptr = (evtlist).head; cptr != NULL; cptr = cptr->next) |
Definition at line 3003 of file trigger.c.
Referenced by afterTriggerInvokeEvents().
| #define for_each_event | ( | eptr, | ||
| cptr | ||||
| ) |
for (eptr = (AfterTriggerEvent) CHUNK_DATA_START(cptr); \ (char *) eptr < (cptr)->freeptr; \ eptr = (AfterTriggerEvent) (((char *) eptr) + SizeofTriggerEvent(eptr)))
Definition at line 3005 of file trigger.c.
Referenced by afterTriggerInvokeEvents().
| #define for_each_event_chunk | ( | eptr, | ||
| cptr, | ||||
| evtlist | ||||
| ) | for_each_chunk(cptr, evtlist) for_each_event(eptr, cptr) |
Definition at line 3010 of file trigger.c.
Referenced by AfterTriggerEndSubXact(), afterTriggerMarkEvents(), and AfterTriggerPendingOnRel().
| #define GetModifiedColumns | ( | relinfo, | ||
| estate | ||||
| ) | (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols) |
Definition at line 66 of file trigger.c.
Referenced by ExecARUpdateTriggers(), ExecASUpdateTriggers(), ExecBRUpdateTriggers(), and ExecBSUpdateTriggers().
| #define GetTriggerSharedData | ( | evt | ) | ((AfterTriggerShared) ((char *) (evt) + ((evt)->ate_flags & AFTER_TRIGGER_OFFSET))) |
Definition at line 2973 of file trigger.c.
Referenced by AfterTriggerEndSubXact(), AfterTriggerExecute(), afterTriggerInvokeEvents(), afterTriggerMarkEvents(), and AfterTriggerPendingOnRel().
| #define MAX_CHUNK_SIZE (1024*1024) |
Referenced by afterTriggerAddEvent().
| #define MIN_CHUNK_SIZE 1024 |
| #define SizeofTriggerEvent | ( | evt | ) |
(((evt)->ate_flags & AFTER_TRIGGER_2CTIDS) ? \ sizeof(AfterTriggerEventData) : sizeof(AfterTriggerEventDataOneCtid))
Definition at line 2969 of file trigger.c.
Referenced by afterTriggerAddEvent().
| typedef struct AfterTriggerEventData* AfterTriggerEvent |
| typedef struct AfterTriggerEventChunk AfterTriggerEventChunk |
| typedef struct AfterTriggerEventData AfterTriggerEventData |
| typedef struct AfterTriggerEventDataOneCtid AfterTriggerEventDataOneCtid |
| typedef struct AfterTriggerEventList AfterTriggerEventList |
| typedef AfterTriggersData* AfterTriggers |
| typedef struct AfterTriggersData AfterTriggersData |
| typedef struct AfterTriggerSharedData* AfterTriggerShared |
| typedef struct AfterTriggerSharedData AfterTriggerSharedData |
| typedef SetConstraintStateData* SetConstraintState |
| typedef struct SetConstraintStateData SetConstraintStateData |
| typedef struct SetConstraintTriggerData* SetConstraintTrigger |
| typedef struct SetConstraintTriggerData SetConstraintTriggerData |
| typedef uint32 TriggerFlags |
| static void afterTriggerAddEvent | ( | AfterTriggerEventList * | events, | |
| AfterTriggerEvent | event, | |||
| AfterTriggerShared | evtshared | |||
| ) | [static] |
Definition at line 3151 of file trigger.c.
References ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, ALLOCSET_DEFAULT_MINSIZE, AllocSetContextCreate(), Assert, AfterTriggerEventData::ate_flags, AfterTriggerSharedData::ats_event, AfterTriggerSharedData::ats_firing_id, AfterTriggerSharedData::ats_relid, AfterTriggerSharedData::ats_tgoid, CHUNK_DATA_START, AfterTriggersData::event_cxt, AfterTriggerEventList::head, MAX_CHUNK_SIZE, MemoryContextAlloc(), Min, AfterTriggerEventChunk::next, NULL, SizeofTriggerEvent, AfterTriggerEventList::tail, AfterTriggerEventList::tailfree, and TopTransactionContext.
Referenced by afterTriggerMarkEvents(), and AfterTriggerSaveEvent().
{
Size eventsize = SizeofTriggerEvent(event);
Size needed = eventsize + sizeof(AfterTriggerSharedData);
AfterTriggerEventChunk *chunk;
AfterTriggerShared newshared;
AfterTriggerEvent newevent;
/*
* If empty list or not enough room in the tail chunk, make a new chunk.
* We assume here that a new shared record will always be needed.
*/
chunk = events->tail;
if (chunk == NULL ||
chunk->endfree - chunk->freeptr < needed)
{
Size chunksize;
/* Create event context if we didn't already */
if (afterTriggers->event_cxt == NULL)
afterTriggers->event_cxt =
AllocSetContextCreate(TopTransactionContext,
"AfterTriggerEvents",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* Chunk size starts at 1KB and is allowed to increase up to 1MB.
* These numbers are fairly arbitrary, though there is a hard limit at
* AFTER_TRIGGER_OFFSET; else we couldn't link event records to their
* shared records using the available space in ate_flags. Another
* constraint is that if the chunk size gets too huge, the search loop
* below would get slow given a (not too common) usage pattern with
* many distinct event types in a chunk. Therefore, we double the
* preceding chunk size only if there weren't too many shared records
* in the preceding chunk; otherwise we halve it. This gives us some
* ability to adapt to the actual usage pattern of the current query
* while still having large chunk sizes in typical usage. All chunk
* sizes used should be MAXALIGN multiples, to ensure that the shared
* records will be aligned safely.
*/
#define MIN_CHUNK_SIZE 1024
#define MAX_CHUNK_SIZE (1024*1024)
#if MAX_CHUNK_SIZE > (AFTER_TRIGGER_OFFSET+1)
#error MAX_CHUNK_SIZE must not exceed AFTER_TRIGGER_OFFSET
#endif
if (chunk == NULL)
chunksize = MIN_CHUNK_SIZE;
else
{
/* preceding chunk size... */
chunksize = chunk->endptr - (char *) chunk;
/* check number of shared records in preceding chunk */
if ((chunk->endptr - chunk->endfree) <=
(100 * sizeof(AfterTriggerSharedData)))
chunksize *= 2; /* okay, double it */
else
chunksize /= 2; /* too many shared records */
chunksize = Min(chunksize, MAX_CHUNK_SIZE);
}
chunk = MemoryContextAlloc(afterTriggers->event_cxt, chunksize);
chunk->next = NULL;
chunk->freeptr = CHUNK_DATA_START(chunk);
chunk->endptr = chunk->endfree = (char *) chunk + chunksize;
Assert(chunk->endfree - chunk->freeptr >= needed);
if (events->head == NULL)
events->head = chunk;
else
events->tail->next = chunk;
events->tail = chunk;
/* events->tailfree is now out of sync, but we'll fix it below */
}
/*
* Try to locate a matching shared-data record already in the chunk. If
* none, make a new one.
*/
for (newshared = ((AfterTriggerShared) chunk->endptr) - 1;
(char *) newshared >= chunk->endfree;
newshared--)
{
if (newshared->ats_tgoid == evtshared->ats_tgoid &&
newshared->ats_relid == evtshared->ats_relid &&
newshared->ats_event == evtshared->ats_event &&
newshared->ats_firing_id == 0)
break;
}
if ((char *) newshared < chunk->endfree)
{
*newshared = *evtshared;
newshared->ats_firing_id = 0; /* just to be sure */
chunk->endfree = (char *) newshared;
}
/* Insert the data */
newevent = (AfterTriggerEvent) chunk->freeptr;
memcpy(newevent, event, eventsize);
/* ... and link the new event to its shared record */
newevent->ate_flags &= ~AFTER_TRIGGER_OFFSET;
newevent->ate_flags |= (char *) newshared - (char *) newevent;
chunk->freeptr += eventsize;
events->tailfree = chunk->freeptr;
}
| void AfterTriggerBeginQuery | ( | void | ) |
Definition at line 3716 of file trigger.c.
References Assert, AfterTriggerEventList::head, AfterTriggersData::maxquerydepth, NULL, AfterTriggersData::query_depth, AfterTriggersData::query_stack, repalloc(), AfterTriggerEventList::tail, and AfterTriggerEventList::tailfree.
Referenced by CopyFrom(), ExecuteTruncate(), and standard_ExecutorStart().
{
AfterTriggerEventList *events;
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* Increase the query stack depth */
afterTriggers->query_depth++;
/*
* Allocate more space in the query stack if needed.
*/
if (afterTriggers->query_depth >= afterTriggers->maxquerydepth)
{
/* repalloc will keep the stack in the same context */
int new_alloc = afterTriggers->maxquerydepth * 2;
afterTriggers->query_stack = (AfterTriggerEventList *)
repalloc(afterTriggers->query_stack,
new_alloc * sizeof(AfterTriggerEventList));
afterTriggers->maxquerydepth = new_alloc;
}
/* Initialize this query's list to empty */
events = &afterTriggers->query_stack[afterTriggers->query_depth];
events->head = NULL;
events->tail = NULL;
events->tailfree = NULL;
}
| void AfterTriggerBeginSubXact | ( | void | ) |
Definition at line 3905 of file trigger.c.
References DEFTRIG_INITALLOC, AfterTriggersData::depth_stack, AfterTriggersData::events, AfterTriggersData::events_stack, AfterTriggersData::firing_counter, AfterTriggersData::firing_stack, GetCurrentTransactionNestLevel(), AfterTriggersData::maxtransdepth, MemoryContextSwitchTo(), NULL, palloc(), AfterTriggersData::query_depth, repalloc(), AfterTriggersData::state_stack, and TopTransactionContext.
Referenced by StartSubTransaction().
{
int my_level = GetCurrentTransactionNestLevel();
/*
* Ignore call if the transaction is in aborted state. (Probably
* shouldn't happen?)
*/
if (afterTriggers == NULL)
return;
/*
* Allocate more space in the stacks if needed. (Note: because the
* minimum nest level of a subtransaction is 2, we waste the first couple
* entries of each array; not worth the notational effort to avoid it.)
*/
while (my_level >= afterTriggers->maxtransdepth)
{
if (afterTriggers->maxtransdepth == 0)
{
MemoryContext old_cxt;
old_cxt = MemoryContextSwitchTo(TopTransactionContext);
#define DEFTRIG_INITALLOC 8
afterTriggers->state_stack = (SetConstraintState *)
palloc(DEFTRIG_INITALLOC * sizeof(SetConstraintState));
afterTriggers->events_stack = (AfterTriggerEventList *)
palloc(DEFTRIG_INITALLOC * sizeof(AfterTriggerEventList));
afterTriggers->depth_stack = (int *)
palloc(DEFTRIG_INITALLOC * sizeof(int));
afterTriggers->firing_stack = (CommandId *)
palloc(DEFTRIG_INITALLOC * sizeof(CommandId));
afterTriggers->maxtransdepth = DEFTRIG_INITALLOC;
MemoryContextSwitchTo(old_cxt);
}
else
{
/* repalloc will keep the stacks in the same context */
int new_alloc = afterTriggers->maxtransdepth * 2;
afterTriggers->state_stack = (SetConstraintState *)
repalloc(afterTriggers->state_stack,
new_alloc * sizeof(SetConstraintState));
afterTriggers->events_stack = (AfterTriggerEventList *)
repalloc(afterTriggers->events_stack,
new_alloc * sizeof(AfterTriggerEventList));
afterTriggers->depth_stack = (int *)
repalloc(afterTriggers->depth_stack,
new_alloc * sizeof(int));
afterTriggers->firing_stack = (CommandId *)
repalloc(afterTriggers->firing_stack,
new_alloc * sizeof(CommandId));
afterTriggers->maxtransdepth = new_alloc;
}
}
/*
* Push the current information into the stack. The SET CONSTRAINTS state
* is not saved until/unless changed. Likewise, we don't make a
* per-subtransaction event context until needed.
*/
afterTriggers->state_stack[my_level] = NULL;
afterTriggers->events_stack[my_level] = afterTriggers->events;
afterTriggers->depth_stack[my_level] = afterTriggers->query_depth;
afterTriggers->firing_stack[my_level] = afterTriggers->firing_counter;
}
| void AfterTriggerBeginXact | ( | void | ) |
Definition at line 3670 of file trigger.c.
References Assert, AfterTriggersData::depth_stack, AfterTriggersData::event_cxt, AfterTriggersData::events, AfterTriggersData::events_stack, AfterTriggersData::firing_counter, AfterTriggersData::firing_stack, AfterTriggerEventList::head, AfterTriggersData::maxquerydepth, AfterTriggersData::maxtransdepth, MemoryContextAlloc(), NULL, AfterTriggersData::query_depth, AfterTriggersData::query_stack, SetConstraintStateCreate(), AfterTriggersData::state, AfterTriggersData::state_stack, AfterTriggerEventList::tail, AfterTriggerEventList::tailfree, and TopTransactionContext.
Referenced by StartTransaction().
{
Assert(afterTriggers == NULL);
/*
* Build empty after-trigger state structure
*/
afterTriggers = (AfterTriggers)
MemoryContextAlloc(TopTransactionContext,
sizeof(AfterTriggersData));
afterTriggers->firing_counter = (CommandId) 1; /* mustn't be 0 */
afterTriggers->state = SetConstraintStateCreate(8);
afterTriggers->events.head = NULL;
afterTriggers->events.tail = NULL;
afterTriggers->events.tailfree = NULL;
afterTriggers->query_depth = -1;
/* We initialize the query stack to a reasonable size */
afterTriggers->query_stack = (AfterTriggerEventList *)
MemoryContextAlloc(TopTransactionContext,
8 * sizeof(AfterTriggerEventList));
afterTriggers->maxquerydepth = 8;
/* Context for events is created only when needed */
afterTriggers->event_cxt = NULL;
/* Subtransaction stack is empty until/unless needed */
afterTriggers->state_stack = NULL;
afterTriggers->events_stack = NULL;
afterTriggers->depth_stack = NULL;
afterTriggers->firing_stack = NULL;
afterTriggers->maxtransdepth = 0;
}
| static bool afterTriggerCheckState | ( | AfterTriggerShared | evtshared | ) | [static] |
Definition at line 3108 of file trigger.c.
References AFTER_TRIGGER_DEFERRABLE, AFTER_TRIGGER_INITDEFERRED, SetConstraintStateData::all_isdeferred, SetConstraintStateData::all_isset, AfterTriggerSharedData::ats_event, AfterTriggerSharedData::ats_tgoid, SetConstraintStateData::numstates, SetConstraintTriggerData::sct_tgisdeferred, SetConstraintTriggerData::sct_tgoid, AfterTriggersData::state, and SetConstraintStateData::trigstates.
Referenced by afterTriggerMarkEvents().
{
Oid tgoid = evtshared->ats_tgoid;
SetConstraintState state = afterTriggers->state;
int i;
/*
* For not-deferrable triggers (i.e. normal AFTER ROW triggers and
* constraints declared NOT DEFERRABLE), the state is always false.
*/
if ((evtshared->ats_event & AFTER_TRIGGER_DEFERRABLE) == 0)
return false;
/*
* Check if SET CONSTRAINTS has been executed for this specific trigger.
*/
for (i = 0; i < state->numstates; i++)
{
if (state->trigstates[i].sct_tgoid == tgoid)
return state->trigstates[i].sct_tgisdeferred;
}
/*
* Check if SET CONSTRAINTS ALL has been executed; if so use that.
*/
if (state->all_isset)
return state->all_isdeferred;
/*
* Otherwise return the default state for the trigger.
*/
return ((evtshared->ats_event & AFTER_TRIGGER_INITDEFERRED) != 0);
}
| void AfterTriggerEndQuery | ( | EState * | estate | ) |
Definition at line 3761 of file trigger.c.
References afterTriggerFreeEventList(), afterTriggerInvokeEvents(), afterTriggerMarkEvents(), Assert, AfterTriggersData::events, AfterTriggersData::firing_counter, NULL, AfterTriggersData::query_depth, and AfterTriggersData::query_stack.
Referenced by CopyFrom(), ExecuteTruncate(), and standard_ExecutorFinish().
{
AfterTriggerEventList *events;
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* Must be inside a query, too */
Assert(afterTriggers->query_depth >= 0);
/*
* Process all immediate-mode triggers queued by the query, and move the
* deferred ones to the main list of deferred events.
*
* Notice that we decide which ones will be fired, and put the deferred
* ones on the main list, before anything is actually fired. This ensures
* reasonably sane behavior if a trigger function does SET CONSTRAINTS ...
* IMMEDIATE: all events we have decided to defer will be available for it
* to fire.
*
* We loop in case a trigger queues more events at the same query level
* (is that even possible?). Be careful here: firing a trigger could
* result in query_stack being repalloc'd, so we can't save its address
* across afterTriggerInvokeEvents calls.
*
* If we find no firable events, we don't have to increment
* firing_counter.
*/
for (;;)
{
events = &afterTriggers->query_stack[afterTriggers->query_depth];
if (afterTriggerMarkEvents(events, &afterTriggers->events, true))
{
CommandId firing_id = afterTriggers->firing_counter++;
/* OK to delete the immediate events after processing them */
if (afterTriggerInvokeEvents(events, firing_id, estate, true))
break; /* all fired */
}
else
break;
}
/* Release query-local storage for events */
afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]);
afterTriggers->query_depth--;
}
| void AfterTriggerEndSubXact | ( | bool | isCommit | ) |
Definition at line 3980 of file trigger.c.
References AFTER_TRIGGER_DONE, AFTER_TRIGGER_IN_PROGRESS, afterTriggerFreeEventList(), afterTriggerRestoreEventList(), Assert, AfterTriggerEventData::ate_flags, AfterTriggerSharedData::ats_firing_id, AfterTriggersData::depth_stack, AfterTriggersData::events, AfterTriggersData::events_stack, AfterTriggersData::firing_stack, for_each_event_chunk, GetCurrentTransactionNestLevel(), GetTriggerSharedData, AfterTriggersData::maxtransdepth, NULL, pfree(), AfterTriggersData::query_depth, AfterTriggersData::query_stack, AfterTriggersData::state, and AfterTriggersData::state_stack.
Referenced by AbortSubTransaction(), and CommitSubTransaction().
{
int my_level = GetCurrentTransactionNestLevel();
SetConstraintState state;
AfterTriggerEvent event;
AfterTriggerEventChunk *chunk;
CommandId subxact_firing_id;
/*
* Ignore call if the transaction is in aborted state. (Probably
* unneeded)
*/
if (afterTriggers == NULL)
return;
/*
* Pop the prior state if needed.
*/
if (isCommit)
{
Assert(my_level < afterTriggers->maxtransdepth);
/* If we saved a prior state, we don't need it anymore */
state = afterTriggers->state_stack[my_level];
if (state != NULL)
pfree(state);
/* this avoids double pfree if error later: */
afterTriggers->state_stack[my_level] = NULL;
Assert(afterTriggers->query_depth ==
afterTriggers->depth_stack[my_level]);
}
else
{
/*
* Aborting. It is possible subxact start failed before calling
* AfterTriggerBeginSubXact, in which case we mustn't risk touching
* stack levels that aren't there.
*/
if (my_level >= afterTriggers->maxtransdepth)
return;
/*
* Release any event lists from queries being aborted, and restore
* query_depth to its pre-subxact value.
*/
while (afterTriggers->query_depth > afterTriggers->depth_stack[my_level])
{
afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]);
afterTriggers->query_depth--;
}
Assert(afterTriggers->query_depth ==
afterTriggers->depth_stack[my_level]);
/*
* Restore the global deferred-event list to its former length,
* discarding any events queued by the subxact.
*/
afterTriggerRestoreEventList(&afterTriggers->events,
&afterTriggers->events_stack[my_level]);
/*
* Restore the trigger state. If the saved state is NULL, then this
* subxact didn't save it, so it doesn't need restoring.
*/
state = afterTriggers->state_stack[my_level];
if (state != NULL)
{
pfree(afterTriggers->state);
afterTriggers->state = state;
}
/* this avoids double pfree if error later: */
afterTriggers->state_stack[my_level] = NULL;
/*
* Scan for any remaining deferred events that were marked DONE or IN
* PROGRESS by this subxact or a child, and un-mark them. We can
* recognize such events because they have a firing ID greater than or
* equal to the firing_counter value we saved at subtransaction start.
* (This essentially assumes that the current subxact includes all
* subxacts started after it.)
*/
subxact_firing_id = afterTriggers->firing_stack[my_level];
for_each_event_chunk(event, chunk, afterTriggers->events)
{
AfterTriggerShared evtshared = GetTriggerSharedData(event);
if (event->ate_flags &
(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))
{
if (evtshared->ats_firing_id >= subxact_firing_id)
event->ate_flags &=
~(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS);
}
}
}
}
| void AfterTriggerEndXact | ( | bool | isCommit | ) |
Definition at line 3882 of file trigger.c.
References AfterTriggersData::event_cxt, and MemoryContextDelete().
Referenced by AbortTransaction(), CommitTransaction(), and PrepareTransaction().
{
/*
* Forget everything we know about AFTER triggers.
*
* Since all the info is in TopTransactionContext or children thereof, we
* don't really need to do anything to reclaim memory. However, the
* pending-events list could be large, and so it's useful to discard it as
* soon as possible --- especially if we are aborting because we ran out
* of memory for the list!
*/
if (afterTriggers && afterTriggers->event_cxt)
MemoryContextDelete(afterTriggers->event_cxt);
afterTriggers = NULL;
}
| static void AfterTriggerExecute | ( | AfterTriggerEvent | event, | |
| Relation | rel, | |||
| TriggerDesc * | trigdesc, | |||
| FmgrInfo * | finfo, | |||
| Instrumentation * | instr, | |||
| MemoryContext | per_tuple_context | |||
| ) | [static] |
Definition at line 3344 of file trigger.c.
References AFTER_TRIGGER_2CTIDS, AfterTriggerEventData::ate_ctid1, AfterTriggerEventData::ate_ctid2, AfterTriggerEventData::ate_flags, AfterTriggerSharedData::ats_event, AfterTriggerSharedData::ats_tgoid, elog, ERROR, ExecCallTriggerFunc(), GetTriggerSharedData, heap_fetch(), heap_freetuple(), InstrStartNode(), InstrStopNode(), InvalidBuffer, ItemPointerCopy, ItemPointerIsValid, MemoryContextReset(), NULL, TriggerDesc::numtriggers, ReleaseBuffer(), SnapshotAny, HeapTupleData::t_self, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgoid, TRIGGER_EVENT_OPMASK, TriggerDesc::triggers, and TriggerData::type.
Referenced by afterTriggerInvokeEvents().
{
AfterTriggerShared evtshared = GetTriggerSharedData(event);
Oid tgoid = evtshared->ats_tgoid;
TriggerData LocTriggerData;
HeapTupleData tuple1;
HeapTupleData tuple2;
HeapTuple rettuple;
Buffer buffer1 = InvalidBuffer;
Buffer buffer2 = InvalidBuffer;
int tgindx;
/*
* Locate trigger in trigdesc.
*/
LocTriggerData.tg_trigger = NULL;
for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++)
{
if (trigdesc->triggers[tgindx].tgoid == tgoid)
{
LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]);
break;
}
}
if (LocTriggerData.tg_trigger == NULL)
elog(ERROR, "could not find trigger %u", tgoid);
/*
* If doing EXPLAIN ANALYZE, start charging time to this trigger. We want
* to include time spent re-fetching tuples in the trigger cost.
*/
if (instr)
InstrStartNode(instr + tgindx);
/*
* Fetch the required tuple(s).
*/
if (ItemPointerIsValid(&(event->ate_ctid1)))
{
ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
LocTriggerData.tg_trigtuple = &tuple1;
LocTriggerData.tg_trigtuplebuf = buffer1;
}
else
{
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
}
/* don't touch ctid2 if not there */
if ((event->ate_flags & AFTER_TRIGGER_2CTIDS) &&
ItemPointerIsValid(&(event->ate_ctid2)))
{
ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
LocTriggerData.tg_newtuple = &tuple2;
LocTriggerData.tg_newtuplebuf = buffer2;
}
else
{
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
}
/*
* Setup the remaining trigger information
*/
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event =
evtshared->ats_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
LocTriggerData.tg_relation = rel;
MemoryContextReset(per_tuple_context);
/*
* Call the trigger and throw away any possibly returned updated tuple.
* (Don't let ExecCallTriggerFunc measure EXPLAIN time.)
*/
rettuple = ExecCallTriggerFunc(&LocTriggerData,
tgindx,
finfo,
NULL,
per_tuple_context);
if (rettuple != NULL && rettuple != &tuple1 && rettuple != &tuple2)
heap_freetuple(rettuple);
/*
* Release buffers
*/
if (buffer1 != InvalidBuffer)
ReleaseBuffer(buffer1);
if (buffer2 != InvalidBuffer)
ReleaseBuffer(buffer2);
/*
* If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count
* one "tuple returned" (really the number of firings).
*/
if (instr)
InstrStopNode(instr + tgindx, 1);
}
| void AfterTriggerFireDeferred | ( | void | ) |
Definition at line 3823 of file trigger.c.
References afterTriggerInvokeEvents(), afterTriggerMarkEvents(), Assert, AfterTriggersData::events, AfterTriggersData::firing_counter, GetTransactionSnapshot(), AfterTriggerEventList::head, NULL, PopActiveSnapshot(), PushActiveSnapshot(), and AfterTriggersData::query_depth.
Referenced by CommitTransaction(), and PrepareTransaction().
{
AfterTriggerEventList *events;
bool snap_pushed = false;
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* ... but not inside a query */
Assert(afterTriggers->query_depth == -1);
/*
* If there are any triggers to fire, make sure we have set a snapshot for
* them to use. (Since PortalRunUtility doesn't set a snap for COMMIT, we
* can't assume ActiveSnapshot is valid on entry.)
*/
events = &afterTriggers->events;
if (events->head != NULL)
{
PushActiveSnapshot(GetTransactionSnapshot());
snap_pushed = true;
}
/*
* Run all the remaining triggers. Loop until they are all gone, in case
* some trigger queues more for us to do.
*/
while (afterTriggerMarkEvents(events, NULL, false))
{
CommandId firing_id = afterTriggers->firing_counter++;
if (afterTriggerInvokeEvents(events, firing_id, NULL, true))
break; /* all fired */
}
/*
* We don't bother freeing the event list, since it will go away anyway
* (and more efficiently than via pfree) in AfterTriggerEndXact.
*/
if (snap_pushed)
PopActiveSnapshot();
}
| static void afterTriggerFreeEventList | ( | AfterTriggerEventList * | events | ) | [static] |
Definition at line 3268 of file trigger.c.
References AfterTriggerEventList::head, AfterTriggerEventChunk::next, NULL, pfree(), AfterTriggerEventList::tail, and AfterTriggerEventList::tailfree.
Referenced by AfterTriggerEndQuery(), AfterTriggerEndSubXact(), and afterTriggerRestoreEventList().
{
AfterTriggerEventChunk *chunk;
AfterTriggerEventChunk *next_chunk;
for (chunk = events->head; chunk != NULL; chunk = next_chunk)
{
next_chunk = chunk->next;
pfree(chunk);
}
events->head = NULL;
events->tail = NULL;
events->tailfree = NULL;
}
| static bool afterTriggerInvokeEvents | ( | AfterTriggerEventList * | events, | |
| CommandId | firing_id, | |||
| EState * | estate, | |||
| bool | delete_ok | |||
| ) | [static] |
Definition at line 3540 of file trigger.c.
References AFTER_TRIGGER_DONE, AFTER_TRIGGER_IN_PROGRESS, AfterTriggerExecute(), ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, ALLOCSET_DEFAULT_MINSIZE, AllocSetContextCreate(), AfterTriggerEventData::ate_flags, AfterTriggerSharedData::ats_firing_id, AfterTriggerSharedData::ats_relid, CHUNK_DATA_START, CreateExecutorState(), CurrentMemoryContext, elog, AfterTriggerEventChunk::endfree, AfterTriggerEventChunk::endptr, ERROR, EState::es_trig_target_relations, ExecCloseIndices(), ExecGetTriggerResultRel(), for_each_chunk, for_each_event, FreeExecutorState(), AfterTriggerEventChunk::freeptr, GetTriggerSharedData, heap_close, lfirst, MemoryContextDelete(), NoLock, NULL, RelationGetRelid, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, AfterTriggerEventList::tail, and AfterTriggerEventList::tailfree.
Referenced by AfterTriggerEndQuery(), AfterTriggerFireDeferred(), and AfterTriggerSetState().
{
bool all_fired = true;
AfterTriggerEventChunk *chunk;
MemoryContext per_tuple_context;
bool local_estate = false;
Relation rel = NULL;
TriggerDesc *trigdesc = NULL;
FmgrInfo *finfo = NULL;
Instrumentation *instr = NULL;
/* Make a local EState if need be */
if (estate == NULL)
{
estate = CreateExecutorState();
local_estate = true;
}
/* Make a per-tuple memory context for trigger function calls */
per_tuple_context =
AllocSetContextCreate(CurrentMemoryContext,
"AfterTriggerTupleContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
for_each_chunk(chunk, *events)
{
AfterTriggerEvent event;
bool all_fired_in_chunk = true;
for_each_event(event, chunk)
{
AfterTriggerShared evtshared = GetTriggerSharedData(event);
/*
* Is it one for me to fire?
*/
if ((event->ate_flags & AFTER_TRIGGER_IN_PROGRESS) &&
evtshared->ats_firing_id == firing_id)
{
/*
* So let's fire it... but first, find the correct relation if
* this is not the same relation as before.
*/
if (rel == NULL || RelationGetRelid(rel) != evtshared->ats_relid)
{
ResultRelInfo *rInfo;
rInfo = ExecGetTriggerResultRel(estate, evtshared->ats_relid);
rel = rInfo->ri_RelationDesc;
trigdesc = rInfo->ri_TrigDesc;
finfo = rInfo->ri_TrigFunctions;
instr = rInfo->ri_TrigInstrument;
if (trigdesc == NULL) /* should not happen */
elog(ERROR, "relation %u has no triggers",
evtshared->ats_relid);
}
/*
* Fire it. Note that the AFTER_TRIGGER_IN_PROGRESS flag is
* still set, so recursive examinations of the event list
* won't try to re-fire it.
*/
AfterTriggerExecute(event, rel, trigdesc, finfo, instr,
per_tuple_context);
/*
* Mark the event as done.
*/
event->ate_flags &= ~AFTER_TRIGGER_IN_PROGRESS;
event->ate_flags |= AFTER_TRIGGER_DONE;
}
else if (!(event->ate_flags & AFTER_TRIGGER_DONE))
{
/* something remains to be done */
all_fired = all_fired_in_chunk = false;
}
}
/* Clear the chunk if delete_ok and nothing left of interest */
if (delete_ok && all_fired_in_chunk)
{
chunk->freeptr = CHUNK_DATA_START(chunk);
chunk->endfree = chunk->endptr;
/*
* If it's last chunk, must sync event list's tailfree too. Note
* that delete_ok must NOT be passed as true if there could be
* stacked AfterTriggerEventList values pointing at this event
* list, since we'd fail to fix their copies of tailfree.
*/
if (chunk == events->tail)
events->tailfree = chunk->freeptr;
}
}
/* Release working resources */
MemoryContextDelete(per_tuple_context);
if (local_estate)
{
ListCell *l;
foreach(l, estate->es_trig_target_relations)
{
ResultRelInfo *resultRelInfo = (ResultRelInfo *) lfirst(l);
/* Close indices and then the relation itself */
ExecCloseIndices(resultRelInfo);
heap_close(resultRelInfo->ri_RelationDesc, NoLock);
}
FreeExecutorState(estate);
}
return all_fired;
}
| static bool afterTriggerMarkEvents | ( | AfterTriggerEventList * | events, | |
| AfterTriggerEventList * | move_list, | |||
| bool | immediate_only | |||
| ) | [static] |
Definition at line 3468 of file trigger.c.
References AFTER_TRIGGER_DONE, AFTER_TRIGGER_IN_PROGRESS, afterTriggerAddEvent(), afterTriggerCheckState(), AfterTriggerEventData::ate_flags, AfterTriggerSharedData::ats_firing_id, AfterTriggersData::firing_counter, for_each_event_chunk, GetTriggerSharedData, and NULL.
Referenced by AfterTriggerEndQuery(), AfterTriggerFireDeferred(), and AfterTriggerSetState().
{
bool found = false;
AfterTriggerEvent event;
AfterTriggerEventChunk *chunk;
for_each_event_chunk(event, chunk, *events)
{
AfterTriggerShared evtshared = GetTriggerSharedData(event);
bool defer_it = false;
if (!(event->ate_flags &
(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS)))
{
/*
* This trigger hasn't been called or scheduled yet. Check if we
* should call it now.
*/
if (immediate_only && afterTriggerCheckState(evtshared))
{
defer_it = true;
}
else
{
/*
* Mark it as to be fired in this firing cycle.
*/
evtshared->ats_firing_id = afterTriggers->firing_counter;
event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS;
found = true;
}
}
/*
* If it's deferred, move it to move_list, if requested.
*/
if (defer_it && move_list != NULL)
{
/* add it to move_list */
afterTriggerAddEvent(move_list, event, evtshared);
/* mark original copy "done" so we don't do it again */
event->ate_flags |= AFTER_TRIGGER_DONE;
}
}
return found;
}
Definition at line 4451 of file trigger.c.
References AFTER_TRIGGER_DONE, AfterTriggerEventData::ate_flags, AfterTriggerSharedData::ats_relid, AfterTriggersData::events, for_each_event_chunk, GetTriggerSharedData, NULL, AfterTriggersData::query_depth, and AfterTriggersData::query_stack.
Referenced by CheckTableNotInUse().
{
AfterTriggerEvent event;
AfterTriggerEventChunk *chunk;
int depth;
/* No-op if we aren't in a transaction. (Shouldn't happen?) */
if (afterTriggers == NULL)
return false;
/* Scan queued events */
for_each_event_chunk(event, chunk, afterTriggers->events)
{
AfterTriggerShared evtshared = GetTriggerSharedData(event);
/*
* We can ignore completed events. (Even if a DONE flag is rolled
* back by subxact abort, it's OK because the effects of the TRUNCATE
* or whatever must get rolled back too.)
*/
if (event->ate_flags & AFTER_TRIGGER_DONE)
continue;
if (evtshared->ats_relid == relid)
return true;
}
/*
* Also scan events queued by incomplete queries. This could only matter
* if TRUNCATE/etc is executed by a function or trigger within an updating
* query on the same relation, which is pretty perverse, but let's check.
*/
for (depth = 0; depth <= afterTriggers->query_depth; depth++)
{
for_each_event_chunk(event, chunk, afterTriggers->query_stack[depth])
{
AfterTriggerShared evtshared = GetTriggerSharedData(event);
if (event->ate_flags & AFTER_TRIGGER_DONE)
continue;
if (evtshared->ats_relid == relid)
return true;
}
}
return false;
}
| static void afterTriggerRestoreEventList | ( | AfterTriggerEventList * | events, | |
| const AfterTriggerEventList * | old_events | |||
| ) | [static] |
Definition at line 3291 of file trigger.c.
References afterTriggerFreeEventList(), AfterTriggerEventChunk::freeptr, AfterTriggerEventChunk::next, NULL, pfree(), AfterTriggerEventList::tail, and AfterTriggerEventList::tailfree.
Referenced by AfterTriggerEndSubXact().
{
AfterTriggerEventChunk *chunk;
AfterTriggerEventChunk *next_chunk;
if (old_events->tail == NULL)
{
/* restoring to a completely empty state, so free everything */
afterTriggerFreeEventList(events);
}
else
{
*events = *old_events;
/* free any chunks after the last one we want to keep */
for (chunk = events->tail->next; chunk != NULL; chunk = next_chunk)
{
next_chunk = chunk->next;
pfree(chunk);
}
/* and clean up the tail chunk to be the right length */
events->tail->next = NULL;
events->tail->freeptr = events->tailfree;
/*
* We don't make any effort to remove now-unused shared data records.
* They might still be useful, anyway.
*/
}
}
| static void AfterTriggerSaveEvent | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| int | event, | |||
| bool | row_trigger, | |||
| HeapTuple | oldtup, | |||
| HeapTuple | newtup, | |||
| List * | recheckIndexes, | |||
| Bitmapset * | modifiedCols | |||
| ) | [static] |
Definition at line 4513 of file trigger.c.
References AFTER_TRIGGER_DEFERRABLE, AFTER_TRIGGER_INITDEFERRED, afterTriggerAddEvent(), Assert, AfterTriggerEventData::ate_ctid1, AfterTriggerEventData::ate_ctid2, AfterTriggerEventData::ate_flags, AfterTriggerSharedData::ats_event, AfterTriggerSharedData::ats_firing_id, AfterTriggerSharedData::ats_relid, AfterTriggerSharedData::ats_tgoid, elog, ERROR, ItemPointerCopy, ItemPointerSetInvalid, list_member_oid(), NULL, TriggerDesc::numtriggers, AfterTriggersData::query_depth, AfterTriggersData::query_stack, RelationGetRelid, RI_FKey_fk_upd_check_required(), RI_FKey_pk_upd_check_required(), RI_FKey_trigger_type(), ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, RI_TRIGGER_FK, RI_TRIGGER_NONE, RI_TRIGGER_PK, HeapTupleData::t_self, Trigger::tgconstrindid, Trigger::tgdeferrable, Trigger::tgfoid, Trigger::tginitdeferred, Trigger::tgoid, Trigger::tgtype, TRIGGER_EVENT_DELETE, TRIGGER_EVENT_INSERT, TRIGGER_EVENT_ROW, TRIGGER_EVENT_TRUNCATE, TRIGGER_EVENT_UPDATE, TRIGGER_FIRED_BY_UPDATE, TRIGGER_TYPE_AFTER, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TriggerEnabled(), and TriggerDesc::triggers.
Referenced by ExecARDeleteTriggers(), ExecARInsertTriggers(), ExecARUpdateTriggers(), ExecASDeleteTriggers(), ExecASInsertTriggers(), ExecASTruncateTriggers(), and ExecASUpdateTriggers().
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
AfterTriggerEventData new_event;
AfterTriggerSharedData new_shared;
int tgtype_event;
int tgtype_level;
int i;
/*
* Check state. We use normal tests not Asserts because it is possible to
* reach here in the wrong state given misconfigured RI triggers, in
* particular deferring a cascade action trigger.
*/
if (afterTriggers == NULL)
elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction");
if (afterTriggers->query_depth < 0)
elog(ERROR, "AfterTriggerSaveEvent() called outside of query");
/*
* Validate the event code and collect the associated tuple CTIDs.
*
* The event code will be used both as a bitmask and an array offset, so
* validation is important to make sure we don't walk off the edge of our
* arrays.
*/
new_event.ate_flags = 0;
switch (event)
{
case TRIGGER_EVENT_INSERT:
tgtype_event = TRIGGER_TYPE_INSERT;
if (row_trigger)
{
Assert(oldtup == NULL);
Assert(newtup != NULL);
ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_DELETE:
tgtype_event = TRIGGER_TYPE_DELETE;
if (row_trigger)
{
Assert(oldtup != NULL);
Assert(newtup == NULL);
ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_UPDATE:
tgtype_event = TRIGGER_TYPE_UPDATE;
if (row_trigger)
{
Assert(oldtup != NULL);
Assert(newtup != NULL);
ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid2));
new_event.ate_flags |= AFTER_TRIGGER_2CTIDS;
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_TRUNCATE:
tgtype_event = TRIGGER_TYPE_TRUNCATE;
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
break;
default:
elog(ERROR, "invalid after-trigger event code: %d", event);
tgtype_event = 0; /* keep compiler quiet */
break;
}
tgtype_level = (row_trigger ? TRIGGER_TYPE_ROW : TRIGGER_TYPE_STATEMENT);
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
tgtype_level,
TRIGGER_TYPE_AFTER,
tgtype_event))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, event,
modifiedCols, oldtup, newtup))
continue;
/*
* If the trigger is a foreign key enforcement trigger, there are
* certain cases where we can skip queueing the event because we can
* tell by inspection that the FK constraint will still pass.
*/
if (TRIGGER_FIRED_BY_UPDATE(event))
{
switch (RI_FKey_trigger_type(trigger->tgfoid))
{
case RI_TRIGGER_PK:
/* Update on trigger's PK table */
if (!RI_FKey_pk_upd_check_required(trigger, rel,
oldtup, newtup))
{
/* skip queuing this event */
continue;
}
break;
case RI_TRIGGER_FK:
/* Update on trigger's FK table */
if (!RI_FKey_fk_upd_check_required(trigger, rel,
oldtup, newtup))
{
/* skip queuing this event */
continue;
}
break;
case RI_TRIGGER_NONE:
/* Not an FK trigger */
break;
}
}
/*
* If the trigger is a deferred unique constraint check trigger, only
* queue it if the unique constraint was potentially violated, which
* we know from index insertion time.
*/
if (trigger->tgfoid == F_UNIQUE_KEY_RECHECK)
{
if (!list_member_oid(recheckIndexes, trigger->tgconstrindid))
continue; /* Uniqueness definitely not violated */
}
/*
* Fill in event structure and add it to the current query's queue.
*/
new_shared.ats_event =
(event & TRIGGER_EVENT_OPMASK) |
(row_trigger ? TRIGGER_EVENT_ROW : 0) |
(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
new_shared.ats_tgoid = trigger->tgoid;
new_shared.ats_relid = RelationGetRelid(rel);
new_shared.ats_firing_id = 0;
afterTriggerAddEvent(&afterTriggers->query_stack[afterTriggers->query_depth],
&new_event, &new_shared);
}
}
| void AfterTriggerSetState | ( | ConstraintsSetStmt * | stmt | ) |
Definition at line 4155 of file trigger.c.
References AccessShareLock, afterTriggerInvokeEvents(), afterTriggerMarkEvents(), SetConstraintStateData::all_isdeferred, SetConstraintStateData::all_isset, Anum_pg_constraint_conname, Anum_pg_constraint_connamespace, Anum_pg_trigger_tgconstraint, BTEqualStrategyNumber, RangeVar::catalogname, ConstraintNameNspIndexId, ConstraintRelationId, ConstraintsSetStmt::constraints, CStringGetDatum, ConstraintsSetStmt::deferred, elog, ereport, errcode(), errmsg(), ERROR, AfterTriggersData::events, fetch_search_path(), AfterTriggersData::firing_counter, get_database_name(), GetCurrentTransactionNestLevel(), GETSTRUCT, GetTransactionSnapshot(), heap_close, heap_open(), HeapTupleGetOid, HeapTupleIsValid, IsSubTransaction(), lappend_oid(), lfirst, lfirst_oid, list_free(), list_make1_oid, LookupExplicitNamespace(), MyDatabaseId, NIL, NULL, SetConstraintStateData::numstates, ObjectIdGetDatum, PopActiveSnapshot(), PushActiveSnapshot(), RangeVar::relname, ScanKeyInit(), RangeVar::schemaname, SetConstraintTriggerData::sct_tgisdeferred, SetConstraintTriggerData::sct_tgoid, SetConstraintStateAddItem(), SetConstraintStateCopy(), SnapshotNow, AfterTriggersData::state, AfterTriggersData::state_stack, systable_beginscan(), systable_endscan(), systable_getnext(), TriggerConstraintIndexId, TriggerRelationId, and SetConstraintStateData::trigstates.
Referenced by standard_ProcessUtility().
{
int my_level = GetCurrentTransactionNestLevel();
/*
* Ignore call if we aren't in a transaction. (Shouldn't happen?)
*/
if (afterTriggers == NULL)
return;
/*
* If in a subtransaction, and we didn't save the current state already,
* save it so it can be restored if the subtransaction aborts.
*/
if (my_level > 1 &&
afterTriggers->state_stack[my_level] == NULL)
{
afterTriggers->state_stack[my_level] =
SetConstraintStateCopy(afterTriggers->state);
}
/*
* Handle SET CONSTRAINTS ALL ...
*/
if (stmt->constraints == NIL)
{
/*
* Forget any previous SET CONSTRAINTS commands in this transaction.
*/
afterTriggers->state->numstates = 0;
/*
* Set the per-transaction ALL state to known.
*/
afterTriggers->state->all_isset = true;
afterTriggers->state->all_isdeferred = stmt->deferred;
}
else
{
Relation conrel;
Relation tgrel;
List *conoidlist = NIL;
List *tgoidlist = NIL;
ListCell *lc;
/*
* Handle SET CONSTRAINTS constraint-name [, ...]
*
* First, identify all the named constraints and make a list of their
* OIDs. Since, unlike the SQL spec, we allow multiple constraints of
* the same name within a schema, the specifications are not
* necessarily unique. Our strategy is to target all matching
* constraints within the first search-path schema that has any
* matches, but disregard matches in schemas beyond the first match.
* (This is a bit odd but it's the historical behavior.)
*/
conrel = heap_open(ConstraintRelationId, AccessShareLock);
foreach(lc, stmt->constraints)
{
RangeVar *constraint = lfirst(lc);
bool found;
List *namespacelist;
ListCell *nslc;
if (constraint->catalogname)
{
if (strcmp(constraint->catalogname, get_database_name(MyDatabaseId)) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
constraint->catalogname, constraint->schemaname,
constraint->relname)));
}
/*
* If we're given the schema name with the constraint, look only
* in that schema. If given a bare constraint name, use the
* search path to find the first matching constraint.
*/
if (constraint->schemaname)
{
Oid namespaceId = LookupExplicitNamespace(constraint->schemaname,
false);
namespacelist = list_make1_oid(namespaceId);
}
else
{
namespacelist = fetch_search_path(true);
}
found = false;
foreach(nslc, namespacelist)
{
Oid namespaceId = lfirst_oid(nslc);
SysScanDesc conscan;
ScanKeyData skey[2];
HeapTuple tup;
ScanKeyInit(&skey[0],
Anum_pg_constraint_conname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(constraint->relname));
ScanKeyInit(&skey[1],
Anum_pg_constraint_connamespace,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(namespaceId));
conscan = systable_beginscan(conrel, ConstraintNameNspIndexId,
true, SnapshotNow, 2, skey);
while (HeapTupleIsValid(tup = systable_getnext(conscan)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
if (con->condeferrable)
conoidlist = lappend_oid(conoidlist,
HeapTupleGetOid(tup));
else if (stmt->deferred)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("constraint \"%s\" is not deferrable",
constraint->relname)));
found = true;
}
systable_endscan(conscan);
/*
* Once we've found a matching constraint we do not search
* later parts of the search path.
*/
if (found)
break;
}
list_free(namespacelist);
/*
* Not found ?
*/
if (!found)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("constraint \"%s\" does not exist",
constraint->relname)));
}
heap_close(conrel, AccessShareLock);
/*
* Now, locate the trigger(s) implementing each of these constraints,
* and make a list of their OIDs.
*/
tgrel = heap_open(TriggerRelationId, AccessShareLock);
foreach(lc, conoidlist)
{
Oid conoid = lfirst_oid(lc);
bool found;
ScanKeyData skey;
SysScanDesc tgscan;
HeapTuple htup;
found = false;
ScanKeyInit(&skey,
Anum_pg_trigger_tgconstraint,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(conoid));
tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
SnapshotNow, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
{
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
/*
* Silently skip triggers that are marked as non-deferrable in
* pg_trigger. This is not an error condition, since a
* deferrable RI constraint may have some non-deferrable
* actions.
*/
if (pg_trigger->tgdeferrable)
tgoidlist = lappend_oid(tgoidlist,
HeapTupleGetOid(htup));
found = true;
}
systable_endscan(tgscan);
/* Safety check: a deferrable constraint should have triggers */
if (!found)
elog(ERROR, "no triggers found for constraint with OID %u",
conoid);
}
heap_close(tgrel, AccessShareLock);
/*
* Now we can set the trigger states of individual triggers for this
* xact.
*/
foreach(lc, tgoidlist)
{
Oid tgoid = lfirst_oid(lc);
SetConstraintState state = afterTriggers->state;
bool found = false;
int i;
for (i = 0; i < state->numstates; i++)
{
if (state->trigstates[i].sct_tgoid == tgoid)
{
state->trigstates[i].sct_tgisdeferred = stmt->deferred;
found = true;
break;
}
}
if (!found)
{
afterTriggers->state =
SetConstraintStateAddItem(state, tgoid, stmt->deferred);
}
}
}
/*
* SQL99 requires that when a constraint is set to IMMEDIATE, any deferred
* checks against that constraint must be made when the SET CONSTRAINTS
* command is executed -- i.e. the effects of the SET CONSTRAINTS command
* apply retroactively. We've updated the constraints state, so scan the
* list of previously deferred events to fire any that have now become
* immediate.
*
* Obviously, if this was SET ... DEFERRED then it can't have converted
* any unfired events to immediate, so we need do nothing in that case.
*/
if (!stmt->deferred)
{
AfterTriggerEventList *events = &afterTriggers->events;
bool snapshot_set = false;
while (afterTriggerMarkEvents(events, NULL, true))
{
CommandId firing_id = afterTriggers->firing_counter++;
/*
* Make sure a snapshot has been established in case trigger
* functions need one. Note that we avoid setting a snapshot if
* we don't find at least one trigger that has to be fired now.
* This is so that BEGIN; SET CONSTRAINTS ...; SET TRANSACTION
* ISOLATION LEVEL SERIALIZABLE; ... works properly. (If we are
* at the start of a transaction it's not possible for any trigger
* events to be queued yet.)
*/
if (!snapshot_set)
{
PushActiveSnapshot(GetTransactionSnapshot());
snapshot_set = true;
}
/*
* We can delete fired events if we are at top transaction level,
* but we'd better not if inside a subtransaction, since the
* subtransaction could later get rolled back.
*/
if (afterTriggerInvokeEvents(events, firing_id, NULL,
!IsSubTransaction()))
break; /* all fired */
}
if (snapshot_set)
PopActiveSnapshot();
}
}
| static void ConvertTriggerToFK | ( | CreateTrigStmt * | stmt, | |
| Oid | funcoid | |||
| ) | [static] |
Definition at line 783 of file trigger.c.
References _, appendStringInfo(), appendStringInfoChar(), appendStringInfoString(), arg, OldTriggerInfo::args, CreateTrigStmt::args, buf, AlterTableStmt::cmds, Constraint::conname, CreateTrigStmt::constrrel, Constraint::contype, copyObject(), StringInfoData::data, AlterTableCmd::def, CreateTrigStmt::deferrable, Constraint::deferrable, elog, equal(), ereport, errdetail_internal(), errmsg(), ERROR, Constraint::fk_attrs, Constraint::fk_del_action, Constraint::fk_matchtype, Constraint::fk_upd_action, FKCONSTR_MATCH_FULL, OldTriggerInfo::funcoids, gettext_noop, i, CreateTrigStmt::initdeferred, Constraint::initdeferred, Constraint::initially_valid, initStringInfo(), InvalidOid, lappend(), lfirst, linitial, list_delete_ptr(), list_make1, Constraint::location, lsecond, lthird, makeNode, makeRangeVar(), MemoryContextSwitchTo(), None_Receiver, NOTICE, NULL, palloc0(), pfree(), Constraint::pk_attrs, Constraint::pktable, PROCESS_UTILITY_SUBCOMMAND, ProcessUtility(), quote_identifier(), CreateTrigStmt::relation, AlterTableStmt::relation, AlterTableStmt::relkind, Constraint::skip_validation, strVal, AlterTableCmd::subtype, and TopMemoryContext.
Referenced by CreateTrigger().
{
static List *info_list = NIL;
static const char *const funcdescr[3] = {
gettext_noop("Found referenced table's UPDATE trigger."),
gettext_noop("Found referenced table's DELETE trigger."),
gettext_noop("Found referencing table's trigger.")
};
char *constr_name;
char *fk_table_name;
char *pk_table_name;
char fk_matchtype = FKCONSTR_MATCH_SIMPLE;
List *fk_attrs = NIL;
List *pk_attrs = NIL;
StringInfoData buf;
int funcnum;
OldTriggerInfo *info = NULL;
ListCell *l;
int i;
/* Parse out the trigger arguments */
constr_name = strVal(linitial(stmt->args));
fk_table_name = strVal(lsecond(stmt->args));
pk_table_name = strVal(lthird(stmt->args));
i = 0;
foreach(l, stmt->args)
{
Value *arg = (Value *) lfirst(l);
i++;
if (i < 4) /* skip constraint and table names */
continue;
if (i == 4) /* handle match type */
{
if (strcmp(strVal(arg), "FULL") == 0)
fk_matchtype = FKCONSTR_MATCH_FULL;
else
fk_matchtype = FKCONSTR_MATCH_SIMPLE;
continue;
}
if (i % 2)
fk_attrs = lappend(fk_attrs, arg);
else
pk_attrs = lappend(pk_attrs, arg);
}
/* Prepare description of constraint for use in messages */
initStringInfo(&buf);
appendStringInfo(&buf, "FOREIGN KEY %s(",
quote_identifier(fk_table_name));
i = 0;
foreach(l, fk_attrs)
{
Value *arg = (Value *) lfirst(l);
if (i++ > 0)
appendStringInfoChar(&buf, ',');
appendStringInfoString(&buf, quote_identifier(strVal(arg)));
}
appendStringInfo(&buf, ") REFERENCES %s(",
quote_identifier(pk_table_name));
i = 0;
foreach(l, pk_attrs)
{
Value *arg = (Value *) lfirst(l);
if (i++ > 0)
appendStringInfoChar(&buf, ',');
appendStringInfoString(&buf, quote_identifier(strVal(arg)));
}
appendStringInfoChar(&buf, ')');
/* Identify class of trigger --- update, delete, or referencing-table */
switch (funcoid)
{
case F_RI_FKEY_CASCADE_UPD:
case F_RI_FKEY_RESTRICT_UPD:
case F_RI_FKEY_SETNULL_UPD:
case F_RI_FKEY_SETDEFAULT_UPD:
case F_RI_FKEY_NOACTION_UPD:
funcnum = 0;
break;
case F_RI_FKEY_CASCADE_DEL:
case F_RI_FKEY_RESTRICT_DEL:
case F_RI_FKEY_SETNULL_DEL:
case F_RI_FKEY_SETDEFAULT_DEL:
case F_RI_FKEY_NOACTION_DEL:
funcnum = 1;
break;
default:
funcnum = 2;
break;
}
/* See if we have a match to this trigger */
foreach(l, info_list)
{
info = (OldTriggerInfo *) lfirst(l);
if (info->funcoids[funcnum] == InvalidOid &&
equal(info->args, stmt->args))
{
info->funcoids[funcnum] = funcoid;
break;
}
}
if (l == NULL)
{
/* First trigger of set, so create a new list entry */
MemoryContext oldContext;
ereport(NOTICE,
(errmsg("ignoring incomplete trigger group for constraint \"%s\" %s",
constr_name, buf.data),
errdetail_internal("%s", _(funcdescr[funcnum]))));
oldContext = MemoryContextSwitchTo(TopMemoryContext);
info = (OldTriggerInfo *) palloc0(sizeof(OldTriggerInfo));
info->args = copyObject(stmt->args);
info->funcoids[funcnum] = funcoid;
info_list = lappend(info_list, info);
MemoryContextSwitchTo(oldContext);
}
else if (info->funcoids[0] == InvalidOid ||
info->funcoids[1] == InvalidOid ||
info->funcoids[2] == InvalidOid)
{
/* Second trigger of set */
ereport(NOTICE,
(errmsg("ignoring incomplete trigger group for constraint \"%s\" %s",
constr_name, buf.data),
errdetail_internal("%s", _(funcdescr[funcnum]))));
}
else
{
/* OK, we have a set, so make the FK constraint ALTER TABLE cmd */
AlterTableStmt *atstmt = makeNode(AlterTableStmt);
AlterTableCmd *atcmd = makeNode(AlterTableCmd);
Constraint *fkcon = makeNode(Constraint);
ereport(NOTICE,
(errmsg("converting trigger group into constraint \"%s\" %s",
constr_name, buf.data),
errdetail_internal("%s", _(funcdescr[funcnum]))));
fkcon->contype = CONSTR_FOREIGN;
fkcon->location = -1;
if (funcnum == 2)
{
/* This trigger is on the FK table */
atstmt->relation = stmt->relation;
if (stmt->constrrel)
fkcon->pktable = stmt->constrrel;
else
{
/* Work around ancient pg_dump bug that omitted constrrel */
fkcon->pktable = makeRangeVar(NULL, pk_table_name, -1);
}
}
else
{
/* This trigger is on the PK table */
fkcon->pktable = stmt->relation;
if (stmt->constrrel)
atstmt->relation = stmt->constrrel;
else
{
/* Work around ancient pg_dump bug that omitted constrrel */
atstmt->relation = makeRangeVar(NULL, fk_table_name, -1);
}
}
atstmt->cmds = list_make1(atcmd);
atstmt->relkind = OBJECT_TABLE;
atcmd->subtype = AT_AddConstraint;
atcmd->def = (Node *) fkcon;
if (strcmp(constr_name, "<unnamed>") == 0)
fkcon->conname = NULL;
else
fkcon->conname = constr_name;
fkcon->fk_attrs = fk_attrs;
fkcon->pk_attrs = pk_attrs;
fkcon->fk_matchtype = fk_matchtype;
switch (info->funcoids[0])
{
case F_RI_FKEY_NOACTION_UPD:
fkcon->fk_upd_action = FKCONSTR_ACTION_NOACTION;
break;
case F_RI_FKEY_CASCADE_UPD:
fkcon->fk_upd_action = FKCONSTR_ACTION_CASCADE;
break;
case F_RI_FKEY_RESTRICT_UPD:
fkcon->fk_upd_action = FKCONSTR_ACTION_RESTRICT;
break;
case F_RI_FKEY_SETNULL_UPD:
fkcon->fk_upd_action = FKCONSTR_ACTION_SETNULL;
break;
case F_RI_FKEY_SETDEFAULT_UPD:
fkcon->fk_upd_action = FKCONSTR_ACTION_SETDEFAULT;
break;
default:
/* can't get here because of earlier checks */
elog(ERROR, "confused about RI update function");
}
switch (info->funcoids[1])
{
case F_RI_FKEY_NOACTION_DEL:
fkcon->fk_del_action = FKCONSTR_ACTION_NOACTION;
break;
case F_RI_FKEY_CASCADE_DEL:
fkcon->fk_del_action = FKCONSTR_ACTION_CASCADE;
break;
case F_RI_FKEY_RESTRICT_DEL:
fkcon->fk_del_action = FKCONSTR_ACTION_RESTRICT;
break;
case F_RI_FKEY_SETNULL_DEL:
fkcon->fk_del_action = FKCONSTR_ACTION_SETNULL;
break;
case F_RI_FKEY_SETDEFAULT_DEL:
fkcon->fk_del_action = FKCONSTR_ACTION_SETDEFAULT;
break;
default:
/* can't get here because of earlier checks */
elog(ERROR, "confused about RI delete function");
}
fkcon->deferrable = stmt->deferrable;
fkcon->initdeferred = stmt->initdeferred;
fkcon->skip_validation = false;
fkcon->initially_valid = true;
/* ... and execute it */
ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
PROCESS_UTILITY_SUBCOMMAND, NULL,
None_Receiver, NULL);
/* Remove the matched item from the list */
info_list = list_delete_ptr(info_list, info);
pfree(info);
/* We leak the copied args ... not worth worrying about */
}
}
| TriggerDesc* CopyTriggerDesc | ( | TriggerDesc * | trigdesc | ) |
Definition at line 1634 of file trigger.c.
References i, NULL, TriggerDesc::numtriggers, palloc(), pstrdup(), Trigger::tgargs, Trigger::tgattr, Trigger::tgname, Trigger::tgnargs, Trigger::tgnattr, Trigger::tgqual, and TriggerDesc::triggers.
Referenced by InitResultRelInfo(), and RelationBuildTriggers().
{
TriggerDesc *newdesc;
Trigger *trigger;
int i;
if (trigdesc == NULL || trigdesc->numtriggers <= 0)
return NULL;
newdesc = (TriggerDesc *) palloc(sizeof(TriggerDesc));
memcpy(newdesc, trigdesc, sizeof(TriggerDesc));
trigger = (Trigger *) palloc(trigdesc->numtriggers * sizeof(Trigger));
memcpy(trigger, trigdesc->triggers,
trigdesc->numtriggers * sizeof(Trigger));
newdesc->triggers = trigger;
for (i = 0; i < trigdesc->numtriggers; i++)
{
trigger->tgname = pstrdup(trigger->tgname);
if (trigger->tgnattr > 0)
{
int16 *newattr;
newattr = (int16 *) palloc(trigger->tgnattr * sizeof(int16));
memcpy(newattr, trigger->tgattr,
trigger->tgnattr * sizeof(int16));
trigger->tgattr = newattr;
}
if (trigger->tgnargs > 0)
{
char **newargs;
int16 j;
newargs = (char **) palloc(trigger->tgnargs * sizeof(char *));
for (j = 0; j < trigger->tgnargs; j++)
newargs[j] = pstrdup(trigger->tgargs[j]);
trigger->tgargs = newargs;
}
if (trigger->tgqual)
trigger->tgqual = pstrdup(trigger->tgqual);
trigger++;
}
return newdesc;
}
| Oid CreateTrigger | ( | CreateTrigStmt * | stmt, | |
| const char * | queryString, | |||
| Oid | constraintOid, | |||
| Oid | indexOid, | |||
| bool | isInternal | |||
| ) |
Definition at line 120 of file trigger.c.
References AccessExclusiveLock, AccessShareLock, ACL_EXECUTE, ACL_KIND_CLASS, ACL_KIND_PROC, ACL_TRIGGER, aclcheck_error(), ACLCHECK_OK, addRangeTableEntryForRelation(), addRTEtoQuery(), allowSystemTableMods, Anum_pg_trigger_tgargs, Anum_pg_trigger_tgattr, Anum_pg_trigger_tgconstraint, Anum_pg_trigger_tgconstrindid, Anum_pg_trigger_tgconstrrelid, Anum_pg_trigger_tgdeferrable, Anum_pg_trigger_tgenabled, Anum_pg_trigger_tgfoid, Anum_pg_trigger_tginitdeferred, Anum_pg_trigger_tgisinternal, Anum_pg_trigger_tgname, Anum_pg_trigger_tgnargs, Anum_pg_trigger_tgqual, Anum_pg_trigger_tgrelid, Anum_pg_trigger_tgtype, CreateTrigStmt::args, Assert, assign_expr_collations(), attnameAttNum(), BoolGetDatum, BTEqualStrategyNumber, buildint2vector(), byteain(), CatalogUpdateIndexes(), CharGetDatum, ObjectAddress::classId, CreateTrigStmt::columns, CONSTRAINT_TRIGGER, CreateTrigStmt::constrrel, ConvertTriggerToFK(), copyObject(), CreateConstraintEntry(), CStringGetDatum, CStringGetTextDatum, DatumGetPointer, CreateTrigStmt::deferrable, DEPENDENCY_AUTO, DEPENDENCY_INTERNAL, DEPENDENCY_NORMAL, DirectFunctionCall1, elog, ereport, errcode(), errdetail(), errmsg(), ERROR, CreateTrigStmt::events, EXPR_KIND_TRIGGER_WHEN, free_parsestate(), CreateTrigStmt::funcname, get_func_rettype(), get_rel_name(), GetNewOid(), GETSTRUCT, GetUserId(), heap_close, heap_form_tuple(), heap_freetuple(), heap_open(), heap_openrv(), HeapTupleIsValid, HeapTupleSetOid, i, CreateTrigStmt::initdeferred, Int16GetDatum, InvalidAttrNumber, InvalidOid, InvokeObjectPostCreateHookArg, CreateTrigStmt::isconstraint, IsSystemRelation(), lfirst, list_length(), Var::location, LookupFuncName(), make_parsestate(), makeAlias(), name, namein(), NameListToString(), namestrcmp(), NIL, nodeToString(), NoLock, NULL, ObjectAddress::objectId, ObjectIdGetDatum, ObjectAddress::objectSubId, OidIsValid, OPAQUEOID, ParseState::p_rtable, ParseState::p_sourcetext, palloc(), parser_errposition(), pfree(), pg_class_aclcheck(), pg_proc_aclcheck(), PointerGetDatum, PRS2_NEW_VARNO, PRS2_OLD_VARNO, pull_var_clause(), PVC_REJECT_AGGREGATES, PVC_REJECT_PLACEHOLDERS, RangeVarGetRelid, RelationData::rd_att, RelationData::rd_rel, recordDependencyOn(), recordDependencyOnExpr(), CreateTrigStmt::relation, RelationGetNamespace, RelationGetRelationName, RelationGetRelid, RelationRelationId, RELKIND_RELATION, RELKIND_VIEW, RangeVar::relname, RELOID, RI_FKey_trigger_type(), RI_TRIGGER_NONE, CreateTrigStmt::row, RowExclusiveLock, ScanKeyInit(), SearchSysCacheCopy1, SetFunctionReturnType(), simple_heap_insert(), simple_heap_update(), SnapshotNow, snprintf(), strVal, systable_beginscan(), systable_endscan(), systable_getnext(), HeapTupleData::t_self, CreateTrigStmt::timing, transformWhereClause(), TRIGGER_CLEAR_TYPE, TRIGGER_FIRES_ON_ORIGIN, TRIGGER_FOR_BEFORE, TRIGGER_FOR_DELETE, TRIGGER_FOR_INSERT, TRIGGER_FOR_INSTEAD, TRIGGER_FOR_ROW, TRIGGER_FOR_TRUNCATE, TRIGGER_SETT_ROW, TRIGGER_TYPE_AFTER, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSTEAD, TRIGGEROID, TriggerRelationId, TriggerRelidNameIndexId, CreateTrigStmt::trigname, values, Var::varattno, Var::varno, WARNING, and CreateTrigStmt::whenClause.
Referenced by CreateFKCheckTrigger(), createForeignKeyTriggers(), index_constraint_create(), and ProcessUtilitySlow().
{
int16 tgtype;
int ncolumns;
int16 *columns;
int2vector *tgattr;
Node *whenClause;
List *whenRtable;
char *qual;
Datum values[Natts_pg_trigger];
bool nulls[Natts_pg_trigger];
Relation rel;
AclResult aclresult;
Relation tgrel;
SysScanDesc tgscan;
ScanKeyData key;
Relation pgrel;
HeapTuple tuple;
Oid fargtypes[1]; /* dummy */
Oid funcoid;
Oid funcrettype;
Oid trigoid;
char internaltrigname[NAMEDATALEN];
char *trigname;
Oid constrrelid = InvalidOid;
ObjectAddress myself,
referenced;
rel = heap_openrv(stmt->relation, AccessExclusiveLock);
/*
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
if (rel->rd_rel->relkind == RELKIND_RELATION)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
stmt->timing != TRIGGER_TYPE_AFTER)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
/*
* Views can have INSTEAD OF triggers (which we check below are
* row-level), or statement-level BEFORE/AFTER triggers.
*/
if (stmt->timing != TRIGGER_TYPE_INSTEAD && stmt->row)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a view",
RelationGetRelationName(rel)),
errdetail("Views cannot have row-level BEFORE or AFTER triggers.")));
/* Disallow TRUNCATE triggers on VIEWs */
if (TRIGGER_FOR_TRUNCATE(stmt->events))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a view",
RelationGetRelationName(rel)),
errdetail("Views cannot have TRUNCATE triggers.")));
}
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view",
RelationGetRelationName(rel))));
if (!allowSystemTableMods && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
if (stmt->isconstraint && stmt->constrrel != NULL)
{
/*
* We must take a lock on the target relation to protect against
* concurrent drop. It's not clear that AccessShareLock is strong
* enough, but we certainly need at least that much... otherwise, we
* might end up creating a pg_constraint entry referencing a
* nonexistent table.
*/
constrrelid = RangeVarGetRelid(stmt->constrrel, AccessShareLock, false);
}
/* permission checks */
if (!isInternal)
{
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_TRIGGER);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
if (OidIsValid(constrrelid))
{
aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
ACL_TRIGGER);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
get_rel_name(constrrelid));
}
}
/* Compute tgtype */
TRIGGER_CLEAR_TYPE(tgtype);
if (stmt->row)
TRIGGER_SETT_ROW(tgtype);
tgtype |= stmt->timing;
tgtype |= stmt->events;
/* Disallow ROW-level TRUNCATE triggers */
if (TRIGGER_FOR_ROW(tgtype) && TRIGGER_FOR_TRUNCATE(tgtype))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("TRUNCATE FOR EACH ROW triggers are not supported")));
/* INSTEAD triggers must be row-level, and can't have WHEN or columns */
if (TRIGGER_FOR_INSTEAD(tgtype))
{
if (!TRIGGER_FOR_ROW(tgtype))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSTEAD OF triggers must be FOR EACH ROW")));
if (stmt->whenClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSTEAD OF triggers cannot have WHEN conditions")));
if (stmt->columns != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("INSTEAD OF triggers cannot have column lists")));
}
/*
* Parse the WHEN clause, if any
*/
if (stmt->whenClause)
{
ParseState *pstate;
RangeTblEntry *rte;
List *varList;
ListCell *lc;
/* Set up a pstate to parse with */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
/*
* Set up RTEs for OLD and NEW references.
*
* 'OLD' must always have varno equal to 1 and 'NEW' equal to 2.
*/
rte = addRangeTableEntryForRelation(pstate, rel,
makeAlias("old", NIL),
false, false);
addRTEtoQuery(pstate, rte, false, true, true);
rte = addRangeTableEntryForRelation(pstate, rel,
makeAlias("new", NIL),
false, false);
addRTEtoQuery(pstate, rte, false, true, true);
/* Transform expression. Copy to be sure we don't modify original */
whenClause = transformWhereClause(pstate,
copyObject(stmt->whenClause),
EXPR_KIND_TRIGGER_WHEN,
"WHEN");
/* we have to fix its collations too */
assign_expr_collations(pstate, whenClause);
/*
* Check for disallowed references to OLD/NEW.
*
* NB: pull_var_clause is okay here only because we don't allow
* subselects in WHEN clauses; it would fail to examine the contents
* of subselects.
*/
varList = pull_var_clause(whenClause,
PVC_REJECT_AGGREGATES,
PVC_REJECT_PLACEHOLDERS);
foreach(lc, varList)
{
Var *var = (Var *) lfirst(lc);
switch (var->varno)
{
case PRS2_OLD_VARNO:
if (!TRIGGER_FOR_ROW(tgtype))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("statement trigger's WHEN condition cannot reference column values"),
parser_errposition(pstate, var->location)));
if (TRIGGER_FOR_INSERT(tgtype))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("INSERT trigger's WHEN condition cannot reference OLD values"),
parser_errposition(pstate, var->location)));
/* system columns are okay here */
break;
case PRS2_NEW_VARNO:
if (!TRIGGER_FOR_ROW(tgtype))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("statement trigger's WHEN condition cannot reference column values"),
parser_errposition(pstate, var->location)));
if (TRIGGER_FOR_DELETE(tgtype))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("DELETE trigger's WHEN condition cannot reference NEW values"),
parser_errposition(pstate, var->location)));
if (var->varattno < 0 && TRIGGER_FOR_BEFORE(tgtype))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"),
parser_errposition(pstate, var->location)));
break;
default:
/* can't happen without add_missing_from, so just elog */
elog(ERROR, "trigger WHEN condition cannot contain references to other relations");
break;
}
}
/* we'll need the rtable for recordDependencyOnExpr */
whenRtable = pstate->p_rtable;
qual = nodeToString(whenClause);
free_parsestate(pstate);
}
else
{
whenClause = NULL;
whenRtable = NIL;
qual = NULL;
}
/*
* Find and validate the trigger function.
*/
funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false);
if (!isInternal)
{
aclresult = pg_proc_aclcheck(funcoid, GetUserId(), ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC,
NameListToString(stmt->funcname));
}
funcrettype = get_func_rettype(funcoid);
if (funcrettype != TRIGGEROID)
{
/*
* We allow OPAQUE just so we can load old dump files. When we see a
* trigger function declared OPAQUE, change it to TRIGGER.
*/
if (funcrettype == OPAQUEOID)
{
ereport(WARNING,
(errmsg("changing return type of function %s from \"opaque\" to \"trigger\"",
NameListToString(stmt->funcname))));
SetFunctionReturnType(funcoid, TRIGGEROID);
}
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("function %s must return type \"trigger\"",
NameListToString(stmt->funcname))));
}
/*
* If the command is a user-entered CREATE CONSTRAINT TRIGGER command that
* references one of the built-in RI_FKey trigger functions, assume it is
* from a dump of a pre-7.3 foreign key constraint, and take steps to
* convert this legacy representation into a regular foreign key
* constraint. Ugly, but necessary for loading old dump files.
*/
if (stmt->isconstraint && !isInternal &&
list_length(stmt->args) >= 6 &&
(list_length(stmt->args) % 2) == 0 &&
RI_FKey_trigger_type(funcoid) != RI_TRIGGER_NONE)
{
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
ConvertTriggerToFK(stmt, funcoid);
return InvalidOid;
}
/*
* If it's a user-entered CREATE CONSTRAINT TRIGGER command, make a
* corresponding pg_constraint entry.
*/
if (stmt->isconstraint && !OidIsValid(constraintOid))
{
/* Internal callers should have made their own constraints */
Assert(!isInternal);
constraintOid = CreateConstraintEntry(stmt->trigname,
RelationGetNamespace(rel),
CONSTRAINT_TRIGGER,
stmt->deferrable,
stmt->initdeferred,
true,
RelationGetRelid(rel),
NULL, /* no conkey */
0,
InvalidOid, /* no domain */
InvalidOid, /* no index */
InvalidOid, /* no foreign key */
NULL,
NULL,
NULL,
NULL,
0,
' ',
' ',
' ',
NULL, /* no exclusion */
NULL, /* no check constraint */
NULL,
NULL,
true, /* islocal */
0, /* inhcount */
true, /* isnoinherit */
isInternal); /* is_internal */
}
/*
* Generate the trigger's OID now, so that we can use it in the name if
* needed.
*/
tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
trigoid = GetNewOid(tgrel);
/*
* If trigger is internally generated, modify the provided trigger name to
* ensure uniqueness by appending the trigger OID. (Callers will usually
* supply a simple constant trigger name in these cases.)
*/
if (isInternal)
{
snprintf(internaltrigname, sizeof(internaltrigname),
"%s_%u", stmt->trigname, trigoid);
trigname = internaltrigname;
}
else
{
/* user-defined trigger; use the specified trigger name as-is */
trigname = stmt->trigname;
}
/*
* Scan pg_trigger for existing triggers on relation. We do this only to
* give a nice error message if there's already a trigger of the same
* name. (The unique index on tgrelid/tgname would complain anyway.) We
* can skip this for internally generated triggers, since the name
* modification above should be sufficient.
*
* NOTE that this is cool only because we have AccessExclusiveLock on the
* relation, so the trigger set won't be changing underneath us.
*/
if (!isInternal)
{
ScanKeyInit(&key,
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(rel)));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
SnapshotNow, 1, &key);
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
{
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" already exists",
trigname, stmt->relation->relname)));
}
systable_endscan(tgscan);
}
/*
* Build the new pg_trigger tuple.
*/
memset(nulls, false, sizeof(nulls));
values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein,
CStringGetDatum(trigname));
values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(funcoid);
values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(tgtype);
values[Anum_pg_trigger_tgenabled - 1] = CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(isInternal);
values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(indexOid);
values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);
if (stmt->args)
{
ListCell *le;
char *args;
int16 nargs = list_length(stmt->args);
int len = 0;
foreach(le, stmt->args)
{
char *ar = strVal(lfirst(le));
len += strlen(ar) + 4;
for (; *ar; ar++)
{
if (*ar == '\\')
len++;
}
}
args = (char *) palloc(len + 1);
args[0] = '\0';
foreach(le, stmt->args)
{
char *s = strVal(lfirst(le));
char *d = args + strlen(args);
while (*s)
{
if (*s == '\\')
*d++ = '\\';
*d++ = *s++;
}
strcpy(d, "\\000");
}
values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(nargs);
values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
CStringGetDatum(args));
}
else
{
values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(0);
values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
CStringGetDatum(""));
}
/* build column number array if it's a column-specific trigger */
ncolumns = list_length(stmt->columns);
if (ncolumns == 0)
columns = NULL;
else
{
ListCell *cell;
int i = 0;
columns = (int16 *) palloc(ncolumns * sizeof(int16));
foreach(cell, stmt->columns)
{
char *name = strVal(lfirst(cell));
int16 attnum;
int j;
/* Lookup column name. System columns are not allowed */
attnum = attnameAttNum(rel, name, false);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
name, RelationGetRelationName(rel))));
/* Check for duplicates */
for (j = i - 1; j >= 0; j--)
{
if (columns[j] == attnum)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" specified more than once",
name)));
}
columns[i++] = attnum;
}
}
tgattr = buildint2vector(columns, ncolumns);
values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(tgattr);
/* set tgqual if trigger has WHEN clause */
if (qual)
values[Anum_pg_trigger_tgqual - 1] = CStringGetTextDatum(qual);
else
nulls[Anum_pg_trigger_tgqual - 1] = true;
tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
/* force tuple to have the desired OID */
HeapTupleSetOid(tuple, trigoid);
/*
* Insert tuple into pg_trigger.
*/
simple_heap_insert(tgrel, tuple);
CatalogUpdateIndexes(tgrel, tuple);
heap_freetuple(tuple);
heap_close(tgrel, RowExclusiveLock);
pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
pfree(DatumGetPointer(values[Anum_pg_trigger_tgargs - 1]));
pfree(DatumGetPointer(values[Anum_pg_trigger_tgattr - 1]));
/*
* Update relation's pg_class entry. Crucial side-effect: other backends
* (and this one too!) are sent SI message to make them rebuild relcache
* entries.
*/
pgrel = heap_open(RelationRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopy1(RELOID,
ObjectIdGetDatum(RelationGetRelid(rel)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u",
RelationGetRelid(rel));
((Form_pg_class) GETSTRUCT(tuple))->relhastriggers = true;
simple_heap_update(pgrel, &tuple->t_self, tuple);
CatalogUpdateIndexes(pgrel, tuple);
heap_freetuple(tuple);
heap_close(pgrel, RowExclusiveLock);
/*
* We used to try to update the rel's relcache entry here, but that's
* fairly pointless since it will happen as a byproduct of the upcoming
* CommandCounterIncrement...
*/
/*
* Record dependencies for trigger. Always place a normal dependency on
* the function.
*/
myself.classId = TriggerRelationId;
myself.objectId = trigoid;
myself.objectSubId = 0;
referenced.classId = ProcedureRelationId;
referenced.objectId = funcoid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
if (isInternal && OidIsValid(constraintOid))
{
/*
* Internally-generated trigger for a constraint, so make it an
* internal dependency of the constraint. We can skip depending on
* the relation(s), as there'll be an indirect dependency via the
* constraint.
*/
referenced.classId = ConstraintRelationId;
referenced.objectId = constraintOid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
}
else
{
/*
* User CREATE TRIGGER, so place dependencies. We make trigger be
* auto-dropped if its relation is dropped or if the FK relation is
* dropped. (Auto drop is compatible with our pre-7.3 behavior.)
*/
referenced.classId = RelationRelationId;
referenced.objectId = RelationGetRelid(rel);
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
if (OidIsValid(constrrelid))
{
referenced.classId = RelationRelationId;
referenced.objectId = constrrelid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
}
/* Not possible to have an index dependency in this case */
Assert(!OidIsValid(indexOid));
/*
* If it's a user-specified constraint trigger, make the constraint
* internally dependent on the trigger instead of vice versa.
*/
if (OidIsValid(constraintOid))
{
referenced.classId = ConstraintRelationId;
referenced.objectId = constraintOid;
referenced.objectSubId = 0;
recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL);
}
}
/* If column-specific trigger, add normal dependencies on columns */
if (columns != NULL)
{
int i;
referenced.classId = RelationRelationId;
referenced.objectId = RelationGetRelid(rel);
for (i = 0; i < ncolumns; i++)
{
referenced.objectSubId = columns[i];
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
}
/*
* If it has a WHEN clause, add dependencies on objects mentioned in the
* expression (eg, functions, as well as any columns used).
*/
if (whenClause != NULL)
recordDependencyOnExpr(&myself, whenClause, whenRtable,
DEPENDENCY_NORMAL);
/* Post creation hook for new trigger */
InvokeObjectPostCreateHookArg(TriggerRelationId, trigoid, 0,
isInternal);
/* Keep lock on target rel until end of xact */
heap_close(rel, NoLock);
return trigoid;
}
Definition at line 1331 of file trigger.c.
References Anum_pg_trigger_tgname, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, CacheInvalidateRelcache(), CatalogUpdateIndexes(), CStringGetDatum, ereport, errcode(), errmsg(), ERROR, GETSTRUCT, heap_close, heap_copytuple(), heap_freetuple(), heap_open(), HeapTupleGetOid, HeapTupleIsValid, InvokeObjectPostAlterHook, NameStr, ObjectIdGetDatum, RelationGetRelationName, RelationGetRelid, RowExclusiveLock, ScanKeyInit(), simple_heap_update(), SnapshotNow, superuser(), systable_beginscan(), systable_endscan(), systable_getnext(), HeapTupleData::t_self, TriggerRelationId, and TriggerRelidNameIndexId.
Referenced by ATExecEnableDisableTrigger().
{
Relation tgrel;
int nkeys;
ScanKeyData keys[2];
SysScanDesc tgscan;
HeapTuple tuple;
bool found;
bool changed;
/* Scan the relevant entries in pg_triggers */
tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
ScanKeyInit(&keys[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(rel)));
if (tgname)
{
ScanKeyInit(&keys[1],
Anum_pg_trigger_tgname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(tgname));
nkeys = 2;
}
else
nkeys = 1;
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
SnapshotNow, nkeys, keys);
found = changed = false;
while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
{
Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple);
if (oldtrig->tgisinternal)
{
/* system trigger ... ok to process? */
if (skip_system)
continue;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system trigger",
NameStr(oldtrig->tgname))));
}
found = true;
if (oldtrig->tgenabled != fires_when)
{
/* need to change this one ... make a copy to scribble on */
HeapTuple newtup = heap_copytuple(tuple);
Form_pg_trigger newtrig = (Form_pg_trigger) GETSTRUCT(newtup);
newtrig->tgenabled = fires_when;
simple_heap_update(tgrel, &newtup->t_self, newtup);
/* Keep catalog indexes current */
CatalogUpdateIndexes(tgrel, newtup);
heap_freetuple(newtup);
changed = true;
}
InvokeObjectPostAlterHook(TriggerRelationId,
HeapTupleGetOid(tuple), 0);
}
systable_endscan(tgscan);
heap_close(tgrel, RowExclusiveLock);
if (tgname && !found)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("trigger \"%s\" for table \"%s\" does not exist",
tgname, RelationGetRelationName(rel))));
/*
* If we changed anything, broadcast a SI inval message to force each
* backend (including our own!) to rebuild relation's relcache entry.
* Otherwise they will fail to apply the change promptly.
*/
if (changed)
CacheInvalidateRelcache(rel);
}
| void ExecARDeleteTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| ItemPointer | tupleid | |||
| ) |
Definition at line 2205 of file trigger.c.
References AfterTriggerSaveEvent(), GetTupleForTrigger(), heap_freetuple(), LockTupleExclusive, NIL, NULL, ResultRelInfo::ri_TrigDesc, TriggerDesc::trig_delete_after_row, and TRIGGER_EVENT_DELETE.
Referenced by ExecDelete().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_delete_after_row)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
tupleid, LockTupleExclusive,
NULL);
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
true, trigtuple, NULL, NIL, NULL);
heap_freetuple(trigtuple);
}
}
| void ExecARInsertTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| HeapTuple | trigtuple, | |||
| List * | recheckIndexes | |||
| ) |
Definition at line 2011 of file trigger.c.
References AfterTriggerSaveEvent(), NULL, ResultRelInfo::ri_TrigDesc, TriggerDesc::trig_insert_after_row, and TRIGGER_EVENT_INSERT.
Referenced by CopyFrom(), CopyFromInsertBatch(), and ExecInsert().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_insert_after_row)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
true, NULL, trigtuple, recheckIndexes, NULL);
}
| void ExecARUpdateTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| ItemPointer | tupleid, | |||
| HeapTuple | newtuple, | |||
| List * | recheckIndexes | |||
| ) |
Definition at line 2443 of file trigger.c.
References AfterTriggerSaveEvent(), GetModifiedColumns, GetTupleForTrigger(), heap_freetuple(), LockTupleExclusive, NULL, ResultRelInfo::ri_TrigDesc, TriggerDesc::trig_update_after_row, and TRIGGER_EVENT_UPDATE.
Referenced by ExecUpdate().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_update_after_row)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
tupleid, LockTupleExclusive,
NULL);
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
true, trigtuple, newtuple, recheckIndexes,
GetModifiedColumns(relinfo, estate));
heap_freetuple(trigtuple);
}
}
| void ExecASDeleteTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 2136 of file trigger.c.
References AfterTriggerSaveEvent(), NIL, NULL, ResultRelInfo::ri_TrigDesc, TriggerDesc::trig_delete_after_statement, and TRIGGER_EVENT_DELETE.
Referenced by fireASTriggers().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_delete_after_statement)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
false, NULL, NULL, NIL, NULL);
}
| void ExecASInsertTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 1937 of file trigger.c.
References AfterTriggerSaveEvent(), NIL, NULL, ResultRelInfo::ri_TrigDesc, TriggerDesc::trig_insert_after_statement, and TRIGGER_EVENT_INSERT.
Referenced by CopyFrom(), and fireASTriggers().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_insert_after_statement)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
false, NULL, NULL, NIL, NULL);
}
| void ExecASTruncateTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 2577 of file trigger.c.
References AfterTriggerSaveEvent(), NIL, NULL, ResultRelInfo::ri_TrigDesc, TriggerDesc::trig_truncate_after_statement, and TRIGGER_EVENT_TRUNCATE.
Referenced by ExecuteTruncate().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_truncate_after_statement)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE,
false, NULL, NULL, NIL, NULL);
}
| void ExecASUpdateTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 2321 of file trigger.c.
References AfterTriggerSaveEvent(), GetModifiedColumns, NIL, NULL, ResultRelInfo::ri_TrigDesc, TriggerDesc::trig_update_after_statement, and TRIGGER_EVENT_UPDATE.
Referenced by fireASTriggers().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_update_after_statement)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
false, NULL, NULL, NIL,
GetModifiedColumns(relinfo, estate));
}
| bool ExecBRDeleteTriggers | ( | EState * | estate, | |
| EPQState * | epqstate, | |||
| ResultRelInfo * | relinfo, | |||
| ItemPointer | tupleid | |||
| ) |
Definition at line 2146 of file trigger.c.
References ExecCallTriggerFunc(), GetPerTupleMemoryContext, GetTupleForTrigger(), heap_freetuple(), LockTupleExclusive, NULL, TriggerDesc::numtriggers, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TRIGGER_EVENT_DELETE, TRIGGER_EVENT_ROW, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TriggerEnabled(), TriggerDesc::triggers, and TriggerData::type.
Referenced by ExecDelete().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
bool result = true;
TriggerData LocTriggerData;
HeapTuple trigtuple;
HeapTuple newtuple;
TupleTableSlot *newSlot;
int i;
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
LockTupleExclusive, &newSlot);
if (trigtuple == NULL)
return false;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_ROW,
TRIGGER_TYPE_BEFORE,
TRIGGER_TYPE_DELETE))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, trigtuple, NULL))
continue;
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple == NULL)
{
result = false; /* tell caller to suppress delete */
break;
}
if (newtuple != trigtuple)
heap_freetuple(newtuple);
}
heap_freetuple(trigtuple);
return result;
}
| TupleTableSlot* ExecBRInsertTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| TupleTableSlot * | slot | |||
| ) |
Definition at line 1947 of file trigger.c.
References EState::es_trig_tuple_slot, ExecCallTriggerFunc(), ExecMaterializeSlot(), ExecSetSlotDescriptor(), ExecStoreTuple(), GetPerTupleMemoryContext, heap_freetuple(), InvalidBuffer, NULL, TriggerDesc::numtriggers, RelationGetDescr, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TRIGGER_EVENT_INSERT, TRIGGER_EVENT_ROW, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TriggerEnabled(), TriggerDesc::triggers, TupleTableSlot::tts_tupleDescriptor, and TriggerData::type.
Referenced by CopyFrom(), and ExecInsert().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
HeapTuple slottuple = ExecMaterializeSlot(slot);
HeapTuple newtuple = slottuple;
HeapTuple oldtuple;
TriggerData LocTriggerData;
int i;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_ROW,
TRIGGER_TYPE_BEFORE,
TRIGGER_TYPE_INSERT))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, NULL, newtuple))
continue;
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (oldtuple != newtuple && oldtuple != slottuple)
heap_freetuple(oldtuple);
if (newtuple == NULL)
return NULL; /* "do nothing" */
}
if (newtuple != slottuple)
{
/*
* Return the modified tuple using the es_trig_tuple_slot. We assume
* the tuple was allocated in per-tuple memory context, and therefore
* will go away by itself. The tuple table slot should not try to
* clear it.
*/
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
if (newslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(newslot, tupdesc);
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
slot = newslot;
}
return slot;
}
| TupleTableSlot* ExecBRUpdateTriggers | ( | EState * | estate, | |
| EPQState * | epqstate, | |||
| ResultRelInfo * | relinfo, | |||
| ItemPointer | tupleid, | |||
| TupleTableSlot * | slot | |||
| ) |
Definition at line 2332 of file trigger.c.
References bms_overlap(), EState::es_trig_tuple_slot, ExecCallTriggerFunc(), ExecFilterJunk(), ExecMaterializeSlot(), ExecSetSlotDescriptor(), ExecStoreTuple(), GetModifiedColumns, GetPerTupleMemoryContext, GetTupleForTrigger(), heap_freetuple(), InvalidBuffer, NULL, TriggerDesc::numtriggers, RelationGetDescr, RelationGetIndexAttrBitmap(), ResultRelInfo::ri_junkFilter, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TRIGGER_EVENT_ROW, TRIGGER_EVENT_UPDATE, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TRIGGER_TYPE_UPDATE, TriggerEnabled(), TriggerDesc::triggers, TupleTableSlot::tts_tupleDescriptor, and TriggerData::type.
Referenced by ExecUpdate().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
HeapTuple slottuple = ExecMaterializeSlot(slot);
HeapTuple newtuple = slottuple;
TriggerData LocTriggerData;
HeapTuple trigtuple;
HeapTuple oldtuple;
TupleTableSlot *newSlot;
int i;
Bitmapset *modifiedCols;
Bitmapset *keyCols;
LockTupleMode lockmode;
/*
* Compute lock mode to use. If columns that are part of the key have not
* been modified, then we can use a weaker lock, allowing for better
* concurrency.
*/
modifiedCols = GetModifiedColumns(relinfo, estate);
keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, true);
if (bms_overlap(keyCols, modifiedCols))
lockmode = LockTupleExclusive;
else
lockmode = LockTupleNoKeyExclusive;
/* get a copy of the on-disk tuple we are planning to update */
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
lockmode, &newSlot);
if (trigtuple == NULL)
return NULL; /* cancel the update action */
/*
* In READ COMMITTED isolation level it's possible that target tuple was
* changed due to concurrent update. In that case we have a raw subplan
* output tuple in newSlot, and need to run it through the junk filter to
* produce an insertable tuple.
*
* Caution: more than likely, the passed-in slot is the same as the
* junkfilter's output slot, so we are clobbering the original value of
* slottuple by doing the filtering. This is OK since neither we nor our
* caller have any more interest in the prior contents of that slot.
*/
if (newSlot != NULL)
{
slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot);
slottuple = ExecMaterializeSlot(slot);
newtuple = slottuple;
}
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_ROW,
TRIGGER_TYPE_BEFORE,
TRIGGER_TYPE_UPDATE))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
modifiedCols, trigtuple, newtuple))
continue;
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (oldtuple != newtuple && oldtuple != slottuple)
heap_freetuple(oldtuple);
if (newtuple == NULL)
{
heap_freetuple(trigtuple);
return NULL; /* "do nothing" */
}
}
heap_freetuple(trigtuple);
if (newtuple != slottuple)
{
/*
* Return the modified tuple using the es_trig_tuple_slot. We assume
* the tuple was allocated in per-tuple memory context, and therefore
* will go away by itself. The tuple table slot should not try to
* clear it.
*/
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
if (newslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(newslot, tupdesc);
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
slot = newslot;
}
return slot;
}
| void ExecBSDeleteTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 2086 of file trigger.c.
References ereport, errcode(), errmsg(), ERROR, ExecCallTriggerFunc(), GetPerTupleMemoryContext, NULL, TriggerDesc::numtriggers, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TriggerDesc::trig_delete_before_statement, TRIGGER_EVENT_DELETE, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_STATEMENT, TriggerEnabled(), TriggerDesc::triggers, and TriggerData::type.
Referenced by fireBSTriggers().
{
TriggerDesc *trigdesc;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
if (!trigdesc->trig_delete_before_statement)
return;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
HeapTuple newtuple;
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE,
TRIGGER_TYPE_DELETE))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, NULL, NULL))
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("BEFORE STATEMENT trigger cannot return a value")));
}
}
| void ExecBSInsertTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 1887 of file trigger.c.
References ereport, errcode(), errmsg(), ERROR, ExecCallTriggerFunc(), GetPerTupleMemoryContext, NULL, TriggerDesc::numtriggers, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TriggerDesc::trig_insert_before_statement, TRIGGER_EVENT_INSERT, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_STATEMENT, TriggerEnabled(), TriggerDesc::triggers, and TriggerData::type.
Referenced by CopyFrom(), and fireBSTriggers().
{
TriggerDesc *trigdesc;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
if (!trigdesc->trig_insert_before_statement)
return;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
HeapTuple newtuple;
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE,
TRIGGER_TYPE_INSERT))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, NULL, NULL))
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("BEFORE STATEMENT trigger cannot return a value")));
}
}
| void ExecBSTruncateTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 2527 of file trigger.c.
References ereport, errcode(), errmsg(), ERROR, ExecCallTriggerFunc(), GetPerTupleMemoryContext, NULL, TriggerDesc::numtriggers, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TriggerDesc::trig_truncate_before_statement, TRIGGER_EVENT_TRUNCATE, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_TRUNCATE, TriggerEnabled(), TriggerDesc::triggers, and TriggerData::type.
Referenced by ExecuteTruncate().
{
TriggerDesc *trigdesc;
int i;
TriggerData LocTriggerData;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
if (!trigdesc->trig_truncate_before_statement)
return;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_TRUNCATE |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
HeapTuple newtuple;
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE,
TRIGGER_TYPE_TRUNCATE))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, NULL, NULL))
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("BEFORE STATEMENT trigger cannot return a value")));
}
}
| void ExecBSUpdateTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo | |||
| ) |
Definition at line 2268 of file trigger.c.
References ereport, errcode(), errmsg(), ERROR, ExecCallTriggerFunc(), GetModifiedColumns, GetPerTupleMemoryContext, NULL, TriggerDesc::numtriggers, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TriggerDesc::trig_update_before_statement, TRIGGER_EVENT_UPDATE, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_UPDATE, TriggerEnabled(), TriggerDesc::triggers, and TriggerData::type.
Referenced by fireBSTriggers().
{
TriggerDesc *trigdesc;
int i;
TriggerData LocTriggerData;
Bitmapset *modifiedCols;
trigdesc = relinfo->ri_TrigDesc;
if (trigdesc == NULL)
return;
if (!trigdesc->trig_update_before_statement)
return;
modifiedCols = GetModifiedColumns(relinfo, estate);
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_BEFORE;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
HeapTuple newtuple;
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE,
TRIGGER_TYPE_UPDATE))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
modifiedCols, NULL, NULL))
continue;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (newtuple)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("BEFORE STATEMENT trigger cannot return a value")));
}
}
| static HeapTuple ExecCallTriggerFunc | ( | TriggerData * | trigdata, | |
| int | tgindx, | |||
| FmgrInfo * | finfo, | |||
| Instrumentation * | instr, | |||
| MemoryContext | per_tuple_context | |||
| ) | [static] |
Definition at line 1805 of file trigger.c.
References Assert, DatumGetPointer, ereport, errcode(), errmsg(), ERROR, FunctionCallInfoData::flinfo, fmgr_info(), FmgrInfo::fn_oid, FunctionCallInvoke, InitFunctionCallInfoData, InstrStartNode(), InstrStopNode(), InvalidOid, FunctionCallInfoData::isnull, MemoryContextSwitchTo(), MyTriggerDepth, NULL, PG_CATCH, PG_END_TRY, PG_RE_THROW, PG_TRY, pgstat_end_function_usage(), pgstat_init_function_usage(), TriggerData::tg_trigger, and Trigger::tgfoid.
Referenced by AfterTriggerExecute(), ExecBRDeleteTriggers(), ExecBRInsertTriggers(), ExecBRUpdateTriggers(), ExecBSDeleteTriggers(), ExecBSInsertTriggers(), ExecBSTruncateTriggers(), ExecBSUpdateTriggers(), ExecIRDeleteTriggers(), ExecIRInsertTriggers(), and ExecIRUpdateTriggers().
{
FunctionCallInfoData fcinfo;
PgStat_FunctionCallUsage fcusage;
Datum result;
MemoryContext oldContext;
finfo += tgindx;
/*
* We cache fmgr lookup info, to avoid making the lookup again on each
* call.
*/
if (finfo->fn_oid == InvalidOid)
fmgr_info(trigdata->tg_trigger->tgfoid, finfo);
Assert(finfo->fn_oid == trigdata->tg_trigger->tgfoid);
/*
* If doing EXPLAIN ANALYZE, start charging time to this trigger.
*/
if (instr)
InstrStartNode(instr + tgindx);
/*
* Do the function evaluation in the per-tuple memory context, so that
* leaked memory will be reclaimed once per tuple. Note in particular that
* any new tuple created by the trigger function will live till the end of
* the tuple cycle.
*/
oldContext = MemoryContextSwitchTo(per_tuple_context);
/*
* Call the function, passing no arguments but setting a context.
*/
InitFunctionCallInfoData(fcinfo, finfo, 0,
InvalidOid, (Node *) trigdata, NULL);
pgstat_init_function_usage(&fcinfo, &fcusage);
MyTriggerDepth++;
PG_TRY();
{
result = FunctionCallInvoke(&fcinfo);
}
PG_CATCH();
{
MyTriggerDepth--;
PG_RE_THROW();
}
PG_END_TRY();
MyTriggerDepth--;
pgstat_end_function_usage(&fcusage, true);
MemoryContextSwitchTo(oldContext);
/*
* Trigger protocol allows function to return a null pointer, but NOT to
* set the isnull result flag.
*/
if (fcinfo.isnull)
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("trigger function %u returned null value",
fcinfo.flinfo->fn_oid)));
/*
* If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count
* one "tuple returned" (really the number of firings).
*/
if (instr)
InstrStopNode(instr + tgindx, 1);
return (HeapTuple) DatumGetPointer(result);
}
| bool ExecIRDeleteTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| HeapTuple | trigtuple | |||
| ) |
Definition at line 2223 of file trigger.c.
References ExecCallTriggerFunc(), GetPerTupleMemoryContext, heap_freetuple(), NULL, TriggerDesc::numtriggers, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TRIGGER_EVENT_DELETE, TRIGGER_EVENT_ROW, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TriggerEnabled(), TriggerDesc::triggers, and TriggerData::type.
Referenced by ExecDelete().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
TriggerData LocTriggerData;
HeapTuple rettuple;
int i;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_INSTEAD;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_ROW,
TRIGGER_TYPE_INSTEAD,
TRIGGER_TYPE_DELETE))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, trigtuple, NULL))
continue;
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
rettuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (rettuple == NULL)
return false; /* Delete was suppressed */
if (rettuple != trigtuple)
heap_freetuple(rettuple);
}
return true;
}
| TupleTableSlot* ExecIRInsertTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| TupleTableSlot * | slot | |||
| ) |
Definition at line 2022 of file trigger.c.
References EState::es_trig_tuple_slot, ExecCallTriggerFunc(), ExecMaterializeSlot(), ExecSetSlotDescriptor(), ExecStoreTuple(), GetPerTupleMemoryContext, heap_freetuple(), InvalidBuffer, NULL, TriggerDesc::numtriggers, RelationGetDescr, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TRIGGER_EVENT_INSERT, TRIGGER_EVENT_ROW, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TriggerEnabled(), TriggerDesc::triggers, TupleTableSlot::tts_tupleDescriptor, and TriggerData::type.
Referenced by ExecInsert().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
HeapTuple slottuple = ExecMaterializeSlot(slot);
HeapTuple newtuple = slottuple;
HeapTuple oldtuple;
TriggerData LocTriggerData;
int i;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_INSTEAD;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_ROW,
TRIGGER_TYPE_INSTEAD,
TRIGGER_TYPE_INSERT))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, NULL, newtuple))
continue;
LocTriggerData.tg_trigtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (oldtuple != newtuple && oldtuple != slottuple)
heap_freetuple(oldtuple);
if (newtuple == NULL)
return NULL; /* "do nothing" */
}
if (newtuple != slottuple)
{
/*
* Return the modified tuple using the es_trig_tuple_slot. We assume
* the tuple was allocated in per-tuple memory context, and therefore
* will go away by itself. The tuple table slot should not try to
* clear it.
*/
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
if (newslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(newslot, tupdesc);
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
slot = newslot;
}
return slot;
}
| TupleTableSlot* ExecIRUpdateTriggers | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| HeapTuple | trigtuple, | |||
| TupleTableSlot * | slot | |||
| ) |
Definition at line 2463 of file trigger.c.
References EState::es_trig_tuple_slot, ExecCallTriggerFunc(), ExecMaterializeSlot(), ExecSetSlotDescriptor(), ExecStoreTuple(), GetPerTupleMemoryContext, heap_freetuple(), InvalidBuffer, NULL, TriggerDesc::numtriggers, RelationGetDescr, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigFunctions, ResultRelInfo::ri_TrigInstrument, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_newtuplebuf, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, TriggerData::tg_trigtuplebuf, Trigger::tgtype, TRIGGER_EVENT_ROW, TRIGGER_EVENT_UPDATE, TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TRIGGER_TYPE_UPDATE, TriggerEnabled(), TriggerDesc::triggers, TupleTableSlot::tts_tupleDescriptor, and TriggerData::type.
Referenced by ExecUpdate().
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
HeapTuple slottuple = ExecMaterializeSlot(slot);
HeapTuple newtuple = slottuple;
TriggerData LocTriggerData;
HeapTuple oldtuple;
int i;
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
TRIGGER_EVENT_ROW |
TRIGGER_EVENT_INSTEAD;
LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
for (i = 0; i < trigdesc->numtriggers; i++)
{
Trigger *trigger = &trigdesc->triggers[i];
if (!TRIGGER_TYPE_MATCHES(trigger->tgtype,
TRIGGER_TYPE_ROW,
TRIGGER_TYPE_INSTEAD,
TRIGGER_TYPE_UPDATE))
continue;
if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
NULL, trigtuple, newtuple))
continue;
LocTriggerData.tg_trigtuple = trigtuple;
LocTriggerData.tg_newtuple = oldtuple = newtuple;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigger = trigger;
newtuple = ExecCallTriggerFunc(&LocTriggerData,
i,
relinfo->ri_TrigFunctions,
relinfo->ri_TrigInstrument,
GetPerTupleMemoryContext(estate));
if (oldtuple != newtuple && oldtuple != slottuple)
heap_freetuple(oldtuple);
if (newtuple == NULL)
return NULL; /* "do nothing" */
}
if (newtuple != slottuple)
{
/*
* Return the modified tuple using the es_trig_tuple_slot. We assume
* the tuple was allocated in per-tuple memory context, and therefore
* will go away by itself. The tuple table slot should not try to
* clear it.
*/
TupleTableSlot *newslot = estate->es_trig_tuple_slot;
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
if (newslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(newslot, tupdesc);
ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
slot = newslot;
}
return slot;
}
| void FreeTriggerDesc | ( | TriggerDesc * | trigdesc | ) |
Definition at line 1685 of file trigger.c.
References i, NULL, TriggerDesc::numtriggers, pfree(), Trigger::tgargs, Trigger::tgattr, Trigger::tgname, Trigger::tgnargs, Trigger::tgnattr, Trigger::tgqual, and TriggerDesc::triggers.
Referenced by RelationBuildTriggers(), and RelationDestroyRelation().
{
Trigger *trigger;
int i;
if (trigdesc == NULL)
return;
trigger = trigdesc->triggers;
for (i = 0; i < trigdesc->numtriggers; i++)
{
pfree(trigger->tgname);
if (trigger->tgnattr > 0)
pfree(trigger->tgattr);
if (trigger->tgnargs > 0)
{
while (--(trigger->tgnargs) >= 0)
pfree(trigger->tgargs[trigger->tgnargs]);
pfree(trigger->tgargs);
}
if (trigger->tgqual)
pfree(trigger->tgqual);
trigger++;
}
pfree(trigdesc->triggers);
pfree(trigdesc);
}
Definition at line 1107 of file trigger.c.
References AccessShareLock, Anum_pg_trigger_tgname, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, CStringGetDatum, ereport, errcode(), errmsg(), ERROR, get_rel_name(), heap_close, heap_open(), HeapTupleGetOid, HeapTupleIsValid, ObjectIdGetDatum, ScanKeyInit(), SnapshotNow, systable_beginscan(), systable_endscan(), systable_getnext(), TriggerRelationId, and TriggerRelidNameIndexId.
Referenced by get_object_address_relobject().
{
Relation tgrel;
ScanKeyData skey[2];
SysScanDesc tgscan;
HeapTuple tup;
Oid oid;
/*
* Find the trigger, verify permissions, set up object address
*/
tgrel = heap_open(TriggerRelationId, AccessShareLock);
ScanKeyInit(&skey[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
ScanKeyInit(&skey[1],
Anum_pg_trigger_tgname,
BTEqualStrategyNumber, F_NAMEEQ,
CStringGetDatum(trigname));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
SnapshotNow, 2, skey);
tup = systable_getnext(tgscan);
if (!HeapTupleIsValid(tup))
{
if (!missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("trigger \"%s\" for table \"%s\" does not exist",
trigname, get_rel_name(relid))));
oid = InvalidOid;
}
else
{
oid = HeapTupleGetOid(tup);
}
systable_endscan(tgscan);
heap_close(tgrel, AccessShareLock);
return oid;
}
| static HeapTuple GetTupleForTrigger | ( | EState * | estate, | |
| EPQState * | epqstate, | |||
| ResultRelInfo * | relinfo, | |||
| ItemPointer | tid, | |||
| LockTupleMode | lockmode, | |||
| TupleTableSlot ** | newSlot | |||
| ) | [static] |
Definition at line 2588 of file trigger.c.
References Assert, BUFFER_LOCK_SHARE, BUFFER_LOCK_UNLOCK, BufferGetPage, HeapUpdateFailureData::cmax, HeapUpdateFailureData::ctid, elog, ereport, errcode(), errhint(), errmsg(), ERROR, EState::es_output_cid, EvalPlanQual(), heap_copytuple(), heap_lock_tuple(), HeapTupleMayBeUpdated, HeapTupleSelfUpdated, HeapTupleUpdated, IsolationUsesXactSnapshot, ItemIdGetLength, ItemIdIsNormal, ItemPointerEquals(), ItemPointerGetBlockNumber, ItemPointerGetOffsetNumber, LockBuffer(), NULL, PageGetItem, PageGetItemId, ReadBuffer(), RelationGetRelid, ReleaseBuffer(), ResultRelInfo::ri_RangeTableIndex, ResultRelInfo::ri_RelationDesc, HeapTupleData::t_data, HeapTupleData::t_len, HeapTupleData::t_self, HeapTupleData::t_tableOid, TupIsNull, and HeapUpdateFailureData::xmax.
Referenced by ExecARDeleteTriggers(), ExecARUpdateTriggers(), ExecBRDeleteTriggers(), and ExecBRUpdateTriggers().
{
Relation relation = relinfo->ri_RelationDesc;
HeapTupleData tuple;
HeapTuple result;
Buffer buffer;
if (newSlot != NULL)
{
HTSU_Result test;
HeapUpdateFailureData hufd;
*newSlot = NULL;
/* caller must pass an epqstate if EvalPlanQual is possible */
Assert(epqstate != NULL);
/*
* lock tuple for update
*/
ltrmark:;
tuple.t_self = *tid;
test = heap_lock_tuple(relation, &tuple,
estate->es_output_cid,
lockmode, false /* wait */,
false, &buffer, &hufd);
switch (test)
{
case HeapTupleSelfUpdated:
/*
* The target tuple was already updated or deleted by the
* current command, or by a later command in the current
* transaction. We ignore the tuple in the former case, and
* throw error in the latter case, for the same reasons
* enumerated in ExecUpdate and ExecDelete in
* nodeModifyTable.c.
*/
if (hufd.cmax != estate->es_output_cid)
ereport(ERROR,
(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
/* treat it as deleted; do not process */
ReleaseBuffer(buffer);
return NULL;
case HeapTupleMayBeUpdated:
break;
case HeapTupleUpdated:
ReleaseBuffer(buffer);
if (IsolationUsesXactSnapshot())
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to concurrent update")));
if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self))
{
/* it was updated, so look at the updated version */
TupleTableSlot *epqslot;
epqslot = EvalPlanQual(estate,
epqstate,
relation,
relinfo->ri_RangeTableIndex,
lockmode,
&hufd.ctid,
hufd.xmax);
if (!TupIsNull(epqslot))
{
*tid = hufd.ctid;
*newSlot = epqslot;
/*
* EvalPlanQual already locked the tuple, but we
* re-call heap_lock_tuple anyway as an easy way of
* re-fetching the correct tuple. Speed is hardly a
* criterion in this path anyhow.
*/
goto ltrmark;
}
}
/*
* if tuple was deleted or PlanQual failed for updated tuple -
* we must not process this tuple!
*/
return NULL;
default:
ReleaseBuffer(buffer);
elog(ERROR, "unrecognized heap_lock_tuple status: %u", test);
return NULL; /* keep compiler quiet */
}
}
else
{
Page page;
ItemId lp;
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
/*
* Although we already know this tuple is valid, we must lock the
* buffer to ensure that no one has a buffer cleanup lock; otherwise
* they might move the tuple while we try to copy it. But we can
* release the lock before actually doing the heap_copytuple call,
* since holding pin is sufficient to prevent anyone from getting a
* cleanup lock they don't already hold.
*/
LockBuffer(buffer, BUFFER_LOCK_SHARE);
page = BufferGetPage(buffer);
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
Assert(ItemIdIsNormal(lp));
tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp);
tuple.t_len = ItemIdGetLength(lp);
tuple.t_self = *tid;
tuple.t_tableOid = RelationGetRelid(relation);
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
}
result = heap_copytuple(&tuple);
ReleaseBuffer(buffer);
return result;
}
| Datum pg_trigger_depth | ( | PG_FUNCTION_ARGS | ) |
Definition at line 4690 of file trigger.c.
References MyTriggerDepth, and PG_RETURN_INT32.
| static void RangeVarCallbackForRenameTrigger | ( | const RangeVar * | rv, | |
| Oid | relid, | |||
| Oid | oldrelid, | |||
| void * | arg | |||
| ) | [static] |
Definition at line 1157 of file trigger.c.
References ACL_KIND_CLASS, aclcheck_error(), ACLCHECK_NOT_OWNER, allowSystemTableMods, ereport, errcode(), errmsg(), ERROR, GETSTRUCT, GetUserId(), HeapTupleIsValid, IsSystemClass(), ObjectIdGetDatum, pg_class_ownercheck(), ReleaseSysCache(), RELKIND_RELATION, RELKIND_VIEW, RangeVar::relname, RELOID, and SearchSysCache1.
Referenced by renametrig().
{
HeapTuple tuple;
Form_pg_class form;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped */
form = (Form_pg_class) GETSTRUCT(tuple);
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view", rv->relname)));
/* you must own the table to rename one of its triggers */
if (!pg_class_ownercheck(relid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
if (!allowSystemTableMods && IsSystemClass(form))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
rv->relname)));
ReleaseSysCache(tuple);
}
| void RelationBuildTriggers | ( | Relation | relation | ) |
Definition at line 1436 of file trigger.c.
References AccessShareLock, Anum_pg_trigger_tgargs, Anum_pg_trigger_tgqual, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, CacheMemoryContext, CopyTriggerDesc(), DatumGetByteaP, DatumGetCString, DirectFunctionCall1, elog, ERROR, fastgetattr, FreeTriggerDesc(), GETSTRUCT, heap_close, heap_open(), HeapTupleGetOid, HeapTupleIsValid, i, MemoryContextSwitchTo(), NameGetDatum, nameout(), TriggerDesc::numtriggers, ObjectIdGetDatum, palloc(), palloc0(), pfree(), pstrdup(), RelationData::rd_att, RelationGetRelationName, RelationGetRelid, repalloc(), ScanKeyInit(), SetTriggerFlags(), SnapshotNow, systable_beginscan(), systable_endscan(), systable_getnext(), TextDatumGetCString, Trigger::tgargs, Trigger::tgattr, Trigger::tgconstraint, Trigger::tgconstrindid, Trigger::tgconstrrelid, Trigger::tgdeferrable, Trigger::tgenabled, Trigger::tgfoid, Trigger::tginitdeferred, Trigger::tgisinternal, Trigger::tgname, Trigger::tgnargs, Trigger::tgnattr, Trigger::tgoid, Trigger::tgqual, Trigger::tgtype, RelationData::trigdesc, TriggerRelationId, TriggerRelidNameIndexId, TriggerDesc::triggers, val, and VARDATA.
Referenced by RelationBuildDesc(), and RelationCacheInitializePhase3().
{
TriggerDesc *trigdesc;
int numtrigs;
int maxtrigs;
Trigger *triggers;
Relation tgrel;
ScanKeyData skey;
SysScanDesc tgscan;
HeapTuple htup;
MemoryContext oldContext;
int i;
/*
* Allocate a working array to hold the triggers (the array is extended if
* necessary)
*/
maxtrigs = 16;
triggers = (Trigger *) palloc(maxtrigs * sizeof(Trigger));
numtrigs = 0;
/*
* Note: since we scan the triggers using TriggerRelidNameIndexId, we will
* be reading the triggers in name order, except possibly during
* emergency-recovery operations (ie, IgnoreSystemIndexes). This in turn
* ensures that triggers will be fired in name order.
*/
ScanKeyInit(&skey,
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
tgrel = heap_open(TriggerRelationId, AccessShareLock);
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
SnapshotNow, 1, &skey);
while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
{
Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
Trigger *build;
Datum datum;
bool isnull;
if (numtrigs >= maxtrigs)
{
maxtrigs *= 2;
triggers = (Trigger *) repalloc(triggers, maxtrigs * sizeof(Trigger));
}
build = &(triggers[numtrigs]);
build->tgoid = HeapTupleGetOid(htup);
build->tgname = DatumGetCString(DirectFunctionCall1(nameout,
NameGetDatum(&pg_trigger->tgname)));
build->tgfoid = pg_trigger->tgfoid;
build->tgtype = pg_trigger->tgtype;
build->tgenabled = pg_trigger->tgenabled;
build->tgisinternal = pg_trigger->tgisinternal;
build->tgconstrrelid = pg_trigger->tgconstrrelid;
build->tgconstrindid = pg_trigger->tgconstrindid;
build->tgconstraint = pg_trigger->tgconstraint;
build->tgdeferrable = pg_trigger->tgdeferrable;
build->tginitdeferred = pg_trigger->tginitdeferred;
build->tgnargs = pg_trigger->tgnargs;
/* tgattr is first var-width field, so OK to access directly */
build->tgnattr = pg_trigger->tgattr.dim1;
if (build->tgnattr > 0)
{
build->tgattr = (int16 *) palloc(build->tgnattr * sizeof(int16));
memcpy(build->tgattr, &(pg_trigger->tgattr.values),
build->tgnattr * sizeof(int16));
}
else
build->tgattr = NULL;
if (build->tgnargs > 0)
{
bytea *val;
char *p;
val = DatumGetByteaP(fastgetattr(htup,
Anum_pg_trigger_tgargs,
tgrel->rd_att, &isnull));
if (isnull)
elog(ERROR, "tgargs is null in trigger for relation \"%s\"",
RelationGetRelationName(relation));
p = (char *) VARDATA(val);
build->tgargs = (char **) palloc(build->tgnargs * sizeof(char *));
for (i = 0; i < build->tgnargs; i++)
{
build->tgargs[i] = pstrdup(p);
p += strlen(p) + 1;
}
}
else
build->tgargs = NULL;
datum = fastgetattr(htup, Anum_pg_trigger_tgqual,
tgrel->rd_att, &isnull);
if (!isnull)
build->tgqual = TextDatumGetCString(datum);
else
build->tgqual = NULL;
numtrigs++;
}
systable_endscan(tgscan);
heap_close(tgrel, AccessShareLock);
/* There might not be any triggers */
if (numtrigs == 0)
{
pfree(triggers);
return;
}
/* Build trigdesc */
trigdesc = (TriggerDesc *) palloc0(sizeof(TriggerDesc));
trigdesc->triggers = triggers;
trigdesc->numtriggers = numtrigs;
for (i = 0; i < numtrigs; i++)
SetTriggerFlags(trigdesc, &(triggers[i]));
/* Copy completed trigdesc into cache storage */
oldContext = MemoryContextSwitchTo(CacheMemoryContext);
relation->trigdesc = CopyTriggerDesc(trigdesc);
MemoryContextSwitchTo(oldContext);
/* Release working memory */
FreeTriggerDesc(trigdesc);
}
| void RemoveTriggerById | ( | Oid | trigOid | ) |
Definition at line 1031 of file trigger.c.
References AccessExclusiveLock, allowSystemTableMods, BTEqualStrategyNumber, CacheInvalidateRelcache(), elog, ereport, errcode(), errmsg(), ERROR, GETSTRUCT, heap_close, heap_open(), HeapTupleIsValid, IsSystemRelation(), NoLock, ObjectIdAttributeNumber, ObjectIdGetDatum, RelationData::rd_rel, RelationGetRelationName, RELKIND_RELATION, RELKIND_VIEW, RowExclusiveLock, ScanKeyInit(), simple_heap_delete(), SnapshotNow, systable_beginscan(), systable_endscan(), systable_getnext(), HeapTupleData::t_self, TriggerOidIndexId, and TriggerRelationId.
Referenced by doDeletion().
{
Relation tgrel;
SysScanDesc tgscan;
ScanKeyData skey[1];
HeapTuple tup;
Oid relid;
Relation rel;
tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
/*
* Find the trigger to delete.
*/
ScanKeyInit(&skey[0],
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(trigOid));
tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
SnapshotNow, 1, skey);
tup = systable_getnext(tgscan);
if (!HeapTupleIsValid(tup))
elog(ERROR, "could not find tuple for trigger %u", trigOid);
/*
* Open and exclusive-lock the relation the trigger belongs to.
*/
relid = ((Form_pg_trigger) GETSTRUCT(tup))->tgrelid;
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view",
RelationGetRelationName(rel))));
if (!allowSystemTableMods && IsSystemRelation(rel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
/*
* Delete the pg_trigger tuple.
*/
simple_heap_delete(tgrel, &tup->t_self);
systable_endscan(tgscan);
heap_close(tgrel, RowExclusiveLock);
/*
* We do not bother to try to determine whether any other triggers remain,
* which would be needed in order to decide whether it's safe to clear the
* relation's relhastriggers. (In any case, there might be a concurrent
* process adding new triggers.) Instead, just force a relcache inval to
* make other backends (and this one too!) rebuild their relcache entries.
* There's no great harm in leaving relhastriggers true even if there are
* no triggers left.
*/
CacheInvalidateRelcache(rel);
/* Keep lock on trigger's rel until end of xact */
heap_close(rel, NoLock);
}
| Oid renametrig | ( | RenameStmt * | stmt | ) |
Definition at line 1200 of file trigger.c.
References AccessExclusiveLock, Anum_pg_trigger_tgname, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, CacheInvalidateRelcache(), CatalogUpdateIndexes(), ereport, errcode(), errmsg(), ERROR, GETSTRUCT, heap_close, heap_copytuple(), heap_open(), HeapTupleGetOid, HeapTupleIsValid, InvokeObjectPostAlterHook, namestrcpy(), RenameStmt::newname, NoLock, NULL, ObjectIdGetDatum, PointerGetDatum, RangeVarCallbackForRenameTrigger(), RangeVarGetRelidExtended(), RenameStmt::relation, relation_close(), relation_open(), RelationGetRelationName, RowExclusiveLock, ScanKeyInit(), simple_heap_update(), SnapshotNow, RenameStmt::subname, systable_beginscan(), systable_endscan(), systable_getnext(), HeapTupleData::t_self, TriggerRelationId, and TriggerRelidNameIndexId.
Referenced by ExecRenameStmt().
{
Oid tgoid;
Relation targetrel;
Relation tgrel;
HeapTuple tuple;
SysScanDesc tgscan;
ScanKeyData key[2];
Oid relid;
/*
* Look up name, check permissions, and acquire lock (which we will NOT
* release until end of transaction).
*/
relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
false, false,
RangeVarCallbackForRenameTrigger,
NULL);
/* Have lock already, so just need to build relcache entry. */
targetrel = relation_open(relid, NoLock);
/*
* Scan pg_trigger twice for existing triggers on relation. We do this in
* order to ensure a trigger does not exist with newname (The unique index
* on tgrelid/tgname would complain anyway) and to ensure a trigger does
* exist with oldname.
*
* NOTE that this is cool only because we have AccessExclusiveLock on the
* relation, so the trigger set won't be changing underneath us.
*/
tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
/*
* First pass -- look for name conflict
*/
ScanKeyInit(&key[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
ScanKeyInit(&key[1],
Anum_pg_trigger_tgname,
BTEqualStrategyNumber, F_NAMEEQ,
PointerGetDatum(stmt->newname));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
SnapshotNow, 2, key);
if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" already exists",
stmt->newname, RelationGetRelationName(targetrel))));
systable_endscan(tgscan);
/*
* Second pass -- look for trigger existing with oldname and update
*/
ScanKeyInit(&key[0],
Anum_pg_trigger_tgrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relid));
ScanKeyInit(&key[1],
Anum_pg_trigger_tgname,
BTEqualStrategyNumber, F_NAMEEQ,
PointerGetDatum(stmt->subname));
tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
SnapshotNow, 2, key);
if (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
{
tgoid = HeapTupleGetOid(tuple);
/*
* Update pg_trigger tuple with new tgname.
*/
tuple = heap_copytuple(tuple); /* need a modifiable copy */
namestrcpy(&((Form_pg_trigger) GETSTRUCT(tuple))->tgname,
stmt->newname);
simple_heap_update(tgrel, &tuple->t_self, tuple);
/* keep system catalog indexes current */
CatalogUpdateIndexes(tgrel, tuple);
InvokeObjectPostAlterHook(TriggerRelationId,
HeapTupleGetOid(tuple), 0);
/*
* Invalidate relation's relcache entry so that other backends (and
* this one too!) are sent SI message to make them rebuild relcache
* entries. (Ideally this should happen automatically...)
*/
CacheInvalidateRelcache(targetrel);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("trigger \"%s\" for table \"%s\" does not exist",
stmt->subname, RelationGetRelationName(targetrel))));
}
systable_endscan(tgscan);
heap_close(tgrel, RowExclusiveLock);
/*
* Close rel, but keep exclusive lock!
*/
relation_close(targetrel, NoLock);
return tgoid;
}
| static SetConstraintState SetConstraintStateAddItem | ( | SetConstraintState | state, | |
| Oid | tgoid, | |||
| bool | tgisdeferred | |||
| ) | [static] |
Definition at line 4125 of file trigger.c.
References Assert, Max, SetConstraintStateData::numalloc, SetConstraintStateData::numstates, repalloc(), SetConstraintTriggerData::sct_tgisdeferred, SetConstraintTriggerData::sct_tgoid, and SetConstraintStateData::trigstates.
Referenced by AfterTriggerSetState().
{
if (state->numstates >= state->numalloc)
{
int newalloc = state->numalloc * 2;
newalloc = Max(newalloc, 8); /* in case original has size 0 */
state = (SetConstraintState)
repalloc(state,
sizeof(SetConstraintStateData) +
(newalloc - 1) *sizeof(SetConstraintTriggerData));
state->numalloc = newalloc;
Assert(state->numstates < state->numalloc);
}
state->trigstates[state->numstates].sct_tgoid = tgoid;
state->trigstates[state->numstates].sct_tgisdeferred = tgisdeferred;
state->numstates++;
return state;
}
| static SetConstraintState SetConstraintStateCopy | ( | SetConstraintState | state | ) | [static] |
Definition at line 4105 of file trigger.c.
References SetConstraintStateData::all_isdeferred, SetConstraintStateData::all_isset, SetConstraintStateData::numstates, SetConstraintStateCreate(), and SetConstraintStateData::trigstates.
Referenced by AfterTriggerSetState().
{
SetConstraintState state;
state = SetConstraintStateCreate(origstate->numstates);
state->all_isset = origstate->all_isset;
state->all_isdeferred = origstate->all_isdeferred;
state->numstates = origstate->numstates;
memcpy(state->trigstates, origstate->trigstates,
origstate->numstates * sizeof(SetConstraintTriggerData));
return state;
}
| static SetConstraintState SetConstraintStateCreate | ( | int | numalloc | ) | [static] |
Definition at line 4080 of file trigger.c.
References MemoryContextAllocZero(), SetConstraintStateData::numalloc, and TopTransactionContext.
Referenced by AfterTriggerBeginXact(), and SetConstraintStateCopy().
{
SetConstraintState state;
/* Behave sanely with numalloc == 0 */
if (numalloc <= 0)
numalloc = 1;
/*
* We assume that zeroing will correctly initialize the state values.
*/
state = (SetConstraintState)
MemoryContextAllocZero(TopTransactionContext,
sizeof(SetConstraintStateData) +
(numalloc - 1) *sizeof(SetConstraintTriggerData));
state->numalloc = numalloc;
return state;
}
| static void SetTriggerFlags | ( | TriggerDesc * | trigdesc, | |
| Trigger * | trigger | |||
| ) | [static] |
Definition at line 1570 of file trigger.c.
References Trigger::tgtype, TriggerDesc::trig_delete_after_row, TriggerDesc::trig_delete_after_statement, TriggerDesc::trig_delete_before_row, TriggerDesc::trig_delete_before_statement, TriggerDesc::trig_delete_instead_row, TriggerDesc::trig_insert_after_row, TriggerDesc::trig_insert_after_statement, TriggerDesc::trig_insert_before_row, TriggerDesc::trig_insert_before_statement, TriggerDesc::trig_insert_instead_row, TriggerDesc::trig_truncate_after_statement, TriggerDesc::trig_truncate_before_statement, TriggerDesc::trig_update_after_row, TriggerDesc::trig_update_after_statement, TriggerDesc::trig_update_before_row, TriggerDesc::trig_update_before_statement, TriggerDesc::trig_update_instead_row, TRIGGER_TYPE_AFTER, TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE, TRIGGER_TYPE_INSERT, TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_MATCHES, TRIGGER_TYPE_ROW, TRIGGER_TYPE_STATEMENT, TRIGGER_TYPE_TRUNCATE, and TRIGGER_TYPE_UPDATE.
Referenced by RelationBuildTriggers().
{
int16 tgtype = trigger->tgtype;
trigdesc->trig_insert_before_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT);
trigdesc->trig_insert_after_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT);
trigdesc->trig_insert_instead_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_INSERT);
trigdesc->trig_insert_before_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT);
trigdesc->trig_insert_after_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT);
trigdesc->trig_update_before_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_UPDATE);
trigdesc->trig_update_after_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_UPDATE);
trigdesc->trig_update_instead_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_UPDATE);
trigdesc->trig_update_before_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_UPDATE);
trigdesc->trig_update_after_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_UPDATE);
trigdesc->trig_delete_before_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE);
trigdesc->trig_delete_after_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_DELETE);
trigdesc->trig_delete_instead_row |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_ROW,
TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_DELETE);
trigdesc->trig_delete_before_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE);
trigdesc->trig_delete_after_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_DELETE);
/* there are no row-level truncate triggers */
trigdesc->trig_truncate_before_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_TRUNCATE);
trigdesc->trig_truncate_after_statement |=
TRIGGER_TYPE_MATCHES(tgtype, TRIGGER_TYPE_STATEMENT,
TRIGGER_TYPE_AFTER, TRIGGER_TYPE_TRUNCATE);
}
| static bool TriggerEnabled | ( | EState * | estate, | |
| ResultRelInfo * | relinfo, | |||
| Trigger * | trigger, | |||
| TriggerEvent | event, | |||
| Bitmapset * | modifiedCols, | |||
| HeapTuple | oldtup, | |||
| HeapTuple | newtup | |||
| ) | [static] |
Definition at line 2728 of file trigger.c.
References Assert, bms_is_member(), ChangeVarNodes(), ExprContext::ecxt_innertuple, ExprContext::ecxt_outertuple, EState::es_query_cxt, EState::es_trig_newtup_slot, EState::es_trig_oldtup_slot, ExecInitExtraTupleSlot(), ExecPrepareExpr(), ExecQual(), ExecSetSlotDescriptor(), ExecStoreTuple(), FirstLowInvalidHeapAttributeNumber, GetPerTupleExprContext, HeapTupleIsValid, INNER_VAR, InvalidBuffer, make_ands_implicit(), MemoryContextSwitchTo(), NIL, NULL, OUTER_VAR, PRS2_NEW_VARNO, PRS2_OLD_VARNO, RelationGetDescr, ResultRelInfo::ri_RelationDesc, ResultRelInfo::ri_TrigDesc, ResultRelInfo::ri_TrigWhenExprs, SESSION_REPLICATION_ROLE_REPLICA, SessionReplicationRole, stringToNode(), Trigger::tgattr, Trigger::tgenabled, Trigger::tgnattr, Trigger::tgqual, TRIGGER_DISABLED, TRIGGER_FIRED_BY_UPDATE, TRIGGER_FIRES_ON_ORIGIN, TRIGGER_FIRES_ON_REPLICA, TriggerDesc::triggers, and TupleTableSlot::tts_tupleDescriptor.
Referenced by AfterTriggerSaveEvent(), ExecBRDeleteTriggers(), ExecBRInsertTriggers(), ExecBRUpdateTriggers(), ExecBSDeleteTriggers(), ExecBSInsertTriggers(), ExecBSTruncateTriggers(), ExecBSUpdateTriggers(), ExecIRDeleteTriggers(), ExecIRInsertTriggers(), and ExecIRUpdateTriggers().
{
/* Check replication-role-dependent enable state */
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
{
if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
trigger->tgenabled == TRIGGER_DISABLED)
return false;
}
else /* ORIGIN or LOCAL role */
{
if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
trigger->tgenabled == TRIGGER_DISABLED)
return false;
}
/*
* Check for column-specific trigger (only possible for UPDATE, and in
* fact we *must* ignore tgattr for other event types)
*/
if (trigger->tgnattr > 0 && TRIGGER_FIRED_BY_UPDATE(event))
{
int i;
bool modified;
modified = false;
for (i = 0; i < trigger->tgnattr; i++)
{
if (bms_is_member(trigger->tgattr[i] - FirstLowInvalidHeapAttributeNumber,
modifiedCols))
{
modified = true;
break;
}
}
if (!modified)
return false;
}
/* Check for WHEN clause */
if (trigger->tgqual)
{
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
List **predicate;
ExprContext *econtext;
TupleTableSlot *oldslot = NULL;
TupleTableSlot *newslot = NULL;
MemoryContext oldContext;
int i;
Assert(estate != NULL);
/*
* trigger is an element of relinfo->ri_TrigDesc->triggers[]; find the
* matching element of relinfo->ri_TrigWhenExprs[]
*/
i = trigger - relinfo->ri_TrigDesc->triggers;
predicate = &relinfo->ri_TrigWhenExprs[i];
/*
* If first time through for this WHEN expression, build expression
* nodetrees for it. Keep them in the per-query memory context so
* they'll survive throughout the query.
*/
if (*predicate == NIL)
{
Node *tgqual;
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
tgqual = stringToNode(trigger->tgqual);
/* Change references to OLD and NEW to INNER_VAR and OUTER_VAR */
ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER_VAR, 0);
ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
/* ExecQual wants implicit-AND form */
tgqual = (Node *) make_ands_implicit((Expr *) tgqual);
*predicate = (List *) ExecPrepareExpr((Expr *) tgqual, estate);
MemoryContextSwitchTo(oldContext);
}
/*
* We will use the EState's per-tuple context for evaluating WHEN
* expressions (creating it if it's not already there).
*/
econtext = GetPerTupleExprContext(estate);
/*
* Put OLD and NEW tuples into tupleslots for expression evaluation.
* These slots can be shared across the whole estate, but be careful
* that they have the current resultrel's tupdesc.
*/
if (HeapTupleIsValid(oldtup))
{
if (estate->es_trig_oldtup_slot == NULL)
{
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
estate->es_trig_oldtup_slot = ExecInitExtraTupleSlot(estate);
MemoryContextSwitchTo(oldContext);
}
oldslot = estate->es_trig_oldtup_slot;
if (oldslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(oldslot, tupdesc);
ExecStoreTuple(oldtup, oldslot, InvalidBuffer, false);
}
if (HeapTupleIsValid(newtup))
{
if (estate->es_trig_newtup_slot == NULL)
{
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
estate->es_trig_newtup_slot = ExecInitExtraTupleSlot(estate);
MemoryContextSwitchTo(oldContext);
}
newslot = estate->es_trig_newtup_slot;
if (newslot->tts_tupleDescriptor != tupdesc)
ExecSetSlotDescriptor(newslot, tupdesc);
ExecStoreTuple(newtup, newslot, InvalidBuffer, false);
}
/*
* Finally evaluate the expression, making the old and/or new tuples
* available as INNER_VAR/OUTER_VAR respectively.
*/
econtext->ecxt_innertuple = oldslot;
econtext->ecxt_outertuple = newslot;
if (!ExecQual(*predicate, econtext, false))
return false;
}
return true;
}
AfterTriggers afterTriggers [static] |
int MyTriggerDepth = 0 [static] |
Definition at line 64 of file trigger.c.
Referenced by ExecCallTriggerFunc(), and pg_trigger_depth().
| int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN |
Definition at line 61 of file trigger.c.
Referenced by assign_session_replication_role(), filter_event_trigger(), matchLocks(), and TriggerEnabled().
1.7.1