1/*-------------------------------------------------------------------------
2 *
3 * evtcache.c
4 * Special-purpose cache for event trigger data.
5 *
6 * Portions Copyright (c) 1996-2019, 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/indexing.h"
21#include "catalog/pg_type.h"
22#include "commands/trigger.h"
23#include "utils/array.h"
24#include "utils/builtins.h"
25#include "utils/catcache.h"
26#include "utils/evtcache.h"
27#include "utils/inval.h"
28#include "utils/memutils.h"
29#include "utils/hsearch.h"
30#include "utils/rel.h"
31#include "utils/snapmgr.h"
32#include "utils/syscache.h"
33
34typedef enum
35{
36 ETCS_NEEDS_REBUILD,
37 ETCS_REBUILD_STARTED,
38 ETCS_VALID
39} EventTriggerCacheStateType;
40
41typedef struct
42{
43 EventTriggerEvent event;
44 List *triggerlist;
45} EventTriggerCacheEntry;
46
47static HTAB *EventTriggerCache;
48static MemoryContext EventTriggerCacheContext;
49static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD;
50
51static void BuildEventTriggerCache(void);
52static void InvalidateEventCacheCallback(Datum arg,
53 int cacheid, uint32 hashvalue);
54static int DecodeTextArrayToCString(Datum array, char ***cstringp);
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 */
63List *
64EventCacheLookup(EventTriggerEvent event)
65{
66 EventTriggerCacheEntry *entry;
67
68 if (EventTriggerCacheState != ETCS_VALID)
69 BuildEventTriggerCache();
70 entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
71 return entry != NULL ? entry->triggerlist : NIL;
72}
73
74/*
75 * Rebuild the event trigger cache.
76 */
77static void
78BuildEventTriggerCache(void)
79{
80 HASHCTL ctl;
81 HTAB *cache;
82 MemoryContext oldcontext;
83 Relation rel;
84 Relation irel;
85 SysScanDesc scan;
86
87 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 */
94 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 */
103 if (CacheMemoryContext == NULL)
104 CreateCacheMemoryContext();
105 EventTriggerCacheContext =
106 AllocSetContextCreate(CacheMemoryContext,
107 "EventTriggerCache",
108 ALLOCSET_DEFAULT_SIZES);
109 CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
110 InvalidateEventCacheCallback,
111 (Datum) 0);
112 }
113
114 /* Switch to correct memory context. */
115 oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
116
117 /* Prevent the memory context from being nuked while we're rebuilding. */
118 EventTriggerCacheState = ETCS_REBUILD_STARTED;
119
120 /* Create new hash table. */
121 MemSet(&ctl, 0, sizeof(ctl));
122 ctl.keysize = sizeof(EventTriggerEvent);
123 ctl.entrysize = sizeof(EventTriggerCacheEntry);
124 ctl.hcxt = EventTriggerCacheContext;
125 cache = hash_create("Event Trigger Cache", 32, &ctl,
126 HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
127
128 /*
129 * Prepare to scan pg_event_trigger in name order.
130 */
131 rel = relation_open(EventTriggerRelationId, AccessShareLock);
132 irel = index_open(EventTriggerNameIndexId, AccessShareLock);
133 scan = systable_beginscan_ordered(rel, irel, NULL, 0, NULL);
134
135 /*
136 * Build a cache item for each pg_event_trigger tuple, and append each one
137 * to the appropriate cache entry.
138 */
139 for (;;)
140 {
141 HeapTuple tup;
142 Form_pg_event_trigger form;
143 char *evtevent;
144 EventTriggerEvent event;
145 EventTriggerCacheItem *item;
146 Datum evttags;
147 bool evttags_isnull;
148 EventTriggerCacheEntry *entry;
149 bool found;
150
151 /* Get next tuple. */
152 tup = systable_getnext_ordered(scan, ForwardScanDirection);
153 if (!HeapTupleIsValid(tup))
154 break;
155
156 /* Skip trigger if disabled. */
157 form = (Form_pg_event_trigger) GETSTRUCT(tup);
158 if (form->evtenabled == TRIGGER_DISABLED)
159 continue;
160
161 /* Decode event name. */
162 evtevent = NameStr(form->evtevent);
163 if (strcmp(evtevent, "ddl_command_start") == 0)
164 event = EVT_DDLCommandStart;
165 else if (strcmp(evtevent, "ddl_command_end") == 0)
166 event = EVT_DDLCommandEnd;
167 else if (strcmp(evtevent, "sql_drop") == 0)
168 event = EVT_SQLDrop;
169 else if (strcmp(evtevent, "table_rewrite") == 0)
170 event = EVT_TableRewrite;
171 else
172 continue;
173
174 /* Allocate new cache item. */
175 item = palloc0(sizeof(EventTriggerCacheItem));
176 item->fnoid = form->evtfoid;
177 item->enabled = form->evtenabled;
178
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 {
184 item->ntags = DecodeTextArrayToCString(evttags, &item->tag);
185 qsort(item->tag, item->ntags, sizeof(char *), pg_qsort_strcmp);
186 }
187
188 /* Add to cache entry. */
189 entry = hash_search(cache, &event, HASH_ENTER, &found);
190 if (found)
191 entry->triggerlist = lappend(entry->triggerlist, item);
192 else
193 entry->triggerlist = list_make1(item);
194 }
195
196 /* Done with pg_event_trigger scan. */
197 systable_endscan_ordered(scan);
198 index_close(irel, AccessShareLock);
199 relation_close(rel, AccessShareLock);
200
201 /* Restore previous memory context. */
202 MemoryContextSwitchTo(oldcontext);
203
204 /* Install new cache. */
205 EventTriggerCache = cache;
206
207 /*
208 * If the cache has been invalidated since we entered this routine, we
209 * still use and return the cache we just finished constructing, to avoid
210 * infinite loops, but we leave the cache marked stale so that we'll
211 * rebuild it again on next access. Otherwise, we mark the cache valid.
212 */
213 if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
214 EventTriggerCacheState = ETCS_VALID;
215}
216
217/*
218 * Decode text[] to an array of C strings.
219 *
220 * We could avoid a bit of overhead here if we were willing to duplicate some
221 * of the logic from deconstruct_array, but it doesn't seem worth the code
222 * complexity.
223 */
224static int
225DecodeTextArrayToCString(Datum array, char ***cstringp)
226{
227 ArrayType *arr = DatumGetArrayTypeP(array);
228 Datum *elems;
229 char **cstring;
230 int i;
231 int nelems;
232
233 if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
234 elog(ERROR, "expected 1-D text array");
235 deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
236
237 cstring = palloc(nelems * sizeof(char *));
238 for (i = 0; i < nelems; ++i)
239 cstring[i] = TextDatumGetCString(elems[i]);
240
241 pfree(elems);
242 *cstringp = cstring;
243 return nelems;
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 */
253static void
254InvalidateEventCacheCallback(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.
260 */
261 if (EventTriggerCacheState == ETCS_VALID)
262 {
263 MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
264 EventTriggerCache = NULL;
265 }
266
267 /* Mark cache for rebuild. */
268 EventTriggerCacheState = ETCS_NEEDS_REBUILD;
269}
270