mirror of https://github.com/postgres/postgres
Commit 3855968f32
added syntax, pg_dump,
psql support, and documentation, but the triggers didn't actually fire.
With this commit, they now do. This is still a pretty basic facility
overall because event triggers do not get a whole lot of information
about what the user is trying to do unless you write them in C; and
there's still no option to fire them anywhere except at the very
beginning of the execution sequence, but it's better than nothing,
and a good building block for future work.
Along the way, add a regression test for ALTER LARGE OBJECT, since
testing of event triggers reveals that we haven't got one.
Dimitri Fontaine and Robert Haas
pull/3/head
parent
be86e3dd5b
commit
3a0e4d36eb
@ -0,0 +1,242 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* evtcache.c |
||||
* Special-purpose cache for event trigger data. |
||||
* |
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* src/backend/utils/cache/evtcache.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/genam.h" |
||||
#include "access/heapam.h" |
||||
#include "catalog/pg_event_trigger.h" |
||||
#include "catalog/indexing.h" |
||||
#include "catalog/pg_type.h" |
||||
#include "commands/trigger.h" |
||||
#include "utils/array.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/evtcache.h" |
||||
#include "utils/inval.h" |
||||
#include "utils/memutils.h" |
||||
#include "utils/hsearch.h" |
||||
#include "utils/rel.h" |
||||
#include "utils/snapmgr.h" |
||||
#include "utils/syscache.h" |
||||
|
||||
typedef struct |
||||
{ |
||||
EventTriggerEvent event; |
||||
List *triggerlist; |
||||
} EventTriggerCacheEntry; |
||||
|
||||
static HTAB *EventTriggerCache; |
||||
static MemoryContext EventTriggerCacheContext; |
||||
|
||||
static void BuildEventTriggerCache(void); |
||||
static void InvalidateEventCacheCallback(Datum arg, |
||||
int cacheid, uint32 hashvalue); |
||||
static int DecodeTextArrayToCString(Datum array, char ***cstringp); |
||||
|
||||
/*
|
||||
* Search the event cache by trigger event. |
||||
* |
||||
* Note that the caller had better copy any data it wants to keep around |
||||
* across any operation that might touch a system catalog into some other |
||||
* memory context, since a cache reset could blow the return value away. |
||||
*/ |
||||
List * |
||||
EventCacheLookup(EventTriggerEvent event) |
||||
{ |
||||
EventTriggerCacheEntry *entry; |
||||
|
||||
if (EventTriggerCache == NULL) |
||||
BuildEventTriggerCache(); |
||||
entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL); |
||||
return entry != NULL ? entry->triggerlist : NULL; |
||||
} |
||||
|
||||
/*
|
||||
* Rebuild the event trigger cache. |
||||
*/ |
||||
static void |
||||
BuildEventTriggerCache(void) |
||||
{ |
||||
HASHCTL ctl; |
||||
HTAB *cache; |
||||
MemoryContext oldcontext; |
||||
Relation rel; |
||||
Relation irel; |
||||
SysScanDesc scan; |
||||
|
||||
if (EventTriggerCacheContext != NULL) |
||||
{ |
||||
/*
|
||||
* The cache has been previously built, and subsequently invalidated, |
||||
* and now we're trying to rebuild it. Normally, there won't be |
||||
* anything in the context at this point, because the invalidation |
||||
* will have already reset it. But if the previous attempt to rebuild |
||||
* the cache failed, then there might be some junk lying around |
||||
* that we want to reclaim. |
||||
*/ |
||||
MemoryContextReset(EventTriggerCacheContext); |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* This is our first time attempting to build the cache, so we need |
||||
* to set up the memory context and register a syscache callback to |
||||
* capture future invalidation events. |
||||
*/ |
||||
if (CacheMemoryContext == NULL) |
||||
CreateCacheMemoryContext(); |
||||
EventTriggerCacheContext = |
||||
AllocSetContextCreate(CacheMemoryContext, |
||||
"EventTriggerCache", |
||||
ALLOCSET_DEFAULT_MINSIZE, |
||||
ALLOCSET_DEFAULT_INITSIZE, |
||||
ALLOCSET_DEFAULT_MAXSIZE); |
||||
CacheRegisterSyscacheCallback(EVENTTRIGGEROID, |
||||
InvalidateEventCacheCallback, |
||||
(Datum) 0); |
||||
} |
||||
|
||||
/* Switch to correct memory context. */ |
||||
oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext); |
||||
|
||||
/*
|
||||
* Create a new hash table, but don't assign it to the global variable |
||||
* until it accurately represents the state of the catalogs, so that |
||||
* if we fail midway through this we won't end up with incorrect cache |
||||
* contents. |
||||
*/ |
||||
MemSet(&ctl, 0, sizeof(ctl)); |
||||
ctl.keysize = sizeof(EventTriggerEvent); |
||||
ctl.entrysize = sizeof(EventTriggerCacheEntry); |
||||
ctl.hash = tag_hash; |
||||
cache = hash_create("Event Trigger Cache", 32, &ctl, |
||||
HASH_ELEM | HASH_FUNCTION); |
||||
|
||||
/*
|
||||
* Prepare to scan pg_event_trigger in name order. We use an MVCC |
||||
* snapshot to avoid getting inconsistent results if the table is |
||||
* being concurrently updated. |
||||
*/ |
||||
rel = relation_open(EventTriggerRelationId, AccessShareLock); |
||||
irel = index_open(EventTriggerNameIndexId, AccessShareLock); |
||||
scan = systable_beginscan_ordered(rel, irel, GetLatestSnapshot(), 0, NULL); |
||||
|
||||
/*
|
||||
* Build a cache item for each pg_event_trigger tuple, and append each |
||||
* one to the appropriate cache entry. |
||||
*/ |
||||
for (;;) |
||||
{ |
||||
HeapTuple tup; |
||||
Form_pg_event_trigger form; |
||||
char *evtevent; |
||||
EventTriggerEvent event; |
||||
EventTriggerCacheItem *item; |
||||
Datum evttags; |
||||
bool evttags_isnull; |
||||
EventTriggerCacheEntry *entry; |
||||
bool found; |
||||
|
||||
/* Get next tuple. */ |
||||
tup = systable_getnext_ordered(scan, ForwardScanDirection); |
||||
if (!HeapTupleIsValid(tup)) |
||||
break; |
||||
|
||||
/* Skip trigger if disabled. */ |
||||
form = (Form_pg_event_trigger) GETSTRUCT(tup); |
||||
if (form->evtenabled == TRIGGER_DISABLED) |
||||
continue; |
||||
|
||||
/* Decode event name. */ |
||||
evtevent = NameStr(form->evtevent); |
||||
if (strcmp(evtevent, "ddl_command_start") == 0) |
||||
event = EVT_DDLCommandStart; |
||||
else |
||||
continue; |
||||
|
||||
/* Allocate new cache item. */ |
||||
item = palloc0(sizeof(EventTriggerCacheItem)); |
||||
item->fnoid = form->evtfoid; |
||||
item->enabled = form->evtenabled; |
||||
|
||||
/* Decode and sort tags array. */ |
||||
evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags, |
||||
RelationGetDescr(rel), &evttags_isnull); |
||||
if (!evttags_isnull) |
||||
{ |
||||
item->ntags = DecodeTextArrayToCString(evttags, &item->tag); |
||||
qsort(item->tag, item->ntags, sizeof(char *), pg_qsort_strcmp); |
||||
} |
||||
|
||||
/* Add to cache entry. */ |
||||
entry = hash_search(cache, &event, HASH_ENTER, &found); |
||||
if (found) |
||||
entry->triggerlist = lappend(entry->triggerlist, item); |
||||
else |
||||
entry->triggerlist = list_make1(item); |
||||
} |
||||
|
||||
/* Done with pg_event_trigger scan. */ |
||||
systable_endscan_ordered(scan); |
||||
index_close(irel, AccessShareLock); |
||||
relation_close(rel, AccessShareLock); |
||||
|
||||
/* Restore previous memory context. */ |
||||
MemoryContextSwitchTo(oldcontext); |
||||
|
||||
/* Cache is now valid. */ |
||||
EventTriggerCache = cache; |
||||
} |
||||
|
||||
/*
|
||||
* Decode text[] to an array of C strings. |
||||
* |
||||
* We could avoid a bit of overhead here if we were willing to duplicate some |
||||
* of the logic from deconstruct_array, but it doesn't seem worth the code |
||||
* complexity. |
||||
*/ |
||||
static int |
||||
DecodeTextArrayToCString(Datum array, char ***cstringp) |
||||
{ |
||||
ArrayType *arr = DatumGetArrayTypeP(array); |
||||
Datum *elems; |
||||
char **cstring; |
||||
int i; |
||||
int nelems; |
||||
|
||||
if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID) |
||||
elog(ERROR, "expected 1-D text array"); |
||||
deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems); |
||||
|
||||
cstring = palloc(nelems * sizeof(char *)); |
||||
for (i = 0; i < nelems; ++i) |
||||
cstring[i] = TextDatumGetCString(elems[i]); |
||||
|
||||
pfree(elems); |
||||
*cstringp = cstring; |
||||
return nelems; |
||||
} |
||||
|
||||
/*
|
||||
* Flush all cache entries when pg_event_trigger is updated. |
||||
* |
||||
* This should be rare enough that we don't need to be very granular about |
||||
* it, so we just blow away everything, which also avoids the possibility of |
||||
* memory leaks. |
||||
*/ |
||||
static void |
||||
InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue) |
||||
{ |
||||
MemoryContextReset(EventTriggerCacheContext); |
||||
EventTriggerCache = NULL; |
||||
} |
@ -0,0 +1,34 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* evtcache.c |
||||
* Special-purpose cache for event trigger data. |
||||
* |
||||
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* src/backend/utils/cache/evtcache.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#ifndef EVTCACHE_H |
||||
#define EVTCACHE_H |
||||
|
||||
#include "nodes/pg_list.h" |
||||
|
||||
typedef enum |
||||
{ |
||||
EVT_DDLCommandStart |
||||
} EventTriggerEvent; |
||||
|
||||
typedef struct |
||||
{ |
||||
Oid fnoid; /* function to be called */ |
||||
char enabled; /* as SESSION_REPLICATION_ROLE_* */ |
||||
int ntags; /* number of command tags */ |
||||
char **tag; /* command tags in SORTED order */ |
||||
} EventTriggerCacheItem; |
||||
|
||||
extern List *EventCacheLookup(EventTriggerEvent event); |
||||
|
||||
#endif /* EVTCACHE_H */ |
Loading…
Reference in new issue