Header And Logo

PostgreSQL
| The world's most advanced open source database.

evtcache.c

Go to the documentation of this file.
00001 /*-------------------------------------------------------------------------
00002  *
00003  * evtcache.c
00004  *    Special-purpose cache for event trigger data.
00005  *
00006  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
00007  * Portions Copyright (c) 1994, Regents of the University of California
00008  *
00009  * IDENTIFICATION
00010  *    src/backend/utils/cache/evtcache.c
00011  *
00012  *-------------------------------------------------------------------------
00013  */
00014 #include "postgres.h"
00015 
00016 #include "access/genam.h"
00017 #include "access/heapam.h"
00018 #include "access/htup_details.h"
00019 #include "catalog/pg_event_trigger.h"
00020 #include "catalog/indexing.h"
00021 #include "catalog/pg_type.h"
00022 #include "commands/trigger.h"
00023 #include "utils/array.h"
00024 #include "utils/builtins.h"
00025 #include "utils/catcache.h"
00026 #include "utils/evtcache.h"
00027 #include "utils/inval.h"
00028 #include "utils/memutils.h"
00029 #include "utils/hsearch.h"
00030 #include "utils/rel.h"
00031 #include "utils/snapmgr.h"
00032 #include "utils/syscache.h"
00033 
00034 typedef enum
00035 {
00036     ETCS_NEEDS_REBUILD,
00037     ETCS_REBUILD_STARTED,
00038     ETCS_VALID
00039 } EventTriggerCacheStateType;
00040 
00041 typedef struct
00042 {
00043     EventTriggerEvent   event;
00044     List       *triggerlist;
00045 } EventTriggerCacheEntry;
00046 
00047 static HTAB *EventTriggerCache;
00048 static MemoryContext EventTriggerCacheContext;
00049 static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD;
00050 
00051 static void BuildEventTriggerCache(void);
00052 static void InvalidateEventCacheCallback(Datum arg,
00053                              int cacheid, uint32 hashvalue);
00054 static int DecodeTextArrayToCString(Datum array, char ***cstringp);
00055 
00056 /*
00057  * Search the event cache by trigger event.
00058  *
00059  * Note that the caller had better copy any data it wants to keep around
00060  * across any operation that might touch a system catalog into some other
00061  * memory context, since a cache reset could blow the return value away.
00062  */
00063 List *
00064 EventCacheLookup(EventTriggerEvent event)
00065 {
00066     EventTriggerCacheEntry *entry;
00067 
00068     if (EventTriggerCacheState != ETCS_VALID)
00069         BuildEventTriggerCache();
00070     entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
00071     return entry != NULL ? entry->triggerlist : NULL;
00072 }
00073 
00074 /*
00075  * Rebuild the event trigger cache.
00076  */
00077 static void
00078 BuildEventTriggerCache(void)
00079 {
00080     HASHCTL         ctl;
00081     HTAB           *cache;
00082     MemoryContext   oldcontext;
00083     Relation        rel;
00084     Relation        irel;
00085     SysScanDesc     scan;
00086 
00087     if (EventTriggerCacheContext != NULL)
00088     {
00089         /*
00090          * Free up any memory already allocated in EventTriggerCacheContext.
00091          * This can happen either because a previous rebuild failed, or
00092          * because an invalidation happened before the rebuild was complete.
00093          */
00094         MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
00095     }
00096     else
00097     {
00098         /*
00099          * This is our first time attempting to build the cache, so we need
00100          * to set up the memory context and register a syscache callback to
00101          * capture future invalidation events.
00102          */
00103         if (CacheMemoryContext == NULL)
00104             CreateCacheMemoryContext();
00105         EventTriggerCacheContext =
00106             AllocSetContextCreate(CacheMemoryContext,
00107                                   "EventTriggerCache",
00108                                   ALLOCSET_DEFAULT_MINSIZE,
00109                                   ALLOCSET_DEFAULT_INITSIZE,
00110                                   ALLOCSET_DEFAULT_MAXSIZE);
00111         CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
00112                                       InvalidateEventCacheCallback,
00113                                       (Datum) 0);
00114     }
00115 
00116     /* Switch to correct memory context. */
00117     oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
00118 
00119     /* Prevent the memory context from being nuked while we're rebuilding. */
00120     EventTriggerCacheState = ETCS_REBUILD_STARTED;
00121 
00122     /* Create new hash table. */
00123     MemSet(&ctl, 0, sizeof(ctl));
00124     ctl.keysize = sizeof(EventTriggerEvent);
00125     ctl.entrysize = sizeof(EventTriggerCacheEntry);
00126     ctl.hash = tag_hash;
00127     ctl.hcxt = EventTriggerCacheContext;
00128     cache = hash_create("Event Trigger Cache", 32, &ctl,
00129                         HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
00130 
00131     /*
00132      * Prepare to scan pg_event_trigger in name order.  We use an MVCC
00133      * snapshot to avoid getting inconsistent results if the table is
00134      * being concurrently updated.
00135      */
00136     rel = relation_open(EventTriggerRelationId, AccessShareLock);
00137     irel = index_open(EventTriggerNameIndexId, AccessShareLock);
00138     scan = systable_beginscan_ordered(rel, irel, GetLatestSnapshot(), 0, NULL);
00139 
00140     /*
00141      * Build a cache item for each pg_event_trigger tuple, and append each
00142      * one to the appropriate cache entry.
00143      */
00144     for (;;)
00145     {
00146         HeapTuple       tup;
00147         Form_pg_event_trigger   form;
00148         char       *evtevent;
00149         EventTriggerEvent   event;
00150         EventTriggerCacheItem *item;
00151         Datum       evttags;
00152         bool        evttags_isnull;
00153         EventTriggerCacheEntry *entry;
00154         bool        found;
00155 
00156         /* Get next tuple. */
00157         tup = systable_getnext_ordered(scan, ForwardScanDirection);
00158         if (!HeapTupleIsValid(tup))
00159             break;
00160 
00161         /* Skip trigger if disabled. */
00162         form = (Form_pg_event_trigger) GETSTRUCT(tup);
00163         if (form->evtenabled == TRIGGER_DISABLED)
00164             continue;
00165 
00166         /* Decode event name. */
00167         evtevent = NameStr(form->evtevent);
00168         if (strcmp(evtevent, "ddl_command_start") == 0)
00169             event = EVT_DDLCommandStart;
00170         else if (strcmp(evtevent, "ddl_command_end") == 0)
00171             event = EVT_DDLCommandEnd;
00172         else if (strcmp(evtevent, "sql_drop") == 0)
00173             event = EVT_SQLDrop;
00174         else
00175             continue;
00176 
00177         /* Allocate new cache item. */
00178         item = palloc0(sizeof(EventTriggerCacheItem));
00179         item->fnoid = form->evtfoid;
00180         item->enabled = form->evtenabled;
00181 
00182         /* Decode and sort tags array. */
00183         evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
00184                                RelationGetDescr(rel), &evttags_isnull);
00185         if (!evttags_isnull)
00186         {
00187             item->ntags = DecodeTextArrayToCString(evttags, &item->tag);
00188             qsort(item->tag, item->ntags, sizeof(char *), pg_qsort_strcmp);
00189         }
00190 
00191         /* Add to cache entry. */
00192         entry = hash_search(cache, &event, HASH_ENTER, &found);
00193         if (found)
00194             entry->triggerlist = lappend(entry->triggerlist, item);
00195         else
00196             entry->triggerlist = list_make1(item);
00197     }
00198 
00199     /* Done with pg_event_trigger scan. */
00200     systable_endscan_ordered(scan);
00201     index_close(irel, AccessShareLock);
00202     relation_close(rel, AccessShareLock);
00203 
00204     /* Restore previous memory context. */
00205     MemoryContextSwitchTo(oldcontext);
00206 
00207     /* Install new cache. */
00208     EventTriggerCache = cache;
00209 
00210     /*
00211      * If the cache has been invalidated since we entered this routine, we
00212      * still use and return the cache we just finished constructing, to avoid
00213      * infinite loops, but we leave the cache marked stale so that we'll
00214      * rebuild it again on next access.  Otherwise, we mark the cache valid.
00215      */
00216     if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
00217         EventTriggerCacheState = ETCS_VALID;
00218 }
00219 
00220 /*
00221  * Decode text[] to an array of C strings.
00222  *
00223  * We could avoid a bit of overhead here if we were willing to duplicate some
00224  * of the logic from deconstruct_array, but it doesn't seem worth the code
00225  * complexity.
00226  */
00227 static int
00228 DecodeTextArrayToCString(Datum array, char ***cstringp)
00229 {
00230     ArrayType  *arr = DatumGetArrayTypeP(array);
00231     Datum      *elems;
00232     char      **cstring;
00233     int         i;
00234     int         nelems;
00235 
00236     if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
00237         elog(ERROR, "expected 1-D text array");
00238     deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
00239 
00240     cstring = palloc(nelems * sizeof(char *));
00241     for (i = 0; i < nelems; ++i)
00242         cstring[i] = TextDatumGetCString(elems[i]);
00243 
00244     pfree(elems);
00245     *cstringp = cstring;
00246     return nelems;
00247 }
00248 
00249 /*
00250  * Flush all cache entries when pg_event_trigger is updated.
00251  *
00252  * This should be rare enough that we don't need to be very granular about
00253  * it, so we just blow away everything, which also avoids the possibility of
00254  * memory leaks.
00255  */
00256 static void
00257 InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
00258 {
00259     /*
00260      * If the cache isn't valid, then there might be a rebuild in progress,
00261      * so we can't immediately blow it away.  But it's advantageous to do
00262      * this when possible, so as to immediately free memory.
00263      */
00264     if (EventTriggerCacheState == ETCS_VALID)
00265     {
00266         MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
00267         EventTriggerCache = NULL;
00268     }
00269 
00270     /* Mark cache for rebuild. */
00271     EventTriggerCacheState = ETCS_NEEDS_REBUILD;
00272 }