postgresql/src/backend/utils/cache/evtcache.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

271 lines
7.5 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* evtcache.c
* Special-purpose cache for event trigger data.
*
* Portions Copyright (c) 1996-2024, 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/htup_details.h"
#include "access/relation.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "tcop/cmdtag.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/evtcache.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
typedef enum
{
ETCS_NEEDS_REBUILD,
ETCS_REBUILD_STARTED,
ETCS_VALID,
} EventTriggerCacheStateType;
typedef struct
{
EventTriggerEvent event;
List *triggerlist;
} EventTriggerCacheEntry;
static HTAB *EventTriggerCache;
static MemoryContext EventTriggerCacheContext;
static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD;
static void BuildEventTriggerCache(void);
static void InvalidateEventCacheCallback(Datum arg,
int cacheid, uint32 hashvalue);
static Bitmapset *DecodeTextArrayToBitmapset(Datum array);
/*
* 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 (EventTriggerCacheState != ETCS_VALID)
BuildEventTriggerCache();
entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
return entry != NULL ? entry->triggerlist : NIL;
}
/*
* Rebuild the event trigger cache.
*/
static void
BuildEventTriggerCache(void)
{
HASHCTL ctl;
HTAB *cache;
MemoryContext oldcontext;
Relation rel;
Relation irel;
SysScanDesc scan;
if (EventTriggerCacheContext != NULL)
{
/*
* Free up any memory already allocated in EventTriggerCacheContext.
* This can happen either because a previous rebuild failed, or
* because an invalidation happened before the rebuild was complete.
*/
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",
Add macros to make AllocSetContextCreate() calls simpler and safer. I found that half a dozen (nearly 5%) of our AllocSetContextCreate calls had typos in the context-sizing parameters. While none of these led to especially significant problems, they did create minor inefficiencies, and it's now clear that expecting people to copy-and-paste those calls accurately is not a great idea. Let's reduce the risk of future errors by introducing single macros that encapsulate the common use-cases. Three such macros are enough to cover all but two special-purpose contexts; those two calls can be left as-is, I think. While this patch doesn't in itself improve matters for third-party extensions, it doesn't break anything for them either, and they can gradually adopt the simplified notation over time. In passing, change TopMemoryContext to use the default allocation parameters. Formerly it could only be extended 8K at a time. That was probably reasonable when this code was written; but nowadays we create many more contexts than we did then, so that it's not unusual to have a couple hundred K in TopMemoryContext, even without considering various dubious code that sticks other things there. There seems no good reason not to let it use growing blocks like most other contexts. Back-patch to 9.6, mostly because that's still close enough to HEAD that it's easy to do so, and keeping the branches in sync can be expected to avoid some future back-patching pain. The bugs fixed by these changes don't seem to be significant enough to justify fixing them further back. Discussion: <21072.1472321324@sss.pgh.pa.us>
2016-08-27 23:50:38 +02:00
ALLOCSET_DEFAULT_SIZES);
CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
InvalidateEventCacheCallback,
(Datum) 0);
}
/* Switch to correct memory context. */
oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
/* Prevent the memory context from being nuked while we're rebuilding. */
EventTriggerCacheState = ETCS_REBUILD_STARTED;
/* Create new hash table. */
ctl.keysize = sizeof(EventTriggerEvent);
ctl.entrysize = sizeof(EventTriggerCacheEntry);
ctl.hcxt = EventTriggerCacheContext;
cache = hash_create("EventTriggerCacheHash", 32, &ctl,
Improve hash_create's API for selecting simple-binary-key hash functions. Previously, if you wanted anything besides C-string hash keys, you had to specify a custom hashing function to hash_create(). Nearly all such callers were specifying tag_hash or oid_hash; which is tedious, and rather error-prone, since a caller could easily miss the opportunity to optimize by using hash_uint32 when appropriate. Replace this with a design whereby callers using simple binary-data keys just specify HASH_BLOBS and don't need to mess with specific support functions. hash_create() itself will take care of optimizing when the key size is four bytes. This nets out saving a few hundred bytes of code space, and offers a measurable performance improvement in tidbitmap.c (which was not exploiting the opportunity to use hash_uint32 for its 4-byte keys). There might be some wins elsewhere too, I didn't analyze closely. In future we could look into offering a similar optimized hashing function for 8-byte keys. Under this design that could be done in a centralized and machine-independent fashion, whereas getting it right for keys of platform-dependent sizes would've been notationally painful before. For the moment, the old way still works fine, so as not to break source code compatibility for loadable modules. Eventually we might want to remove tag_hash and friends from the exported API altogether, since there's no real need for them to be explicitly referenced from outside dynahash.c. Teodor Sigaev and Tom Lane
2014-12-18 19:36:29 +01:00
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
/*
* Prepare to scan pg_event_trigger in name order.
*/
rel = relation_open(EventTriggerRelationId, AccessShareLock);
irel = index_open(EventTriggerNameIndexId, AccessShareLock);
scan = systable_beginscan_ordered(rel, irel, NULL, 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 if (strcmp(evtevent, "ddl_command_end") == 0)
event = EVT_DDLCommandEnd;
else if (strcmp(evtevent, "sql_drop") == 0)
event = EVT_SQLDrop;
else if (strcmp(evtevent, "table_rewrite") == 0)
event = EVT_TableRewrite;
else if (strcmp(evtevent, "login") == 0)
event = EVT_Login;
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->tagset = DecodeTextArrayToBitmapset(evttags);
/* 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);
/* Install new cache. */
EventTriggerCache = cache;
/*
* If the cache has been invalidated since we entered this routine, we
* still use and return the cache we just finished constructing, to avoid
* infinite loops, but we leave the cache marked stale so that we'll
* rebuild it again on next access. Otherwise, we mark the cache valid.
*/
if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
EventTriggerCacheState = ETCS_VALID;
}
/*
* Decode text[] to a Bitmapset of CommandTags.
*
* 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 Bitmapset *
DecodeTextArrayToBitmapset(Datum array)
{
ArrayType *arr = DatumGetArrayTypeP(array);
Datum *elems;
Bitmapset *bms;
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_builtin(arr, TEXTOID, &elems, NULL, &nelems);
for (bms = NULL, i = 0; i < nelems; ++i)
{
char *str = TextDatumGetCString(elems[i]);
bms = bms_add_member(bms, GetCommandTagEnum(str));
pfree(str);
}
pfree(elems);
return bms;
}
/*
* 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)
{
/*
* If the cache isn't valid, then there might be a rebuild in progress, so
* we can't immediately blow it away. But it's advantageous to do this
* when possible, so as to immediately free memory.
*/
if (EventTriggerCacheState == ETCS_VALID)
{
MemoryContextReset(EventTriggerCacheContext);
EventTriggerCache = NULL;
}
/* Mark cache for rebuild. */
EventTriggerCacheState = ETCS_NEEDS_REBUILD;
}