postgresql/src/backend/utils/cache/evtcache.c
Michael Paquier ee642cccc4 Switch SysCacheIdentifier to a typedef enum
The main purpose of this change is to allow an ABI checker to understand
when the list of SysCacheIdentifier changes, by switching all the
routine declarations that relied on a signed integer for a syscache ID
to this new type.  This is going to be useful in the long-term for
versions newer than v19 so as we will be able to check when the list of
values in SysCacheIdentifier is updated in a non-ABI compliant fashion.

Most of the changes of this commit are due to the new definition of
SyscacheCallbackFunction, where a SysCacheIdentifier is now required for
the syscache ID.  It is a mechanical change, still slightly invasive.

There are more areas in the tree that could be improved with an ABI
checker in mind; this takes care of only one area.

Reported-by: Tom Lane <tgl@sss.pgh.pa.us>
Author: Andreas Karlsson <andreas@proxel.se>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/289125.1770913057@sss.pgh.pa.us
2026-02-18 09:58:38 +09:00

274 lines
7.6 KiB
C

/*-------------------------------------------------------------------------
*
* evtcache.c
* Special-purpose cache for event trigger data.
*
* Portions Copyright (c) 1996-2026, 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,
SysCacheIdentifier 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;
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);
}
/* 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;
MemoryContext oldcontext;
/* 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;
/* Switch to correct memory context. */
oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
/* Allocate new cache item. */
item = palloc0_object(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);
/* Restore previous memory context. */
MemoryContextSwitchTo(oldcontext);
}
/* Done with pg_event_trigger scan. */
systable_endscan_ordered(scan);
index_close(irel, AccessShareLock);
relation_close(rel, AccessShareLock);
/* 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);
if (arr != DatumGetPointer(array))
pfree(arr);
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, SysCacheIdentifier 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;
}