Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * evtcache.c
4 : * Special-purpose cache for event trigger data.
5 : *
6 : * Portions Copyright (c) 1996-2024, 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 852334 : EventCacheLookup(EventTriggerEvent event)
64 : {
65 : EventTriggerCacheEntry *entry;
66 :
67 852334 : if (EventTriggerCacheState != ETCS_VALID)
68 6248 : BuildEventTriggerCache();
69 852334 : entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
70 852334 : return entry != NULL ? entry->triggerlist : NIL;
71 : }
72 :
73 : /*
74 : * Rebuild the event trigger cache.
75 : */
76 : static void
77 6248 : BuildEventTriggerCache(void)
78 : {
79 : HASHCTL ctl;
80 : HTAB *cache;
81 : MemoryContext oldcontext;
82 : Relation rel;
83 : Relation irel;
84 : SysScanDesc scan;
85 :
86 6248 : 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 724 : 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 5524 : if (CacheMemoryContext == NULL)
103 0 : CreateCacheMemoryContext();
104 5524 : EventTriggerCacheContext =
105 5524 : AllocSetContextCreate(CacheMemoryContext,
106 : "EventTriggerCache",
107 : ALLOCSET_DEFAULT_SIZES);
108 5524 : CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
109 : InvalidateEventCacheCallback,
110 : (Datum) 0);
111 : }
112 :
113 : /* Switch to correct memory context. */
114 6248 : oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
115 :
116 : /* Prevent the memory context from being nuked while we're rebuilding. */
117 6248 : EventTriggerCacheState = ETCS_REBUILD_STARTED;
118 :
119 : /* Create new hash table. */
120 6248 : ctl.keysize = sizeof(EventTriggerEvent);
121 6248 : ctl.entrysize = sizeof(EventTriggerCacheEntry);
122 6248 : ctl.hcxt = EventTriggerCacheContext;
123 6248 : cache = hash_create("EventTriggerCacheHash", 32, &ctl,
124 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
125 :
126 : /*
127 : * Prepare to scan pg_event_trigger in name order.
128 : */
129 6248 : rel = relation_open(EventTriggerRelationId, AccessShareLock);
130 6248 : irel = index_open(EventTriggerNameIndexId, AccessShareLock);
131 6248 : scan = systable_beginscan_ordered(rel, irel, NULL, 0, NULL);
132 :
133 : /*
134 : * Build a cache item for each pg_event_trigger tuple, and append each one
135 : * to the appropriate cache entry.
136 : */
137 : for (;;)
138 684 : {
139 : HeapTuple tup;
140 : Form_pg_event_trigger form;
141 : char *evtevent;
142 : EventTriggerEvent event;
143 : EventTriggerCacheItem *item;
144 : Datum evttags;
145 : bool evttags_isnull;
146 : EventTriggerCacheEntry *entry;
147 : bool found;
148 :
149 : /* Get next tuple. */
150 6932 : tup = systable_getnext_ordered(scan, ForwardScanDirection);
151 6932 : if (!HeapTupleIsValid(tup))
152 6248 : break;
153 :
154 : /* Skip trigger if disabled. */
155 684 : form = (Form_pg_event_trigger) GETSTRUCT(tup);
156 684 : if (form->evtenabled == TRIGGER_DISABLED)
157 34 : continue;
158 :
159 : /* Decode event name. */
160 650 : evtevent = NameStr(form->evtevent);
161 650 : if (strcmp(evtevent, "ddl_command_start") == 0)
162 86 : event = EVT_DDLCommandStart;
163 564 : else if (strcmp(evtevent, "ddl_command_end") == 0)
164 170 : event = EVT_DDLCommandEnd;
165 394 : else if (strcmp(evtevent, "sql_drop") == 0)
166 116 : event = EVT_SQLDrop;
167 278 : else if (strcmp(evtevent, "table_rewrite") == 0)
168 16 : event = EVT_TableRewrite;
169 262 : else if (strcmp(evtevent, "login") == 0)
170 262 : event = EVT_Login;
171 : else
172 0 : continue;
173 :
174 : /* Allocate new cache item. */
175 650 : item = palloc0(sizeof(EventTriggerCacheItem));
176 650 : item->fnoid = form->evtfoid;
177 650 : item->enabled = form->evtenabled;
178 :
179 : /* Decode and sort tags array. */
180 650 : evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
181 : RelationGetDescr(rel), &evttags_isnull);
182 650 : if (!evttags_isnull)
183 180 : item->tagset = DecodeTextArrayToBitmapset(evttags);
184 :
185 : /* Add to cache entry. */
186 650 : entry = hash_search(cache, &event, HASH_ENTER, &found);
187 650 : if (found)
188 42 : entry->triggerlist = lappend(entry->triggerlist, item);
189 : else
190 608 : entry->triggerlist = list_make1(item);
191 : }
192 :
193 : /* Done with pg_event_trigger scan. */
194 6248 : systable_endscan_ordered(scan);
195 6248 : index_close(irel, AccessShareLock);
196 6248 : relation_close(rel, AccessShareLock);
197 :
198 : /* Restore previous memory context. */
199 6248 : MemoryContextSwitchTo(oldcontext);
200 :
201 : /* Install new cache. */
202 6248 : 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 6248 : if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
211 6248 : EventTriggerCacheState = ETCS_VALID;
212 6248 : }
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 180 : DecodeTextArrayToBitmapset(Datum array)
223 : {
224 180 : ArrayType *arr = DatumGetArrayTypeP(array);
225 : Datum *elems;
226 : Bitmapset *bms;
227 : int i;
228 : int nelems;
229 :
230 180 : if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
231 0 : elog(ERROR, "expected 1-D text array");
232 180 : deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems);
233 :
234 462 : for (bms = NULL, i = 0; i < nelems; ++i)
235 : {
236 282 : char *str = TextDatumGetCString(elems[i]);
237 :
238 282 : bms = bms_add_member(bms, GetCommandTagEnum(str));
239 282 : pfree(str);
240 : }
241 :
242 180 : pfree(elems);
243 :
244 180 : return bms;
245 : }
246 :
247 : /*
248 : * Flush all cache entries when pg_event_trigger is updated.
249 : *
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
252 : * memory leaks.
253 : */
254 : static void
255 1600 : InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
256 : {
257 : /*
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.
261 : */
262 1600 : if (EventTriggerCacheState == ETCS_VALID)
263 : {
264 778 : MemoryContextReset(EventTriggerCacheContext);
265 778 : EventTriggerCache = NULL;
266 : }
267 :
268 : /* Mark cache for rebuild. */
269 1600 : EventTriggerCacheState = ETCS_NEEDS_REBUILD;
270 1600 : }
|