1 /*-------------------------------------------------------------------------
4 * Special-purpose cache for event trigger data.
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
10 * src/backend/utils/cache/evtcache.c
12 *-------------------------------------------------------------------------
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"
38 } EventTriggerCacheStateType
;
42 EventTriggerEvent event
;
44 } EventTriggerCacheEntry
;
46 static HTAB
*EventTriggerCache
;
47 static MemoryContext EventTriggerCacheContext
;
48 static EventTriggerCacheStateType EventTriggerCacheState
= ETCS_NEEDS_REBUILD
;
50 static void BuildEventTriggerCache(void);
51 static void InvalidateEventCacheCallback(Datum arg
,
52 int cacheid
, uint32 hashvalue
);
53 static Bitmapset
*DecodeTextArrayToBitmapset(Datum array
);
56 * Search the event cache by trigger event.
58 * Note that the caller had better copy any data it wants to keep around
59 * across any operation that might touch a system catalog into some other
60 * memory context, since a cache reset could blow the return value away.
63 EventCacheLookup(EventTriggerEvent event
)
65 EventTriggerCacheEntry
*entry
;
67 if (EventTriggerCacheState
!= ETCS_VALID
)
68 BuildEventTriggerCache();
69 entry
= hash_search(EventTriggerCache
, &event
, HASH_FIND
, NULL
);
70 return entry
!= NULL
? entry
->triggerlist
: NIL
;
74 * Rebuild the event trigger cache.
77 BuildEventTriggerCache(void)
81 MemoryContext oldcontext
;
86 if (EventTriggerCacheContext
!= NULL
)
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.
93 MemoryContextReset(EventTriggerCacheContext
);
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.
102 if (CacheMemoryContext
== NULL
)
103 CreateCacheMemoryContext();
104 EventTriggerCacheContext
=
105 AllocSetContextCreate(CacheMemoryContext
,
107 ALLOCSET_DEFAULT_SIZES
);
108 CacheRegisterSyscacheCallback(EVENTTRIGGEROID
,
109 InvalidateEventCacheCallback
,
113 /* Switch to correct memory context. */
114 oldcontext
= MemoryContextSwitchTo(EventTriggerCacheContext
);
116 /* Prevent the memory context from being nuked while we're rebuilding. */
117 EventTriggerCacheState
= ETCS_REBUILD_STARTED
;
119 /* Create new hash table. */
120 ctl
.keysize
= sizeof(EventTriggerEvent
);
121 ctl
.entrysize
= sizeof(EventTriggerCacheEntry
);
122 ctl
.hcxt
= EventTriggerCacheContext
;
123 cache
= hash_create("EventTriggerCacheHash", 32, &ctl
,
124 HASH_ELEM
| HASH_BLOBS
| HASH_CONTEXT
);
127 * Prepare to scan pg_event_trigger in name order.
129 rel
= relation_open(EventTriggerRelationId
, AccessShareLock
);
130 irel
= index_open(EventTriggerNameIndexId
, AccessShareLock
);
131 scan
= systable_beginscan_ordered(rel
, irel
, NULL
, 0, NULL
);
134 * Build a cache item for each pg_event_trigger tuple, and append each one
135 * to the appropriate cache entry.
140 Form_pg_event_trigger form
;
142 EventTriggerEvent event
;
143 EventTriggerCacheItem
*item
;
146 EventTriggerCacheEntry
*entry
;
149 /* Get next tuple. */
150 tup
= systable_getnext_ordered(scan
, ForwardScanDirection
);
151 if (!HeapTupleIsValid(tup
))
154 /* Skip trigger if disabled. */
155 form
= (Form_pg_event_trigger
) GETSTRUCT(tup
);
156 if (form
->evtenabled
== TRIGGER_DISABLED
)
159 /* Decode event name. */
160 evtevent
= NameStr(form
->evtevent
);
161 if (strcmp(evtevent
, "ddl_command_start") == 0)
162 event
= EVT_DDLCommandStart
;
163 else if (strcmp(evtevent
, "ddl_command_end") == 0)
164 event
= EVT_DDLCommandEnd
;
165 else if (strcmp(evtevent
, "sql_drop") == 0)
167 else if (strcmp(evtevent
, "table_rewrite") == 0)
168 event
= EVT_TableRewrite
;
169 else if (strcmp(evtevent
, "login") == 0)
174 /* Allocate new cache item. */
175 item
= palloc0(sizeof(EventTriggerCacheItem
));
176 item
->fnoid
= form
->evtfoid
;
177 item
->enabled
= form
->evtenabled
;
179 /* Decode and sort tags array. */
180 evttags
= heap_getattr(tup
, Anum_pg_event_trigger_evttags
,
181 RelationGetDescr(rel
), &evttags_isnull
);
183 item
->tagset
= DecodeTextArrayToBitmapset(evttags
);
185 /* Add to cache entry. */
186 entry
= hash_search(cache
, &event
, HASH_ENTER
, &found
);
188 entry
->triggerlist
= lappend(entry
->triggerlist
, item
);
190 entry
->triggerlist
= list_make1(item
);
193 /* Done with pg_event_trigger scan. */
194 systable_endscan_ordered(scan
);
195 index_close(irel
, AccessShareLock
);
196 relation_close(rel
, AccessShareLock
);
198 /* Restore previous memory context. */
199 MemoryContextSwitchTo(oldcontext
);
201 /* Install new cache. */
202 EventTriggerCache
= cache
;
205 * If the cache has been invalidated since we entered this routine, we
206 * still use and return the cache we just finished constructing, to avoid
207 * infinite loops, but we leave the cache marked stale so that we'll
208 * rebuild it again on next access. Otherwise, we mark the cache valid.
210 if (EventTriggerCacheState
== ETCS_REBUILD_STARTED
)
211 EventTriggerCacheState
= ETCS_VALID
;
215 * Decode text[] to a Bitmapset of CommandTags.
217 * We could avoid a bit of overhead here if we were willing to duplicate some
218 * of the logic from deconstruct_array, but it doesn't seem worth the code
222 DecodeTextArrayToBitmapset(Datum array
)
224 ArrayType
*arr
= DatumGetArrayTypeP(array
);
230 if (ARR_NDIM(arr
) != 1 || ARR_HASNULL(arr
) || ARR_ELEMTYPE(arr
) != TEXTOID
)
231 elog(ERROR
, "expected 1-D text array");
232 deconstruct_array_builtin(arr
, TEXTOID
, &elems
, NULL
, &nelems
);
234 for (bms
= NULL
, i
= 0; i
< nelems
; ++i
)
236 char *str
= TextDatumGetCString(elems
[i
]);
238 bms
= bms_add_member(bms
, GetCommandTagEnum(str
));
248 * Flush all cache entries when pg_event_trigger is updated.
250 * This should be rare enough that we don't need to be very granular about
251 * it, so we just blow away everything, which also avoids the possibility of
255 InvalidateEventCacheCallback(Datum arg
, int cacheid
, uint32 hashvalue
)
258 * If the cache isn't valid, then there might be a rebuild in progress, so
259 * we can't immediately blow it away. But it's advantageous to do this
260 * when possible, so as to immediately free memory.
262 if (EventTriggerCacheState
== ETCS_VALID
)
264 MemoryContextReset(EventTriggerCacheContext
);
265 EventTriggerCache
= NULL
;
268 /* Mark cache for rebuild. */
269 EventTriggerCacheState
= ETCS_NEEDS_REBUILD
;