diff options
Diffstat (limited to 'src/backend/utils/cache/evtcache.c')
-rw-r--r-- | src/backend/utils/cache/evtcache.c | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c new file mode 100644 index 00000000000..565dc449d16 --- /dev/null +++ b/src/backend/utils/cache/evtcache.c @@ -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; +} |