2012-07-20 17:38:47 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* evtcache.c
|
|
|
|
* Special-purpose cache for event trigger data.
|
|
|
|
*
|
2013-01-01 23:15:01 +01:00
|
|
|
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
|
2012-07-20 17:38:47 +02:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/backend/utils/cache/evtcache.c
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/genam.h"
|
|
|
|
#include "access/heapam.h"
|
2012-08-30 22:15:44 +02:00
|
|
|
#include "access/htup_details.h"
|
2012-07-20 17:38:47 +02:00
|
|
|
#include "catalog/pg_event_trigger.h"
|
|
|
|
#include "catalog/indexing.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
|
|
#include "commands/trigger.h"
|
|
|
|
#include "utils/array.h"
|
|
|
|
#include "utils/builtins.h"
|
2012-08-29 00:02:07 +02:00
|
|
|
#include "utils/catcache.h"
|
2012-07-20 17:38:47 +02:00
|
|
|
#include "utils/evtcache.h"
|
|
|
|
#include "utils/inval.h"
|
|
|
|
#include "utils/memutils.h"
|
|
|
|
#include "utils/hsearch.h"
|
|
|
|
#include "utils/rel.h"
|
|
|
|
#include "utils/snapmgr.h"
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
2012-08-08 22:38:37 +02:00
|
|
|
typedef enum
|
|
|
|
{
|
|
|
|
ETCS_NEEDS_REBUILD,
|
|
|
|
ETCS_REBUILD_STARTED,
|
|
|
|
ETCS_VALID
|
|
|
|
} EventTriggerCacheStateType;
|
|
|
|
|
2012-07-20 17:38:47 +02:00
|
|
|
typedef struct
|
|
|
|
{
|
2013-05-29 22:58:43 +02:00
|
|
|
EventTriggerEvent event;
|
2012-07-20 17:38:47 +02:00
|
|
|
List *triggerlist;
|
|
|
|
} EventTriggerCacheEntry;
|
|
|
|
|
|
|
|
static HTAB *EventTriggerCache;
|
|
|
|
static MemoryContext EventTriggerCacheContext;
|
2012-08-08 22:38:37 +02:00
|
|
|
static EventTriggerCacheStateType EventTriggerCacheState = ETCS_NEEDS_REBUILD;
|
2012-07-20 17:38:47 +02:00
|
|
|
|
|
|
|
static void BuildEventTriggerCache(void);
|
|
|
|
static void InvalidateEventCacheCallback(Datum arg,
|
|
|
|
int cacheid, uint32 hashvalue);
|
2013-05-29 22:58:43 +02:00
|
|
|
static int DecodeTextArrayToCString(Datum array, char ***cstringp);
|
2012-07-20 17:38:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Search the event cache by trigger event.
|
|
|
|
*
|
|
|
|
* Note that the caller had better copy any data it wants to keep around
|
|
|
|
* across any operation that might touch a system catalog into some other
|
|
|
|
* memory context, since a cache reset could blow the return value away.
|
|
|
|
*/
|
|
|
|
List *
|
|
|
|
EventCacheLookup(EventTriggerEvent event)
|
|
|
|
{
|
|
|
|
EventTriggerCacheEntry *entry;
|
|
|
|
|
2012-08-08 22:38:37 +02:00
|
|
|
if (EventTriggerCacheState != ETCS_VALID)
|
2012-07-20 17:38:47 +02:00
|
|
|
BuildEventTriggerCache();
|
|
|
|
entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
|
|
|
|
return entry != NULL ? entry->triggerlist : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Rebuild the event trigger cache.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
BuildEventTriggerCache(void)
|
|
|
|
{
|
2013-05-29 22:58:43 +02:00
|
|
|
HASHCTL ctl;
|
|
|
|
HTAB *cache;
|
|
|
|
MemoryContext oldcontext;
|
|
|
|
Relation rel;
|
|
|
|
Relation irel;
|
|
|
|
SysScanDesc scan;
|
2012-07-20 17:38:47 +02:00
|
|
|
|
|
|
|
if (EventTriggerCacheContext != NULL)
|
|
|
|
{
|
|
|
|
/*
|
2012-08-08 22:38:37 +02:00
|
|
|
* Free up any memory already allocated in EventTriggerCacheContext.
|
|
|
|
* This can happen either because a previous rebuild failed, or
|
|
|
|
* because an invalidation happened before the rebuild was complete.
|
2012-07-20 17:38:47 +02:00
|
|
|
*/
|
2012-08-07 22:59:42 +02:00
|
|
|
MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
|
2012-07-20 17:38:47 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
2013-05-29 22:58:43 +02:00
|
|
|
* This is our first time attempting to build the cache, so we need to
|
|
|
|
* set up the memory context and register a syscache callback to
|
2012-07-20 17:38:47 +02:00
|
|
|
* capture future invalidation events.
|
|
|
|
*/
|
|
|
|
if (CacheMemoryContext == NULL)
|
|
|
|
CreateCacheMemoryContext();
|
|
|
|
EventTriggerCacheContext =
|
|
|
|
AllocSetContextCreate(CacheMemoryContext,
|
|
|
|
"EventTriggerCache",
|
|
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
|
|
CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
|
|
|
|
InvalidateEventCacheCallback,
|
|
|
|
(Datum) 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Switch to correct memory context. */
|
|
|
|
oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
|
|
|
|
|
2012-08-08 22:38:37 +02:00
|
|
|
/* Prevent the memory context from being nuked while we're rebuilding. */
|
|
|
|
EventTriggerCacheState = ETCS_REBUILD_STARTED;
|
|
|
|
|
|
|
|
/* Create new hash table. */
|
2012-07-20 17:38:47 +02:00
|
|
|
MemSet(&ctl, 0, sizeof(ctl));
|
|
|
|
ctl.keysize = sizeof(EventTriggerEvent);
|
|
|
|
ctl.entrysize = sizeof(EventTriggerCacheEntry);
|
|
|
|
ctl.hash = tag_hash;
|
2012-08-07 22:59:42 +02:00
|
|
|
ctl.hcxt = EventTriggerCacheContext;
|
2012-07-20 17:38:47 +02:00
|
|
|
cache = hash_create("Event Trigger Cache", 32, &ctl,
|
2012-08-07 22:59:42 +02:00
|
|
|
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
|
2012-07-20 17:38:47 +02:00
|
|
|
|
|
|
|
/*
|
2013-05-29 22:58:43 +02:00
|
|
|
* Prepare to scan pg_event_trigger in name order. We use an MVCC
|
|
|
|
* snapshot to avoid getting inconsistent results if the table is being
|
|
|
|
* concurrently updated.
|
2012-07-20 17:38:47 +02:00
|
|
|
*/
|
|
|
|
rel = relation_open(EventTriggerRelationId, AccessShareLock);
|
|
|
|
irel = index_open(EventTriggerNameIndexId, AccessShareLock);
|
|
|
|
scan = systable_beginscan_ordered(rel, irel, GetLatestSnapshot(), 0, NULL);
|
|
|
|
|
|
|
|
/*
|
2013-05-29 22:58:43 +02:00
|
|
|
* Build a cache item for each pg_event_trigger tuple, and append each one
|
|
|
|
* to the appropriate cache entry.
|
2012-07-20 17:38:47 +02:00
|
|
|
*/
|
|
|
|
for (;;)
|
|
|
|
{
|
2013-05-29 22:58:43 +02:00
|
|
|
HeapTuple tup;
|
|
|
|
Form_pg_event_trigger form;
|
2012-07-20 17:38:47 +02:00
|
|
|
char *evtevent;
|
2013-05-29 22:58:43 +02:00
|
|
|
EventTriggerEvent event;
|
2012-07-20 17:38:47 +02:00
|
|
|
EventTriggerCacheItem *item;
|
|
|
|
Datum evttags;
|
|
|
|
bool evttags_isnull;
|
|
|
|
EventTriggerCacheEntry *entry;
|
|
|
|
bool found;
|
|
|
|
|
|
|
|
/* Get next tuple. */
|
|
|
|
tup = systable_getnext_ordered(scan, ForwardScanDirection);
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Skip trigger if disabled. */
|
|
|
|
form = (Form_pg_event_trigger) GETSTRUCT(tup);
|
|
|
|
if (form->evtenabled == TRIGGER_DISABLED)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Decode event name. */
|
|
|
|
evtevent = NameStr(form->evtevent);
|
|
|
|
if (strcmp(evtevent, "ddl_command_start") == 0)
|
|
|
|
event = EVT_DDLCommandStart;
|
2013-01-22 00:00:24 +01:00
|
|
|
else if (strcmp(evtevent, "ddl_command_end") == 0)
|
|
|
|
event = EVT_DDLCommandEnd;
|
Add sql_drop event for event triggers
This event takes place just before ddl_command_end, and is fired if and
only if at least one object has been dropped by the command. (For
instance, DROP TABLE IF EXISTS of a table that does not in fact exist
will not lead to such a trigger firing). Commands that drop multiple
objects (such as DROP SCHEMA or DROP OWNED BY) will cause a single event
to fire. Some firings might be surprising, such as
ALTER TABLE DROP COLUMN.
The trigger is fired after the drop has taken place, because that has
been deemed the safest design, to avoid exposing possibly-inconsistent
internal state (system catalogs as well as current transaction) to the
user function code. This means that careful tracking of object
identification is required during the object removal phase.
Like other currently existing events, there is support for tag
filtering.
To support the new event, add a new pg_event_trigger_dropped_objects()
set-returning function, which returns a set of rows comprising the
objects affected by the command. This is to be used within the user
function code, and is mostly modelled after the recently introduced
pg_identify_object() function.
Catalog version bumped due to the new function.
Dimitri Fontaine and Álvaro Herrera
Review by Robert Haas, Tom Lane
2013-03-27 20:02:10 +01:00
|
|
|
else if (strcmp(evtevent, "sql_drop") == 0)
|
|
|
|
event = EVT_SQLDrop;
|
2012-07-20 17:38:47 +02:00
|
|
|
else
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Allocate new cache item. */
|
|
|
|
item = palloc0(sizeof(EventTriggerCacheItem));
|
|
|
|
item->fnoid = form->evtfoid;
|
|
|
|
item->enabled = form->evtenabled;
|
|
|
|
|
|
|
|
/* Decode and sort tags array. */
|
|
|
|
evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
|
|
|
|
RelationGetDescr(rel), &evttags_isnull);
|
|
|
|
if (!evttags_isnull)
|
|
|
|
{
|
|
|
|
item->ntags = DecodeTextArrayToCString(evttags, &item->tag);
|
|
|
|
qsort(item->tag, item->ntags, sizeof(char *), pg_qsort_strcmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add to cache entry. */
|
|
|
|
entry = hash_search(cache, &event, HASH_ENTER, &found);
|
|
|
|
if (found)
|
|
|
|
entry->triggerlist = lappend(entry->triggerlist, item);
|
|
|
|
else
|
|
|
|
entry->triggerlist = list_make1(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Done with pg_event_trigger scan. */
|
|
|
|
systable_endscan_ordered(scan);
|
|
|
|
index_close(irel, AccessShareLock);
|
|
|
|
relation_close(rel, AccessShareLock);
|
|
|
|
|
|
|
|
/* Restore previous memory context. */
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
|
2012-08-08 22:38:37 +02:00
|
|
|
/* Install new cache. */
|
2012-07-20 17:38:47 +02:00
|
|
|
EventTriggerCache = cache;
|
2012-08-08 22:38:37 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If the cache has been invalidated since we entered this routine, we
|
|
|
|
* still use and return the cache we just finished constructing, to avoid
|
|
|
|
* infinite loops, but we leave the cache marked stale so that we'll
|
|
|
|
* rebuild it again on next access. Otherwise, we mark the cache valid.
|
|
|
|
*/
|
|
|
|
if (EventTriggerCacheState == ETCS_REBUILD_STARTED)
|
|
|
|
EventTriggerCacheState = ETCS_VALID;
|
2012-07-20 17:38:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Decode text[] to an array of C strings.
|
|
|
|
*
|
|
|
|
* We could avoid a bit of overhead here if we were willing to duplicate some
|
|
|
|
* of the logic from deconstruct_array, but it doesn't seem worth the code
|
|
|
|
* complexity.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
DecodeTextArrayToCString(Datum array, char ***cstringp)
|
|
|
|
{
|
|
|
|
ArrayType *arr = DatumGetArrayTypeP(array);
|
|
|
|
Datum *elems;
|
|
|
|
char **cstring;
|
|
|
|
int i;
|
|
|
|
int nelems;
|
|
|
|
|
|
|
|
if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
|
|
|
|
elog(ERROR, "expected 1-D text array");
|
|
|
|
deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
|
|
|
|
|
|
|
|
cstring = palloc(nelems * sizeof(char *));
|
|
|
|
for (i = 0; i < nelems; ++i)
|
|
|
|
cstring[i] = TextDatumGetCString(elems[i]);
|
|
|
|
|
|
|
|
pfree(elems);
|
|
|
|
*cstringp = cstring;
|
|
|
|
return nelems;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Flush all cache entries when pg_event_trigger is updated.
|
|
|
|
*
|
|
|
|
* This should be rare enough that we don't need to be very granular about
|
|
|
|
* it, so we just blow away everything, which also avoids the possibility of
|
|
|
|
* memory leaks.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
|
|
|
|
{
|
2012-08-08 22:38:37 +02:00
|
|
|
/*
|
2013-05-29 22:58:43 +02:00
|
|
|
* If the cache isn't valid, then there might be a rebuild in progress, so
|
|
|
|
* we can't immediately blow it away. But it's advantageous to do this
|
|
|
|
* when possible, so as to immediately free memory.
|
2012-08-08 22:38:37 +02:00
|
|
|
*/
|
|
|
|
if (EventTriggerCacheState == ETCS_VALID)
|
|
|
|
{
|
|
|
|
MemoryContextResetAndDeleteChildren(EventTriggerCacheContext);
|
|
|
|
EventTriggerCache = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Mark cache for rebuild. */
|
|
|
|
EventTriggerCacheState = ETCS_NEEDS_REBUILD;
|
2012-07-20 17:38:47 +02:00
|
|
|
}
|