sepgsql: update TAP test to use fat comma style
[pgsql.git] / src / backend / utils / cache / evtcache.c
blobce596bf563856c95e6af49f5ec7902da8ad845a9
1 /*-------------------------------------------------------------------------
3 * evtcache.c
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
9 * IDENTIFICATION
10 * src/backend/utils/cache/evtcache.c
12 *-------------------------------------------------------------------------
14 #include "postgres.h"
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"
33 typedef enum
35 ETCS_NEEDS_REBUILD,
36 ETCS_REBUILD_STARTED,
37 ETCS_VALID,
38 } EventTriggerCacheStateType;
40 typedef struct
42 EventTriggerEvent event;
43 List *triggerlist;
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.
62 List *
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.
76 static void
77 BuildEventTriggerCache(void)
79 HASHCTL ctl;
80 HTAB *cache;
81 MemoryContext oldcontext;
82 Relation rel;
83 Relation irel;
84 SysScanDesc scan;
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);
95 else
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,
106 "EventTriggerCache",
107 ALLOCSET_DEFAULT_SIZES);
108 CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
109 InvalidateEventCacheCallback,
110 (Datum) 0);
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.
137 for (;;)
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;
149 /* Get next tuple. */
150 tup = systable_getnext_ordered(scan, ForwardScanDirection);
151 if (!HeapTupleIsValid(tup))
152 break;
154 /* Skip trigger if disabled. */
155 form = (Form_pg_event_trigger) GETSTRUCT(tup);
156 if (form->evtenabled == TRIGGER_DISABLED)
157 continue;
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)
166 event = EVT_SQLDrop;
167 else if (strcmp(evtevent, "table_rewrite") == 0)
168 event = EVT_TableRewrite;
169 else if (strcmp(evtevent, "login") == 0)
170 event = EVT_Login;
171 else
172 continue;
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);
182 if (!evttags_isnull)
183 item->tagset = DecodeTextArrayToBitmapset(evttags);
185 /* Add to cache entry. */
186 entry = hash_search(cache, &event, HASH_ENTER, &found);
187 if (found)
188 entry->triggerlist = lappend(entry->triggerlist, item);
189 else
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
219 * complexity.
221 static Bitmapset *
222 DecodeTextArrayToBitmapset(Datum array)
224 ArrayType *arr = DatumGetArrayTypeP(array);
225 Datum *elems;
226 Bitmapset *bms;
227 int i;
228 int nelems;
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));
239 pfree(str);
242 pfree(elems);
244 return bms;
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
252 * memory leaks.
254 static void
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;