/*-------------------------------------------------------------------------
*
* evtcache . c
* Special - purpose cache for event trigger data .
*
* Portions Copyright ( c ) 1996 - 2025 , 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" ,
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,
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;
}
Messung V0.5 in Prozent C=95 H=94 G=94
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland