Age Owner Branch data TLA 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 *
4986 rhaas@postgresql.org 64 :CBC 471724 : EventCacheLookup(EventTriggerEvent event)
65 : : {
66 : : EventTriggerCacheEntry *entry;
67 : :
4967 68 [ + + ]: 471724 : if (EventTriggerCacheState != ETCS_VALID)
4986 69 : 3599 : BuildEventTriggerCache();
70 : 471724 : entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
3204 71 [ + + ]: 471724 : return entry != NULL ? entry->triggerlist : NIL;
72 : : }
73 : :
74 : : /*
75 : : * Rebuild the event trigger cache.
76 : : */
77 : : static void
4986 78 : 3599 : BuildEventTriggerCache(void)
79 : : {
80 : : HASHCTL ctl;
81 : : HTAB *cache;
82 : : Relation rel;
83 : : Relation irel;
84 : : SysScanDesc scan;
85 : :
86 [ + + ]: 3599 : 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 : : */
851 nathan@postgresql.or 93 : 371 : 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 : : */
4986 rhaas@postgresql.org 102 [ - + ]: 3228 : if (CacheMemoryContext == NULL)
4986 rhaas@postgresql.org 103 :UBC 0 : CreateCacheMemoryContext();
4986 rhaas@postgresql.org 104 :CBC 3228 : EventTriggerCacheContext =
105 : 3228 : AllocSetContextCreate(CacheMemoryContext,
106 : : "EventTriggerCache",
107 : : ALLOCSET_DEFAULT_SIZES);
108 : 3228 : CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
109 : : InvalidateEventCacheCallback,
110 : : (Datum) 0);
111 : : }
112 : :
113 : : /* Prevent the memory context from being nuked while we're rebuilding. */
4967 114 : 3599 : EventTriggerCacheState = ETCS_REBUILD_STARTED;
115 : :
116 : : /* Create new hash table. */
4986 117 : 3599 : ctl.keysize = sizeof(EventTriggerEvent);
118 : 3599 : ctl.entrysize = sizeof(EventTriggerCacheEntry);
4968 119 : 3599 : ctl.hcxt = EventTriggerCacheContext;
984 dgustafsson@postgres 120 : 3599 : 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 : : */
4986 rhaas@postgresql.org 126 : 3599 : rel = relation_open(EventTriggerRelationId, AccessShareLock);
127 : 3599 : irel = index_open(EventTriggerNameIndexId, AccessShareLock);
4639 128 : 3599 : 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 (;;)
4986 135 : 356 : {
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 : 3955 : tup = systable_getnext_ordered(scan, ForwardScanDirection);
149 [ + + ]: 3955 : if (!HeapTupleIsValid(tup))
150 : 3599 : break;
151 : :
152 : : /* Skip trigger if disabled. */
153 : 356 : form = (Form_pg_event_trigger) GETSTRUCT(tup);
154 [ + + ]: 356 : if (form->evtenabled == TRIGGER_DISABLED)
155 : 17 : continue;
156 : :
157 : : /* Decode event name. */
158 : 339 : evtevent = NameStr(form->evtevent);
159 [ + + ]: 339 : if (strcmp(evtevent, "ddl_command_start") == 0)
160 : 44 : event = EVT_DDLCommandStart;
4801 161 [ + + ]: 295 : else if (strcmp(evtevent, "ddl_command_end") == 0)
162 : 88 : event = EVT_DDLCommandEnd;
4736 alvherre@alvh.no-ip. 163 [ + + ]: 207 : else if (strcmp(evtevent, "sql_drop") == 0)
164 : 61 : event = EVT_SQLDrop;
4115 simon@2ndQuadrant.co 165 [ + + ]: 146 : else if (strcmp(evtevent, "table_rewrite") == 0)
166 : 8 : event = EVT_TableRewrite;
881 akorotkov@postgresql 167 [ + - ]: 138 : else if (strcmp(evtevent, "login") == 0)
168 : 138 : event = EVT_Login;
169 : : else
4986 rhaas@postgresql.org 170 :UBC 0 : continue;
171 : :
172 : : /* Switch to correct memory context. */
225 tgl@sss.pgh.pa.us 173 :GNC 339 : oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
174 : :
175 : : /* Allocate new cache item. */
95 michael@paquier.xyz 176 : 339 : item = palloc0_object(EventTriggerCacheItem);
4986 rhaas@postgresql.org 177 :CBC 339 : item->fnoid = form->evtfoid;
178 : 339 : item->enabled = form->evtenabled;
179 : :
180 : : /* Decode and sort tags array. */
181 : 339 : evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
182 : : RelationGetDescr(rel), &evttags_isnull);
183 [ + + ]: 339 : if (!evttags_isnull)
2204 alvherre@alvh.no-ip. 184 : 93 : item->tagset = DecodeTextArrayToBitmapset(evttags);
185 : :
186 : : /* Add to cache entry. */
4986 rhaas@postgresql.org 187 : 339 : entry = hash_search(cache, &event, HASH_ENTER, &found);
188 [ + + ]: 339 : if (found)
189 : 21 : entry->triggerlist = lappend(entry->triggerlist, item);
190 : : else
191 : 318 : entry->triggerlist = list_make1(item);
192 : :
193 : : /* Restore previous memory context. */
225 tgl@sss.pgh.pa.us 194 :GNC 339 : MemoryContextSwitchTo(oldcontext);
195 : : }
196 : :
197 : : /* Done with pg_event_trigger scan. */
4986 rhaas@postgresql.org 198 :CBC 3599 : systable_endscan_ordered(scan);
199 : 3599 : index_close(irel, AccessShareLock);
200 : 3599 : relation_close(rel, AccessShareLock);
201 : :
202 : : /* Install new cache. */
203 : 3599 : 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 : : */
4967 211 [ + - ]: 3599 : if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
212 : 3599 : EventTriggerCacheState = ETCS_VALID;
4986 213 : 3599 : }
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 *
2204 alvherre@alvh.no-ip. 223 : 93 : DecodeTextArrayToBitmapset(Datum array)
224 : : {
4986 rhaas@postgresql.org 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)
- + ]
4986 rhaas@postgresql.org 232 [ # # ]:UBC 0 : elog(ERROR, "expected 1-D text array");
1353 peter@eisentraut.org 233 :CBC 93 : deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems);
234 : :
2204 alvherre@alvh.no-ip. 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 : :
4986 rhaas@postgresql.org 243 : 93 : pfree(elems);
101 peter@eisentraut.org 244 [ + - ]:GNC 93 : if (arr != DatumGetPointer(array))
225 tgl@sss.pgh.pa.us 245 : 93 : pfree(arr);
246 : :
2204 alvherre@alvh.no-ip. 247 :CBC 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
25 michael@paquier.xyz 258 :GNC 818 : 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 : : */
4967 rhaas@postgresql.org 266 [ + + ]:CBC 818 : if (EventTriggerCacheState == ETCS_VALID)
267 : : {
851 nathan@postgresql.or 268 : 396 : MemoryContextReset(EventTriggerCacheContext);
4967 rhaas@postgresql.org 269 : 396 : EventTriggerCache = NULL;
270 : : }
271 : :
272 : : /* Mark cache for rebuild. */
273 : 818 : EventTriggerCacheState = ETCS_NEEDS_REBUILD;
4986 274 : 818 : }
|