mirror of
https://github.com/postgres/postgres.git
synced 2025-06-14 18:42:34 +03:00
Make new event trigger facility actually do something.
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
This commit is contained in:
4
src/backend/utils/cache/Makefile
vendored
4
src/backend/utils/cache/Makefile
vendored
@ -12,7 +12,7 @@ subdir = src/backend/utils/cache
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
|
||||
OBJS = attoptcache.o catcache.o inval.o plancache.o relcache.o relmapper.o \
|
||||
spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
|
||||
OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o relcache.o \
|
||||
relmapper.o spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
|
||||
|
||||
include $(top_srcdir)/src/backend/common.mk
|
||||
|
242
src/backend/utils/cache/evtcache.c
vendored
Normal file
242
src/backend/utils/cache/evtcache.c
vendored
Normal file
@ -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;
|
||||
}
|
Reference in New Issue
Block a user