1 /*-------------------------------------------------------------------------
2  *
3  * evtcache.c
4  *	  Special-purpose cache for event trigger data.
5  *
6  * Portions Copyright (c) 1996-2017, 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/heapam.h"
18 #include "access/htup_details.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 
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 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  */
63 List *
EventCacheLookup(EventTriggerEvent event)64 EventCacheLookup(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  */
77 static void
BuildEventTriggerCache(void)78 BuildEventTriggerCache(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  */
224 static int
DecodeTextArrayToCString(Datum array,char *** cstringp)225 DecodeTextArrayToCString(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  */
253 static void
InvalidateEventCacheCallback(Datum arg,int cacheid,uint32 hashvalue)254 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.
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