Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * evtcache.c
4 : * Special-purpose cache for event trigger data.
5 : *
6 : * Portions Copyright (c) 1996-2023, 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/snapmgr.h"
32 : #include "utils/syscache.h"
33 :
34 : typedef enum
35 : {
36 : ETCS_NEEDS_REBUILD,
37 : ETCS_REBUILD_STARTED,
38 : ETCS_VALID
39 : } EventTriggerCacheStateType;
40 :
41 : typedef struct
42 : {
43 : EventTriggerEvent event;
44 : List *triggerlist;
45 : } EventTriggerCacheEntry;
46 :
47 : static HTAB *EventTriggerCache;
48 : static MemoryContext EventTriggerCacheContext;
49 : static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD;
50 :
51 : static void BuildEventTriggerCache(void);
52 : static void InvalidateEventCacheCallback(Datum arg,
53 : int cacheid, 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 *
3915 rhaas 64 CBC 931926 : EventCacheLookup(EventTriggerEvent event)
65 : {
66 : EventTriggerCacheEntry *entry;
67 :
3896 68 931926 : if (EventTriggerCacheState != ETCS_VALID)
3915 69 2896 : BuildEventTriggerCache();
70 931926 : entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
2133 71 931926 : return entry != NULL ? entry->triggerlist : NIL;
72 : }
73 :
74 : /*
75 : * Rebuild the event trigger cache.
76 : */
77 : static void
3915 78 2896 : BuildEventTriggerCache(void)
79 : {
80 : HASHCTL ctl;
81 : HTAB *cache;
82 : MemoryContext oldcontext;
83 : Relation rel;
84 : Relation irel;
85 : SysScanDesc scan;
86 :
87 2896 : if (EventTriggerCacheContext != NULL)
88 : {
89 : /*
90 : * Free up any memory already allocated in EventTriggerCacheContext.
91 : * This can happen either because a previous rebuild failed, or
92 : * because an invalidation happened before the rebuild was complete.
93 : */
3897 94 330 : MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
95 : }
96 : else
97 : {
98 : /*
99 : * This is our first time attempting to build the cache, so we need to
100 : * set up the memory context and register a syscache callback to
101 : * capture future invalidation events.
102 : */
3915 103 2566 : if (CacheMemoryContext == NULL)
3915 rhaas 104 UBC 0 : CreateCacheMemoryContext();
3915 rhaas 105 CBC 2566 : EventTriggerCacheContext =
106 2566 : AllocSetContextCreate(CacheMemoryContext,
107 : "EventTriggerCache",
108 : ALLOCSET_DEFAULT_SIZES);
109 2566 : CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
110 : InvalidateEventCacheCallback,
111 : (Datum) 0);
112 : }
113 :
114 : /* Switch to correct memory context. */
115 2896 : oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
116 :
117 : /* Prevent the memory context from being nuked while we're rebuilding. */
3896 118 2896 : EventTriggerCacheState = ETCS_REBUILD_STARTED;
119 :
120 : /* Create new hash table. */
3915 121 2896 : ctl.keysize = sizeof(EventTriggerEvent);
122 2896 : ctl.entrysize = sizeof(EventTriggerCacheEntry);
3897 123 2896 : ctl.hcxt = EventTriggerCacheContext;
3915 124 2896 : cache = hash_create("Event Trigger Cache", 32, &ctl,
125 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
126 :
127 : /*
128 : * Prepare to scan pg_event_trigger in name order.
129 : */
130 2896 : rel = relation_open(EventTriggerRelationId, AccessShareLock);
131 2896 : irel = index_open(EventTriggerNameIndexId, AccessShareLock);
3568 132 2896 : scan = systable_beginscan_ordered(rel, irel, NULL, 0, NULL);
133 :
134 : /*
135 : * Build a cache item for each pg_event_trigger tuple, and append each one
136 : * to the appropriate cache entry.
137 : */
138 : for (;;)
3915 139 145 : {
140 : HeapTuple tup;
141 : Form_pg_event_trigger form;
142 : char *evtevent;
143 : EventTriggerEvent event;
144 : EventTriggerCacheItem *item;
145 : Datum evttags;
146 : bool evttags_isnull;
147 : EventTriggerCacheEntry *entry;
148 : bool found;
149 :
150 : /* Get next tuple. */
151 3041 : tup = systable_getnext_ordered(scan, ForwardScanDirection);
152 3041 : if (!HeapTupleIsValid(tup))
153 2896 : break;
154 :
155 : /* Skip trigger if disabled. */
156 145 : form = (Form_pg_event_trigger) GETSTRUCT(tup);
157 145 : if (form->evtenabled == TRIGGER_DISABLED)
158 14 : continue;
159 :
160 : /* Decode event name. */
161 131 : evtevent = NameStr(form->evtevent);
162 131 : if (strcmp(evtevent, "ddl_command_start") == 0)
163 34 : event = EVT_DDLCommandStart;
3730 164 97 : else if (strcmp(evtevent, "ddl_command_end") == 0)
165 61 : event = EVT_DDLCommandEnd;
3665 alvherre 166 36 : else if (strcmp(evtevent, "sql_drop") == 0)
167 28 : event = EVT_SQLDrop;
3044 simon 168 8 : else if (strcmp(evtevent, "table_rewrite") == 0)
169 8 : event = EVT_TableRewrite;
170 : else
3915 rhaas 171 UBC 0 : continue;
172 :
173 : /* Allocate new cache item. */
3915 rhaas 174 CBC 131 : item = palloc0(sizeof(EventTriggerCacheItem));
175 131 : item->fnoid = form->evtfoid;
176 131 : item->enabled = form->evtenabled;
177 :
178 : /* Decode and sort tags array. */
179 131 : evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
180 : RelationGetDescr(rel), &evttags_isnull);
181 131 : if (!evttags_isnull)
1133 alvherre 182 39 : item->tagset = DecodeTextArrayToBitmapset(evttags);
183 :
184 : /* Add to cache entry. */
3915 rhaas 185 131 : entry = hash_search(cache, &event, HASH_ENTER, &found);
186 131 : if (found)
187 12 : entry->triggerlist = lappend(entry->triggerlist, item);
188 : else
189 119 : entry->triggerlist = list_make1(item);
190 : }
191 :
192 : /* Done with pg_event_trigger scan. */
193 2896 : systable_endscan_ordered(scan);
194 2896 : index_close(irel, AccessShareLock);
195 2896 : relation_close(rel, AccessShareLock);
196 :
197 : /* Restore previous memory context. */
198 2896 : MemoryContextSwitchTo(oldcontext);
199 :
200 : /* Install new cache. */
201 2896 : EventTriggerCache = cache;
202 :
203 : /*
204 : * If the cache has been invalidated since we entered this routine, we
205 : * still use and return the cache we just finished constructing, to avoid
206 : * infinite loops, but we leave the cache marked stale so that we'll
207 : * rebuild it again on next access. Otherwise, we mark the cache valid.
208 : */
3896 209 2896 : if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
210 2896 : EventTriggerCacheState = ETCS_VALID;
3915 211 2896 : }
212 :
213 : /*
214 : * Decode text[] to a Bitmapset of CommandTags.
215 : *
216 : * We could avoid a bit of overhead here if we were willing to duplicate some
217 : * of the logic from deconstruct_array, but it doesn't seem worth the code
218 : * complexity.
219 : */
220 : static Bitmapset *
1133 alvherre 221 39 : DecodeTextArrayToBitmapset(Datum array)
222 : {
3915 rhaas 223 39 : ArrayType *arr = DatumGetArrayTypeP(array);
224 : Datum *elems;
225 : Bitmapset *bms;
226 : int i;
227 : int nelems;
228 :
229 39 : if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
3915 rhaas 230 UBC 0 : elog(ERROR, "expected 1-D text array");
282 peter 231 GNC 39 : deconstruct_array_builtin(arr, TEXTOID, &elems, NULL, &nelems);
3915 rhaas 232 ECB :
1133 alvherre 233 GIC 129 : for (bms = NULL, i = 0; i < nelems; ++i)
1133 alvherre 234 ECB : {
1133 alvherre 235 GIC 90 : char *str = TextDatumGetCString(elems[i]);
1133 alvherre 236 ECB :
1133 alvherre 237 CBC 90 : bms = bms_add_member(bms, GetCommandTagEnum(str));
1133 alvherre 238 GIC 90 : pfree(str);
239 : }
3915 rhaas 240 ECB :
3915 rhaas 241 GIC 39 : pfree(elems);
1133 alvherre 242 ECB :
1133 alvherre 243 GIC 39 : return bms;
244 : }
245 :
246 : /*
247 : * Flush all cache entries when pg_event_trigger is updated.
248 : *
249 : * This should be rare enough that we don't need to be very granular about
250 : * it, so we just blow away everything, which also avoids the possibility of
251 : * memory leaks.
252 : */
3915 rhaas 253 ECB : static void
3915 rhaas 254 GIC 703 : InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
255 : {
256 : /*
257 : * If the cache isn't valid, then there might be a rebuild in progress, so
258 : * we can't immediately blow it away. But it's advantageous to do this
259 : * when possible, so as to immediately free memory.
3896 rhaas 260 ECB : */
3896 rhaas 261 GIC 703 : if (EventTriggerCacheState == ETCS_VALID)
3896 rhaas 262 ECB : {
3896 rhaas 263 CBC 347 : MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
3896 rhaas 264 GIC 347 : EventTriggerCache = NULL;
265 : }
266 :
3896 rhaas 267 ECB : /* Mark cache for rebuild. */
3896 rhaas 268 CBC 703 : EventTriggerCacheState = ETCS_NEEDS_REBUILD;
3915 rhaas 269 GIC 703 : }
|