Branch data 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 : : int cacheid, uint32 hashvalue);
53 : : static Bitmapset *DecodeTextArrayToBitmapset(Datum array);
54 : :
55 : : /*
56 : : * Search the event cache by trigger event.
57 : : *
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.
61 : : */
62 : : List *
63 : 77608 : EventCacheLookup(EventTriggerEvent event)
64 : : {
65 : 77608 : EventTriggerCacheEntry *entry;
66 : :
67 [ + + ]: 77608 : if (EventTriggerCacheState != ETCS_VALID)
68 : 327 : BuildEventTriggerCache();
69 : 77608 : entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
70 [ + + ]: 77608 : return entry != NULL ? entry->triggerlist : NIL;
71 : 77608 : }
72 : :
73 : : /*
74 : : * Rebuild the event trigger cache.
75 : : */
76 : : static void
77 : 327 : BuildEventTriggerCache(void)
78 : : {
79 : 327 : HASHCTL ctl;
80 : 327 : HTAB *cache;
81 : 327 : Relation rel;
82 : 327 : Relation irel;
83 : 327 : SysScanDesc scan;
84 : :
85 [ + + ]: 327 : if (EventTriggerCacheContext != NULL)
86 : : {
87 : : /*
88 : : * Free up any memory already allocated in EventTriggerCacheContext.
89 : : * This can happen either because a previous rebuild failed, or
90 : : * because an invalidation happened before the rebuild was complete.
91 : : */
92 : 73 : MemoryContextReset(EventTriggerCacheContext);
93 : 73 : }
94 : : else
95 : : {
96 : : /*
97 : : * This is our first time attempting to build the cache, so we need to
98 : : * set up the memory context and register a syscache callback to
99 : : * capture future invalidation events.
100 : : */
101 [ + - ]: 254 : if (CacheMemoryContext == NULL)
102 : 0 : CreateCacheMemoryContext();
103 : 254 : EventTriggerCacheContext =
104 : 254 : AllocSetContextCreate(CacheMemoryContext,
105 : : "EventTriggerCache",
106 : : ALLOCSET_DEFAULT_SIZES);
107 : 254 : CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
108 : : InvalidateEventCacheCallback,
109 : : (Datum) 0);
110 : : }
111 : :
112 : : /* Prevent the memory context from being nuked while we're rebuilding. */
113 : 327 : EventTriggerCacheState = ETCS_REBUILD_STARTED;
114 : :
115 : : /* Create new hash table. */
116 : 327 : ctl.keysize = sizeof(EventTriggerEvent);
117 : 327 : ctl.entrysize = sizeof(EventTriggerCacheEntry);
118 : 327 : ctl.hcxt = EventTriggerCacheContext;
119 : 327 : cache = hash_create("EventTriggerCacheHash", 32, &ctl,
120 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
121 : :
122 : : /*
123 : : * Prepare to scan pg_event_trigger in name order.
124 : : */
125 : 327 : rel = relation_open(EventTriggerRelationId, AccessShareLock);
126 : 327 : irel = index_open(EventTriggerNameIndexId, AccessShareLock);
127 : 327 : scan = systable_beginscan_ordered(rel, irel, NULL, 0, NULL);
128 : :
129 : : /*
130 : : * Build a cache item for each pg_event_trigger tuple, and append each one
131 : : * to the appropriate cache entry.
132 : : */
133 : 384 : for (;;)
134 : : {
135 : 389 : HeapTuple tup;
136 : 389 : Form_pg_event_trigger form;
137 : 389 : char *evtevent;
138 : 389 : EventTriggerEvent event;
139 : 389 : EventTriggerCacheItem *item;
140 : 389 : Datum evttags;
141 : 389 : bool evttags_isnull;
142 : 389 : EventTriggerCacheEntry *entry;
143 : 389 : bool found;
144 : 389 : MemoryContext oldcontext;
145 : :
146 : : /* Get next tuple. */
147 : 389 : tup = systable_getnext_ordered(scan, ForwardScanDirection);
148 [ + + ]: 389 : if (!HeapTupleIsValid(tup))
149 : 327 : break;
150 : :
151 : : /* Skip trigger if disabled. */
152 : 62 : form = (Form_pg_event_trigger) GETSTRUCT(tup);
153 [ + + ]: 62 : if (form->evtenabled == TRIGGER_DISABLED)
154 : 5 : continue;
155 : :
156 : : /* Decode event name. */
157 : 57 : evtevent = NameStr(form->evtevent);
158 [ + + ]: 57 : if (strcmp(evtevent, "ddl_command_start") == 0)
159 : 13 : event = EVT_DDLCommandStart;
160 [ + + ]: 44 : else if (strcmp(evtevent, "ddl_command_end") == 0)
161 : 20 : event = EVT_DDLCommandEnd;
162 [ + + ]: 24 : else if (strcmp(evtevent, "sql_drop") == 0)
163 : 20 : event = EVT_SQLDrop;
164 [ + + ]: 4 : else if (strcmp(evtevent, "table_rewrite") == 0)
165 : 2 : event = EVT_TableRewrite;
166 [ - + ]: 2 : else if (strcmp(evtevent, "login") == 0)
167 : 2 : event = EVT_Login;
168 : : else
169 : 0 : continue;
170 : :
171 : : /* Switch to correct memory context. */
172 : 57 : oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
173 : :
174 : : /* Allocate new cache item. */
175 : 57 : item = palloc0_object(EventTriggerCacheItem);
176 : 57 : item->fnoid = form->evtfoid;
177 : 57 : item->enabled = form->evtenabled;
178 : :
179 : : /* Decode and sort tags array. */
180 : 114 : evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
181 : 57 : RelationGetDescr(rel), &evttags_isnull);
182 [ + + ]: 57 : if (!evttags_isnull)
183 : 31 : item->tagset = DecodeTextArrayToBitmapset(evttags);
184 : :
185 : : /* Add to cache entry. */
186 : 57 : entry = hash_search(cache, &event, HASH_ENTER, &found);
187 [ + + ]: 57 : if (found)
188 : 7 : entry->triggerlist = lappend(entry->triggerlist, item);
189 : : else
190 : 50 : entry->triggerlist = list_make1(item);
191 : :
192 : : /* Restore previous memory context. */
193 : 57 : MemoryContextSwitchTo(oldcontext);
194 [ - + + + ]: 389 : }
195 : :
196 : : /* Done with pg_event_trigger scan. */
197 : 327 : systable_endscan_ordered(scan);
198 : 327 : index_close(irel, AccessShareLock);
199 : 327 : relation_close(rel, AccessShareLock);
200 : :
201 : : /* Install new cache. */
202 : 327 : EventTriggerCache = cache;
203 : :
204 : : /*
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.
209 : : */
210 [ - + ]: 327 : if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
211 : 327 : EventTriggerCacheState = ETCS_VALID;
212 : 327 : }
213 : :
214 : : /*
215 : : * Decode text[] to a Bitmapset of CommandTags.
216 : : *
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
219 : : * complexity.
220 : : */
221 : : static Bitmapset *
222 : 31 : DecodeTextArrayToBitmapset(Datum array)
223 : : {
224 : 31 : ArrayType *arr = DatumGetArrayTypeP(array);
225 : 31 : Datum *elems;
226 : 31 : Bitmapset *bms;
227 : 31 : int i;
228 : 31 : int nelems;
229 : :
230 [ + - ]: 31 : if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
231 [ # # # # ]: 0 : elog(ERROR, "expected 1-D text array");
232 : 31 : deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems);
233 : :
234 [ + + ]: 79 : for (bms = NULL, i = 0; i < nelems; ++i)
235 : : {
236 : 48 : char *str = TextDatumGetCString(elems[i]);
237 : :
238 : 48 : bms = bms_add_member(bms, GetCommandTagEnum(str));
239 : 48 : pfree(str);
240 : 48 : }
241 : :
242 : 31 : pfree(elems);
243 [ - + ]: 31 : if (arr != DatumGetPointer(array))
244 : 31 : pfree(arr);
245 : :
246 : 62 : return bms;
247 : 31 : }
248 : :
249 : : /*
250 : : * Flush all cache entries when pg_event_trigger is updated.
251 : : *
252 : : * This should be rare enough that we don't need to be very granular about
253 : : * it, so we just blow away everything, which also avoids the possibility of
254 : : * memory leaks.
255 : : */
256 : : static void
257 : 135 : InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
258 : : {
259 : : /*
260 : : * If the cache isn't valid, then there might be a rebuild in progress, so
261 : : * we can't immediately blow it away. But it's advantageous to do this
262 : : * when possible, so as to immediately free memory.
263 : : */
264 [ + + ]: 135 : if (EventTriggerCacheState == ETCS_VALID)
265 : : {
266 : 76 : MemoryContextReset(EventTriggerCacheContext);
267 : 76 : EventTriggerCache = NULL;
268 : 76 : }
269 : :
270 : : /* Mark cache for rebuild. */
271 : 135 : EventTriggerCacheState = ETCS_NEEDS_REBUILD;
272 : 135 : }
|