LCOV - code coverage report
Current view: top level - src/backend/utils/cache - evtcache.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 96.2 % 78 75
Test Date: 2026-03-04 04:14:49 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * evtcache.c
       4              :  *    Special-purpose cache for event trigger data.
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  * IDENTIFICATION
      10              :  *    src/backend/utils/cache/evtcache.c
      11              :  *
      12              :  *-------------------------------------------------------------------------
      13              :  */
      14              : #include "postgres.h"
      15              : 
      16              : #include "access/genam.h"
      17              : #include "access/htup_details.h"
      18              : #include "access/relation.h"
      19              : #include "catalog/pg_event_trigger.h"
      20              : #include "catalog/pg_type.h"
      21              : #include "commands/trigger.h"
      22              : #include "tcop/cmdtag.h"
      23              : #include "utils/array.h"
      24              : #include "utils/builtins.h"
      25              : #include "utils/catcache.h"
      26              : #include "utils/evtcache.h"
      27              : #include "utils/hsearch.h"
      28              : #include "utils/inval.h"
      29              : #include "utils/memutils.h"
      30              : #include "utils/rel.h"
      31              : #include "utils/syscache.h"
      32              : 
      33              : typedef enum
      34              : {
      35              :     ETCS_NEEDS_REBUILD,
      36              :     ETCS_REBUILD_STARTED,
      37              :     ETCS_VALID,
      38              : } EventTriggerCacheStateType;
      39              : 
      40              : typedef struct
      41              : {
      42              :     EventTriggerEvent event;
      43              :     List       *triggerlist;
      44              : } EventTriggerCacheEntry;
      45              : 
      46              : static HTAB *EventTriggerCache;
      47              : static MemoryContext EventTriggerCacheContext;
      48              : static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD;
      49              : 
      50              : static void BuildEventTriggerCache(void);
      51              : static void InvalidateEventCacheCallback(Datum arg,
      52              :                                          SysCacheIdentifier cacheid,
      53              :                                          uint32 hashvalue);
      54              : static Bitmapset *DecodeTextArrayToBitmapset(Datum array);
      55              : 
      56              : /*
      57              :  * Search the event cache by trigger event.
      58              :  *
      59              :  * Note that the caller had better copy any data it wants to keep around
      60              :  * across any operation that might touch a system catalog into some other
      61              :  * memory context, since a cache reset could blow the return value away.
      62              :  */
      63              : List *
      64       481619 : EventCacheLookup(EventTriggerEvent event)
      65              : {
      66              :     EventTriggerCacheEntry *entry;
      67              : 
      68       481619 :     if (EventTriggerCacheState != ETCS_VALID)
      69         3621 :         BuildEventTriggerCache();
      70       481619 :     entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
      71       481619 :     return entry != NULL ? entry->triggerlist : NIL;
      72              : }
      73              : 
      74              : /*
      75              :  * Rebuild the event trigger cache.
      76              :  */
      77              : static void
      78         3621 : BuildEventTriggerCache(void)
      79              : {
      80              :     HASHCTL     ctl;
      81              :     HTAB       *cache;
      82              :     Relation    rel;
      83              :     Relation    irel;
      84              :     SysScanDesc scan;
      85              : 
      86         3621 :     if (EventTriggerCacheContext != NULL)
      87              :     {
      88              :         /*
      89              :          * Free up any memory already allocated in EventTriggerCacheContext.
      90              :          * This can happen either because a previous rebuild failed, or
      91              :          * because an invalidation happened before the rebuild was complete.
      92              :          */
      93          361 :         MemoryContextReset(EventTriggerCacheContext);
      94              :     }
      95              :     else
      96              :     {
      97              :         /*
      98              :          * This is our first time attempting to build the cache, so we need to
      99              :          * set up the memory context and register a syscache callback to
     100              :          * capture future invalidation events.
     101              :          */
     102         3260 :         if (CacheMemoryContext == NULL)
     103            0 :             CreateCacheMemoryContext();
     104         3260 :         EventTriggerCacheContext =
     105         3260 :             AllocSetContextCreate(CacheMemoryContext,
     106              :                                   "EventTriggerCache",
     107              :                                   ALLOCSET_DEFAULT_SIZES);
     108         3260 :         CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
     109              :                                       InvalidateEventCacheCallback,
     110              :                                       (Datum) 0);
     111              :     }
     112              : 
     113              :     /* Prevent the memory context from being nuked while we're rebuilding. */
     114         3621 :     EventTriggerCacheState = ETCS_REBUILD_STARTED;
     115              : 
     116              :     /* Create new hash table. */
     117         3621 :     ctl.keysize = sizeof(EventTriggerEvent);
     118         3621 :     ctl.entrysize = sizeof(EventTriggerCacheEntry);
     119         3621 :     ctl.hcxt = EventTriggerCacheContext;
     120         3621 :     cache = hash_create("EventTriggerCacheHash", 32, &ctl,
     121              :                         HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
     122              : 
     123              :     /*
     124              :      * Prepare to scan pg_event_trigger in name order.
     125              :      */
     126         3621 :     rel = relation_open(EventTriggerRelationId, AccessShareLock);
     127         3621 :     irel = index_open(EventTriggerNameIndexId, AccessShareLock);
     128         3621 :     scan = systable_beginscan_ordered(rel, irel, NULL, 0, NULL);
     129              : 
     130              :     /*
     131              :      * Build a cache item for each pg_event_trigger tuple, and append each one
     132              :      * to the appropriate cache entry.
     133              :      */
     134              :     for (;;)
     135          354 :     {
     136              :         HeapTuple   tup;
     137              :         Form_pg_event_trigger form;
     138              :         char       *evtevent;
     139              :         EventTriggerEvent event;
     140              :         EventTriggerCacheItem *item;
     141              :         Datum       evttags;
     142              :         bool        evttags_isnull;
     143              :         EventTriggerCacheEntry *entry;
     144              :         bool        found;
     145              :         MemoryContext oldcontext;
     146              : 
     147              :         /* Get next tuple. */
     148         3975 :         tup = systable_getnext_ordered(scan, ForwardScanDirection);
     149         3975 :         if (!HeapTupleIsValid(tup))
     150         3621 :             break;
     151              : 
     152              :         /* Skip trigger if disabled. */
     153          354 :         form = (Form_pg_event_trigger) GETSTRUCT(tup);
     154          354 :         if (form->evtenabled == TRIGGER_DISABLED)
     155           17 :             continue;
     156              : 
     157              :         /* Decode event name. */
     158          337 :         evtevent = NameStr(form->evtevent);
     159          337 :         if (strcmp(evtevent, "ddl_command_start") == 0)
     160           44 :             event = EVT_DDLCommandStart;
     161          293 :         else if (strcmp(evtevent, "ddl_command_end") == 0)
     162           87 :             event = EVT_DDLCommandEnd;
     163          206 :         else if (strcmp(evtevent, "sql_drop") == 0)
     164           61 :             event = EVT_SQLDrop;
     165          145 :         else if (strcmp(evtevent, "table_rewrite") == 0)
     166            8 :             event = EVT_TableRewrite;
     167          137 :         else if (strcmp(evtevent, "login") == 0)
     168          137 :             event = EVT_Login;
     169              :         else
     170            0 :             continue;
     171              : 
     172              :         /* Switch to correct memory context. */
     173          337 :         oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
     174              : 
     175              :         /* Allocate new cache item. */
     176          337 :         item = palloc0_object(EventTriggerCacheItem);
     177          337 :         item->fnoid = form->evtfoid;
     178          337 :         item->enabled = form->evtenabled;
     179              : 
     180              :         /* Decode and sort tags array. */
     181          337 :         evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
     182              :                                RelationGetDescr(rel), &evttags_isnull);
     183          337 :         if (!evttags_isnull)
     184           93 :             item->tagset = DecodeTextArrayToBitmapset(evttags);
     185              : 
     186              :         /* Add to cache entry. */
     187          337 :         entry = hash_search(cache, &event, HASH_ENTER, &found);
     188          337 :         if (found)
     189           21 :             entry->triggerlist = lappend(entry->triggerlist, item);
     190              :         else
     191          316 :             entry->triggerlist = list_make1(item);
     192              : 
     193              :         /* Restore previous memory context. */
     194          337 :         MemoryContextSwitchTo(oldcontext);
     195              :     }
     196              : 
     197              :     /* Done with pg_event_trigger scan. */
     198         3621 :     systable_endscan_ordered(scan);
     199         3621 :     index_close(irel, AccessShareLock);
     200         3621 :     relation_close(rel, AccessShareLock);
     201              : 
     202              :     /* Install new cache. */
     203         3621 :     EventTriggerCache = cache;
     204              : 
     205              :     /*
     206              :      * If the cache has been invalidated since we entered this routine, we
     207              :      * still use and return the cache we just finished constructing, to avoid
     208              :      * infinite loops, but we leave the cache marked stale so that we'll
     209              :      * rebuild it again on next access.  Otherwise, we mark the cache valid.
     210              :      */
     211         3621 :     if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
     212         3621 :         EventTriggerCacheState = ETCS_VALID;
     213         3621 : }
     214              : 
     215              : /*
     216              :  * Decode text[] to a Bitmapset of CommandTags.
     217              :  *
     218              :  * We could avoid a bit of overhead here if we were willing to duplicate some
     219              :  * of the logic from deconstruct_array, but it doesn't seem worth the code
     220              :  * complexity.
     221              :  */
     222              : static Bitmapset *
     223           93 : DecodeTextArrayToBitmapset(Datum array)
     224              : {
     225           93 :     ArrayType  *arr = DatumGetArrayTypeP(array);
     226              :     Datum      *elems;
     227              :     Bitmapset  *bms;
     228              :     int         i;
     229              :     int         nelems;
     230              : 
     231           93 :     if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
     232            0 :         elog(ERROR, "expected 1-D text array");
     233           93 :     deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems);
     234              : 
     235          237 :     for (bms = NULL, i = 0; i < nelems; ++i)
     236              :     {
     237          144 :         char       *str = TextDatumGetCString(elems[i]);
     238              : 
     239          144 :         bms = bms_add_member(bms, GetCommandTagEnum(str));
     240          144 :         pfree(str);
     241              :     }
     242              : 
     243           93 :     pfree(elems);
     244           93 :     if (arr != DatumGetPointer(array))
     245           93 :         pfree(arr);
     246              : 
     247           93 :     return bms;
     248              : }
     249              : 
     250              : /*
     251              :  * Flush all cache entries when pg_event_trigger is updated.
     252              :  *
     253              :  * This should be rare enough that we don't need to be very granular about
     254              :  * it, so we just blow away everything, which also avoids the possibility of
     255              :  * memory leaks.
     256              :  */
     257              : static void
     258          804 : InvalidateEventCacheCallback(Datum arg, SysCacheIdentifier cacheid,
     259              :                              uint32 hashvalue)
     260              : {
     261              :     /*
     262              :      * If the cache isn't valid, then there might be a rebuild in progress, so
     263              :      * we can't immediately blow it away.  But it's advantageous to do this
     264              :      * when possible, so as to immediately free memory.
     265              :      */
     266          804 :     if (EventTriggerCacheState == ETCS_VALID)
     267              :     {
     268          387 :         MemoryContextReset(EventTriggerCacheContext);
     269          387 :         EventTriggerCache = NULL;
     270              :     }
     271              : 
     272              :     /* Mark cache for rebuild. */
     273          804 :     EventTriggerCacheState = ETCS_NEEDS_REBUILD;
     274          804 : }
        

Generated by: LCOV version 2.0-1