aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/catalog/dependency.c65
-rw-r--r--src/backend/commands/event_trigger.c518
-rw-r--r--src/backend/parser/gram.y1
-rw-r--r--src/backend/tcop/utility.c64
-rw-r--r--src/backend/utils/adt/regproc.c2
-rw-r--r--src/backend/utils/cache/evtcache.c2
-rw-r--r--src/include/catalog/catversion.h2
-rw-r--r--src/include/catalog/pg_proc.h3
-rw-r--r--src/include/commands/event_trigger.h9
-rw-r--r--src/include/utils/builtins.h3
-rw-r--r--src/include/utils/evtcache.h3
-rw-r--r--src/test/regress/expected/event_trigger.out197
-rw-r--r--src/test/regress/sql/event_trigger.sql106
13 files changed, 865 insertions, 110 deletions
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index ddf199049ef..286137c2513 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -191,6 +191,44 @@ static bool stack_address_present_add_flags(const ObjectAddress *object,
/*
+ * Go through the objects given running the final actions on them, and execute
+ * the actual deletion.
+ */
+static void
+deleteObjectsInList(ObjectAddresses *targetObjects, Relation *depRel,
+ int flags)
+{
+ int i;
+
+ /*
+ * Keep track of objects for event triggers, if necessary.
+ */
+ if (trackDroppedObjectsNeeded())
+ {
+ for (i = 0; i < targetObjects->numrefs; i++)
+ {
+ ObjectAddress *thisobj = targetObjects->refs + i;
+
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ EventTriggerSQLDropAddObject(thisobj);
+ }
+ }
+ }
+
+ /*
+ * Delete all the objects in the proper order.
+ */
+ for (i = 0; i < targetObjects->numrefs; i++)
+ {
+ ObjectAddress *thisobj = targetObjects->refs + i;
+
+ deleteOneObject(thisobj, depRel, flags);
+ }
+}
+
+/*
* performDeletion: attempt to drop the specified object. If CASCADE
* behavior is specified, also drop any dependent objects (recursively).
* If RESTRICT behavior is specified, error out if there are any dependent
@@ -215,7 +253,6 @@ performDeletion(const ObjectAddress *object,
{
Relation depRel;
ObjectAddresses *targetObjects;
- int i;
/*
* We save some cycles by opening pg_depend just once and passing the
@@ -250,15 +287,8 @@ performDeletion(const ObjectAddress *object,
NOTICE,
object);
- /*
- * Delete all the objects in the proper order.
- */
- for (i = 0; i < targetObjects->numrefs; i++)
- {
- ObjectAddress *thisobj = targetObjects->refs + i;
-
- deleteOneObject(thisobj, &depRel, flags);
- }
+ /* do the deed */
+ deleteObjectsInList(targetObjects, &depRel, flags);
/* And clean up */
free_object_addresses(targetObjects);
@@ -332,15 +362,8 @@ performMultipleDeletions(const ObjectAddresses *objects,
NOTICE,
(objects->numrefs == 1 ? objects->refs : NULL));
- /*
- * Delete all the objects in the proper order.
- */
- for (i = 0; i < targetObjects->numrefs; i++)
- {
- ObjectAddress *thisobj = targetObjects->refs + i;
-
- deleteOneObject(thisobj, &depRel, flags);
- }
+ /* do the deed */
+ deleteObjectsInList(targetObjects, &depRel, flags);
/* And clean up */
free_object_addresses(targetObjects);
@@ -356,6 +379,10 @@ performMultipleDeletions(const ObjectAddresses *objects,
* This is currently used only to clean out the contents of a schema
* (namespace): the passed object is a namespace. We normally want this
* to be done silently, so there's an option to suppress NOTICE messages.
+ *
+ * Note we don't fire object drop event triggers here; it would be wrong to do
+ * so for the current only use of this function, but if more callers are added
+ * this might need to be reconsidered.
*/
void
deleteWhatDependsOn(const ObjectAddress *object,
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index fbe8f49a9e7..ed5240d63b0 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -19,14 +19,17 @@
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_event_trigger.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h"
+#include "funcapi.h"
#include "parser/parse_func.h"
#include "pgstat.h"
+#include "lib/ilist.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -39,6 +42,17 @@
#include "utils/syscache.h"
#include "tcop/utility.h"
+
+typedef struct EventTriggerQueryState
+{
+ slist_head SQLDropList;
+ bool in_sql_drop;
+ MemoryContext cxt;
+ struct EventTriggerQueryState *previous;
+} EventTriggerQueryState;
+
+EventTriggerQueryState *currentEventTriggerState = NULL;
+
typedef struct
{
const char *obtypename;
@@ -89,6 +103,17 @@ static event_trigger_support_data event_trigger_support[] = {
{ NULL, false }
};
+/* Support for dropped objects */
+typedef struct SQLDropObject
+{
+ ObjectAddress address;
+ const char *schemaname;
+ const char *objname;
+ const char *objidentity;
+ const char *objecttype;
+ slist_node next;
+} SQLDropObject;
+
static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup,
Oid newOwnerId);
@@ -127,7 +152,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
/* Validate event name. */
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
- strcmp(stmt->eventname, "ddl_command_end") != 0)
+ strcmp(stmt->eventname, "ddl_command_end") != 0 &&
+ strcmp(stmt->eventname, "sql_drop") != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized event name \"%s\"",
@@ -151,7 +177,10 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
}
/* Validate tag list, if any. */
- if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL)
+ if ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||
+ strcmp(stmt->eventname, "ddl_command_end") == 0 ||
+ strcmp(stmt->eventname, "sql_drop") == 0)
+ && tags != NULL)
validate_ddl_tags("tag", tags);
/*
@@ -220,7 +249,8 @@ check_ddl_tag(const char *tag)
pg_strcasecmp(tag, "SELECT INTO") == 0 ||
pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 ||
pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
- pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)
+ pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 ||
+ pg_strcasecmp(tag, "DROP OWNED") == 0)
return EVENT_TRIGGER_COMMAND_TAG_OK;
/*
@@ -568,35 +598,19 @@ filter_event_trigger(const char **tag, EventTriggerCacheItem *item)
}
/*
- * Fire ddl_command_start triggers.
+ * Setup for running triggers for the given event. Return value is an OID list
+ * of functions to run; if there are any, trigdata is filled with an
+ * appropriate EventTriggerData for them to receive.
*/
-void
-EventTriggerDDLCommandStart(Node *parsetree)
+static List *
+EventTriggerCommonSetup(Node *parsetree,
+ EventTriggerEvent event, const char *eventstr,
+ EventTriggerData *trigdata)
{
+ const char *tag;
List *cachelist;
- List *runlist = NIL;
ListCell *lc;
- const char *tag;
- EventTriggerData trigdata;
-
- /*
- * Event Triggers are completely disabled in standalone mode. There are
- * (at least) two reasons for this:
- *
- * 1. A sufficiently broken event trigger might not only render the
- * database unusable, but prevent disabling itself to fix the situation.
- * In this scenario, restarting in standalone mode provides an escape
- * hatch.
- *
- * 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and
- * therefore will malfunction if pg_event_trigger's indexes are damaged.
- * To allow recovery from a damaged index, we need some operating mode
- * wherein event triggers are disabled. (Or we could implement
- * heapscan-and-sort logic for that case, but having disaster recovery
- * scenarios depend on code that's otherwise untested isn't appetizing.)
- */
- if (!IsUnderPostmaster)
- return;
+ List *runlist = NIL;
/*
* We want the list of command tags for which this procedure is actually
@@ -624,9 +638,9 @@ EventTriggerDDLCommandStart(Node *parsetree)
#endif
/* Use cache to find triggers for this event; fast exit if none. */
- cachelist = EventCacheLookup(EVT_DDLCommandStart);
- if (cachelist == NULL)
- return;
+ cachelist = EventCacheLookup(event);
+ if (cachelist == NIL)
+ return NIL;
/* Get the command tag. */
tag = CreateCommandTag(parsetree);
@@ -649,11 +663,51 @@ EventTriggerDDLCommandStart(Node *parsetree)
}
}
- /* Construct event trigger data. */
- trigdata.type = T_EventTriggerData;
- trigdata.event = "ddl_command_start";
- trigdata.parsetree = parsetree;
- trigdata.tag = tag;
+ /* don't spend any more time on this if no functions to run */
+ if (runlist == NIL)
+ return NIL;
+
+ trigdata->type = T_EventTriggerData;
+ trigdata->event = eventstr;
+ trigdata->parsetree = parsetree;
+ trigdata->tag = tag;
+
+ return runlist;
+}
+
+/*
+ * Fire ddl_command_start triggers.
+ */
+void
+EventTriggerDDLCommandStart(Node *parsetree)
+{
+ List *runlist;
+ EventTriggerData trigdata;
+
+ /*
+ * Event Triggers are completely disabled in standalone mode. There are
+ * (at least) two reasons for this:
+ *
+ * 1. A sufficiently broken event trigger might not only render the
+ * database unusable, but prevent disabling itself to fix the situation.
+ * In this scenario, restarting in standalone mode provides an escape
+ * hatch.
+ *
+ * 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and
+ * therefore will malfunction if pg_event_trigger's indexes are damaged.
+ * To allow recovery from a damaged index, we need some operating mode
+ * wherein event triggers are disabled. (Or we could implement
+ * heapscan-and-sort logic for that case, but having disaster recovery
+ * scenarios depend on code that's otherwise untested isn't appetizing.)
+ */
+ if (!IsUnderPostmaster)
+ return;
+
+ runlist = EventTriggerCommonSetup(parsetree,
+ EVT_DDLCommandStart, "ddl_command_start",
+ &trigdata);
+ if (runlist == NIL)
+ return;
/* Run the triggers. */
EventTriggerInvoke(runlist, &trigdata);
@@ -674,10 +728,7 @@ EventTriggerDDLCommandStart(Node *parsetree)
void
EventTriggerDDLCommandEnd(Node *parsetree)
{
- List *cachelist;
- List *runlist = NIL;
- ListCell *lc;
- const char *tag;
+ List *runlist;
EventTriggerData trigdata;
/*
@@ -687,53 +738,61 @@ EventTriggerDDLCommandEnd(Node *parsetree)
if (!IsUnderPostmaster)
return;
+ runlist = EventTriggerCommonSetup(parsetree,
+ EVT_DDLCommandEnd, "ddl_command_end",
+ &trigdata);
+ if (runlist == NIL)
+ return;
+
/*
- * See EventTriggerDDLCommandStart for a discussion about why this check is
- * important.
- *
+ * Make sure anything the main command did will be visible to the
+ * event triggers.
*/
-#ifdef USE_ASSERT_CHECKING
- if (assert_enabled)
- {
- const char *dbgtag;
+ CommandCounterIncrement();
- dbgtag = CreateCommandTag(parsetree);
- if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
- elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
- }
-#endif
+ /* Run the triggers. */
+ EventTriggerInvoke(runlist, &trigdata);
- /* Use cache to find triggers for this event; fast exit if none. */
- cachelist = EventCacheLookup(EVT_DDLCommandEnd);
- if (cachelist == NULL)
- return;
+ /* Cleanup. */
+ list_free(runlist);
+}
- /* Get the command tag. */
- tag = CreateCommandTag(parsetree);
+/*
+ * Fire sql_drop triggers.
+ */
+void
+EventTriggerSQLDrop(Node *parsetree)
+{
+ List *runlist;
+ EventTriggerData trigdata;
/*
- * Filter list of event triggers by command tag, and copy them into
- * our memory context. Once we start running the command trigers, or
- * indeed once we do anything at all that touches the catalogs, an
- * invalidation might leave cachelist pointing at garbage, so we must
- * do this before we can do much else.
+ * See EventTriggerDDLCommandStart for a discussion about why event
+ * triggers are disabled in single user mode.
*/
- foreach (lc, cachelist)
- {
- EventTriggerCacheItem *item = lfirst(lc);
+ if (!IsUnderPostmaster)
+ return;
- if (filter_event_trigger(&tag, item))
- {
- /* We must plan to fire this trigger. */
- runlist = lappend_oid(runlist, item->fnoid);
- }
- }
+ /*
+ * Use current state to determine whether this event fires at all. If there
+ * are no triggers for the sql_drop event, then we don't have anything to do
+ * here. Note that dropped object collection is disabled if this is the case,
+ * so even if we were to try to run, the list would be empty.
+ */
+ if (!currentEventTriggerState ||
+ slist_is_empty(&currentEventTriggerState->SQLDropList))
+ return;
- /* Construct event trigger data. */
- trigdata.type = T_EventTriggerData;
- trigdata.event = "ddl_command_end";
- trigdata.parsetree = parsetree;
- trigdata.tag = tag;
+ runlist = EventTriggerCommonSetup(parsetree,
+ EVT_SQLDrop, "sql_drop",
+ &trigdata);
+ /*
+ * Nothing to do if run list is empty. Note this shouldn't happen, because
+ * if there are no sql_drop events, then objects-to-drop wouldn't have been
+ * collected in the first place and we would have quitted above.
+ */
+ if (runlist == NIL)
+ return;
/*
* Make sure anything the main command did will be visible to the
@@ -741,8 +800,27 @@ EventTriggerDDLCommandEnd(Node *parsetree)
*/
CommandCounterIncrement();
+ /*
+ * Make sure pg_event_trigger_dropped_objects only works when running these
+ * triggers. Use PG_TRY to ensure in_sql_drop is reset even when one
+ * trigger fails. (This is perhaps not necessary, as the currentState
+ * variable will be removed shortly by our caller, but it seems better to
+ * play safe.)
+ */
+ currentEventTriggerState->in_sql_drop = true;
+
/* Run the triggers. */
- EventTriggerInvoke(runlist, &trigdata);
+ PG_TRY();
+ {
+ EventTriggerInvoke(runlist, &trigdata);
+ }
+ PG_CATCH();
+ {
+ currentEventTriggerState->in_sql_drop = false;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ currentEventTriggerState->in_sql_drop = false;
/* Cleanup. */
list_free(runlist);
@@ -832,3 +910,287 @@ EventTriggerSupportsObjectType(ObjectType obtype)
}
return true;
}
+
+/*
+ * Prepare event trigger state for a new complete query to run, if necessary;
+ * returns whether this was done. If it was, EventTriggerEndCompleteQuery must
+ * be called when the query is done, regardless of whether it succeeds or fails
+ * -- so use of a PG_TRY block is mandatory.
+ */
+bool
+EventTriggerBeginCompleteQuery(void)
+{
+ EventTriggerQueryState *state;
+ MemoryContext cxt;
+
+ /*
+ * Currently, sql_drop events are the only reason to have event trigger
+ * state at all; so if there are none, don't install one.
+ */
+ if (!trackDroppedObjectsNeeded())
+ return false;
+
+ cxt = AllocSetContextCreate(TopMemoryContext,
+ "event trigger state",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));
+ state->cxt = cxt;
+ slist_init(&(state->SQLDropList));
+ state->in_sql_drop = false;
+
+ state->previous = currentEventTriggerState;
+ currentEventTriggerState = state;
+
+ return true;
+}
+
+/*
+ * Query completed (or errored out) -- clean up local state, return to previous
+ * one.
+ *
+ * Note: it's an error to call this routine if EventTriggerBeginCompleteQuery
+ * returned false previously.
+ *
+ * Note: this might be called in the PG_CATCH block of a failing transaction,
+ * so be wary of running anything unnecessary. (In particular, it's probably
+ * unwise to try to allocate memory.)
+ */
+void
+EventTriggerEndCompleteQuery(void)
+{
+ EventTriggerQueryState *prevstate;
+
+ prevstate = currentEventTriggerState->previous;
+
+ /* this avoids the need for retail pfree of SQLDropList items: */
+ MemoryContextDelete(currentEventTriggerState->cxt);
+
+ currentEventTriggerState = prevstate;
+}
+
+/*
+ * Do we need to keep close track of objects being dropped?
+ *
+ * This is useful because there is a cost to running with them enabled.
+ */
+bool
+trackDroppedObjectsNeeded(void)
+{
+ /* true if any sql_drop event trigger exists */
+ return list_length(EventCacheLookup(EVT_SQLDrop)) > 0;
+}
+
+/*
+ * Support for dropped objects information on event trigger functions.
+ *
+ * We keep the list of objects dropped by the current command in current
+ * state's SQLDropList (comprising SQLDropObject items). Each time a new
+ * command is to start, a clean EventTriggerQueryState is created; commands
+ * that drop objects do the dependency.c dance to drop objects, which
+ * populates the current state's SQLDropList; when the event triggers are
+ * invoked they can consume the list via pg_event_trigger_dropped_objects().
+ * When the command finishes, the EventTriggerQueryState is cleared, and
+ * the one from the previous command is restored (when no command is in
+ * execution, the current state is NULL).
+ *
+ * All this lets us support the case that an event trigger function drops
+ * objects "reentrantly".
+ */
+
+/*
+ * Register one object as being dropped by the current command.
+ */
+void
+EventTriggerSQLDropAddObject(ObjectAddress *object)
+{
+ SQLDropObject *obj;
+ MemoryContext oldcxt;
+
+ if (!currentEventTriggerState)
+ return;
+
+ Assert(EventTriggerSupportsObjectType(getObjectClass(object)));
+
+ /* don't report temp schemas */
+ if (object->classId == NamespaceRelationId &&
+ isAnyTempNamespace(object->objectId))
+ return;
+
+ oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+ obj = palloc0(sizeof(SQLDropObject));
+ obj->address = *object;
+
+ /*
+ * Obtain schema names from the object's catalog tuple, if one exists;
+ * this lets us skip objects in temp schemas. We trust that ObjectProperty
+ * contains all object classes that can be schema-qualified.
+ */
+ if (is_objectclass_supported(object->classId))
+ {
+ Relation catalog;
+ HeapTuple tuple;
+
+ catalog = heap_open(obj->address.classId, AccessShareLock);
+ tuple = get_catalog_object_by_oid(catalog, obj->address.objectId);
+
+ if (tuple)
+ {
+ AttrNumber attnum;
+ Datum datum;
+ bool isnull;
+
+ attnum = get_object_attnum_namespace(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ {
+ Oid namespaceId;
+
+ namespaceId = DatumGetObjectId(datum);
+ /* Don't report objects in temp namespaces */
+ if (isAnyTempNamespace(namespaceId))
+ {
+ pfree(obj);
+ heap_close(catalog, AccessShareLock);
+ MemoryContextSwitchTo(oldcxt);
+ return;
+ }
+
+ obj->schemaname = get_namespace_name(namespaceId);
+ }
+ }
+
+ if (get_object_namensp_unique(obj->address.classId) &&
+ obj->address.objectSubId == 0)
+ {
+ attnum = get_object_attnum_name(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ obj->objname = pstrdup(NameStr(*DatumGetName(datum)));
+ }
+ }
+ }
+
+ heap_close(catalog, AccessShareLock);
+ }
+
+ /* object identity */
+ obj->objidentity = getObjectIdentity(&obj->address);
+
+ /* and object type, too */
+ obj->objecttype = getObjectTypeDescription(&obj->address);
+
+ slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * pg_event_trigger_dropped_objects
+ *
+ * Make the list of dropped objects available to the user function run by the
+ * Event Trigger.
+ */
+Datum
+pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext per_query_ctx;
+ MemoryContext oldcontext;
+ slist_iter iter;
+
+ /*
+ * Protect this function from being called out of context
+ */
+ if (!currentEventTriggerState ||
+ !currentEventTriggerState->in_sql_drop)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s can only be called in a sql_drop event trigger function",
+ "pg_event_trigger_dropped_objects()")));
+
+ /* check to see if caller supports us returning a tuplestore */
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* Build tuplestore to hold the result rows */
+ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ slist_foreach(iter, &(currentEventTriggerState->SQLDropList))
+ {
+ SQLDropObject *obj;
+ int i = 0;
+ Datum values[7];
+ bool nulls[7];
+
+ obj = slist_container(SQLDropObject, next, iter.cur);
+
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /* classid */
+ values[i++] = ObjectIdGetDatum(obj->address.classId);
+
+ /* objid */
+ values[i++] = ObjectIdGetDatum(obj->address.objectId);
+
+ /* objsubid */
+ values[i++] = Int32GetDatum(obj->address.objectSubId);
+
+ /* object_type */
+ values[i++] = CStringGetTextDatum(obj->objecttype);
+
+ /* schema_name */
+ if (obj->schemaname)
+ values[i++] = CStringGetTextDatum(obj->schemaname);
+ else
+ nulls[i++] = true;
+
+ /* object_name */
+ if (obj->objname)
+ values[i++] = CStringGetTextDatum(obj->objname);
+ else
+ nulls[i++] = true;
+
+ /* object_identity */
+ if (obj->objidentity)
+ values[i++] = CStringGetTextDatum(obj->objidentity);
+ else
+ nulls[i++] = true;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0d82141fef6..ed8502c6e4e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4478,7 +4478,6 @@ DropTrigStmt:
*
* QUERIES :
* CREATE EVENT TRIGGER ...
- * DROP EVENT TRIGGER ...
* ALTER EVENT TRIGGER ...
*
*****************************************************************************/
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a1c03f1f763..77b4e5368e7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -346,12 +346,13 @@ ProcessUtility(Node *parsetree,
do { \
if (isCompleteQuery) \
{ \
- EventTriggerDDLCommandStart(parsetree); \
+ EventTriggerDDLCommandStart(parsetree); \
} \
fncall; \
if (isCompleteQuery) \
{ \
- EventTriggerDDLCommandEnd(parsetree); \
+ EventTriggerSQLDrop(parsetree); \
+ EventTriggerDDLCommandEnd(parsetree); \
} \
} while (0)
@@ -366,10 +367,48 @@ ProcessUtility(Node *parsetree,
fncall; \
if (_supported) \
{ \
+ EventTriggerSQLDrop(parsetree); \
EventTriggerDDLCommandEnd(parsetree); \
} \
} while (0)
+/*
+ * UTILITY_BEGIN_QUERY and UTILITY_END_QUERY are a pair of macros to enclose
+ * execution of a single DDL command, to ensure the event trigger environment
+ * is appropriately set up before starting, and tore down after completion or
+ * error.
+ */
+#define UTILITY_BEGIN_QUERY(isComplete) \
+ do { \
+ bool _needCleanup = false; \
+ \
+ if (isComplete) \
+ { \
+ _needCleanup = EventTriggerBeginCompleteQuery(); \
+ } \
+ \
+ PG_TRY(); \
+ { \
+ /* avoid empty statement when followed by a semicolon */ \
+ (void) 0
+
+#define UTILITY_END_QUERY() \
+ } \
+ PG_CATCH(); \
+ { \
+ if (_needCleanup) \
+ { \
+ EventTriggerEndCompleteQuery(); \
+ } \
+ PG_RE_THROW(); \
+ } \
+ PG_END_TRY(); \
+ if (_needCleanup) \
+ { \
+ EventTriggerEndCompleteQuery(); \
+ } \
+ } while (0)
+
void
standard_ProcessUtility(Node *parsetree,
const char *queryString,
@@ -386,6 +425,8 @@ standard_ProcessUtility(Node *parsetree,
if (completionTag)
completionTag[0] = '\0';
+ UTILITY_BEGIN_QUERY(isCompleteQuery);
+
switch (nodeTag(parsetree))
{
/*
@@ -615,7 +656,10 @@ standard_ProcessUtility(Node *parsetree,
}
if (isCompleteQuery)
+ {
+ EventTriggerSQLDrop(parsetree);
EventTriggerDDLCommandEnd(parsetree);
+ }
}
break;
@@ -726,7 +770,10 @@ standard_ProcessUtility(Node *parsetree,
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
+ {
+ EventTriggerSQLDrop(parsetree);
EventTriggerDDLCommandEnd(parsetree);
+ }
break;
}
@@ -856,6 +903,12 @@ standard_ProcessUtility(Node *parsetree,
ereport(NOTICE,
(errmsg("relation \"%s\" does not exist, skipping",
atstmt->relation->relname)));
+
+ if (isCompleteQuery)
+ {
+ EventTriggerSQLDrop(parsetree);
+ EventTriggerDDLCommandEnd(parsetree);
+ }
}
break;
@@ -1248,8 +1301,9 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_DropOwnedStmt:
- /* no event triggers for global objects */
- DropOwnedObjects((DropOwnedStmt *) parsetree);
+ InvokeDDLCommandEventTriggers(
+ parsetree,
+ DropOwnedObjects((DropOwnedStmt *) parsetree));
break;
case T_ReassignOwnedStmt:
@@ -1372,6 +1426,8 @@ standard_ProcessUtility(Node *parsetree,
(int) nodeTag(parsetree));
break;
}
+
+ UTILITY_END_QUERY();
}
/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 94599aa44ba..700247e4741 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -345,7 +345,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
/*
* Would this proc be found (given the right args) by regprocedurein?
- * If not, we need to qualify it.
+ * If not, or if caller requests it, we need to qualify it.
*/
if (!force_qualify && FunctionIsVisible(procedure_oid))
nspname = NULL;
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index 34c61280c27..bbd3ae369d3 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -169,6 +169,8 @@ BuildEventTriggerCache(void)
event = EVT_DDLCommandStart;
else if (strcmp(evtevent, "ddl_command_end") == 0)
event = EVT_DDLCommandEnd;
+ else if (strcmp(evtevent, "sql_drop") == 0)
+ event = EVT_SQLDrop;
else
continue;
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index cbc4673d1b4..0eb8eefb8ff 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201303201
+#define CATALOG_VERSION_NO 201303271
#endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 4aee00233a9..151987ec1d3 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4693,6 +4693,9 @@ DATA(insert OID = 3473 ( spg_range_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0
DESCR("SP-GiST support for quad tree over range");
+/* event triggers */
+DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
+DESCR("list objects dropped by the current command");
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 74c150bd084..8ea99c19c99 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -13,13 +13,14 @@
#ifndef EVENT_TRIGGER_H
#define EVENT_TRIGGER_H
+#include "catalog/objectaddress.h"
#include "catalog/pg_event_trigger.h"
#include "nodes/parsenodes.h"
typedef struct EventTriggerData
{
NodeTag type;
- char *event; /* event name */
+ const char *event; /* event name */
Node *parsetree; /* parse tree */
const char *tag; /* command tag */
} EventTriggerData;
@@ -42,5 +43,11 @@ extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
+extern void EventTriggerSQLDrop(Node *parsetree);
+
+extern bool EventTriggerBeginCompleteQuery(void);
+extern void EventTriggerEndCompleteQuery(void);
+extern bool trackDroppedObjectsNeeded(void);
+extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
#endif /* EVENT_TRIGGER_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index cd8ac9462b5..e71876502e1 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1151,6 +1151,9 @@ extern Datum pg_identify_object(PG_FUNCTION_ARGS);
/* commands/constraint.c */
extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
+/* commands/event_trigger.c */
+extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+
/* commands/extension.c */
extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
extern Datum pg_available_extension_versions(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h
index c230995212d..945e5b53cb8 100644
--- a/src/include/utils/evtcache.h
+++ b/src/include/utils/evtcache.h
@@ -19,7 +19,8 @@
typedef enum
{
EVT_DDLCommandStart,
- EVT_DDLCommandEnd
+ EVT_DDLCommandEnd,
+ EVT_SQLDrop
} EventTriggerEvent;
typedef struct
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index bf020de16f6..060cd722bef 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -93,11 +93,204 @@ ERROR: event trigger "regress_event_trigger" does not exist
drop role regression_bob;
ERROR: role "regression_bob" cannot be dropped because some objects depend on it
DETAIL: owner of event trigger regress_event_trigger3
+-- cleanup before next test
-- these are all OK; the second one should emit a NOTICE
drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2;
NOTICE: event trigger "regress_event_trigger2" does not exist, skipping
drop event trigger regress_event_trigger3;
drop event trigger regress_event_trigger_end;
-drop function test_event_trigger();
-drop role regression_bob;
+-- test support for dropped objects
+CREATE SCHEMA schema_one authorization regression_bob;
+CREATE SCHEMA schema_two authorization regression_bob;
+CREATE SCHEMA audit_tbls authorization regression_bob;
+SET SESSION AUTHORIZATION regression_bob;
+CREATE TABLE schema_one.table_one(a int);
+CREATE TABLE schema_one."table two"(a int);
+CREATE TABLE schema_one.table_three(a int);
+CREATE TABLE audit_tbls.schema_one_table_two(the_value text);
+CREATE TABLE schema_two.table_two(a int);
+CREATE TABLE schema_two.table_three(a int, b text);
+CREATE TABLE audit_tbls.schema_two_table_three(the_value text);
+CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql
+ CALLED ON NULL INPUT
+ AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$;
+CREATE AGGREGATE schema_two.newton
+ (BASETYPE = int, SFUNC = schema_two.add, STYPE = int);
+RESET SESSION AUTHORIZATION;
+CREATE TABLE undroppable_objs (
+ object_type text,
+ object_identity text
+);
+INSERT INTO undroppable_objs VALUES
+('table', 'schema_one.table_three'),
+('table', 'audit_tbls.schema_two_table_three');
+CREATE TABLE dropped_objects (
+ type text,
+ schema text,
+ object text
+);
+-- This tests errors raised within event triggers; the one in audit_tbls
+-- uses 2nd-level recursive invocation via test_evtrig_dropped_objects().
+CREATE OR REPLACE FUNCTION undroppable() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+DECLARE
+ obj record;
+BEGIN
+ PERFORM 1 FROM pg_tables WHERE tablename = 'undroppable_objs';
+ IF NOT FOUND THEN
+ RAISE NOTICE 'table undroppable_objs not found, skipping';
+ RETURN;
+ END IF;
+ FOR obj IN
+ SELECT * FROM pg_event_trigger_dropped_objects() JOIN
+ undroppable_objs USING (object_type, object_identity)
+ LOOP
+ RAISE EXCEPTION 'object % of type % cannot be dropped',
+ obj.object_identity, obj.object_type;
+ END LOOP;
+END;
+$$;
+CREATE EVENT TRIGGER undroppable ON sql_drop
+ EXECUTE PROCEDURE undroppable();
+CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+DECLARE
+ obj record;
+BEGIN
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
+ LOOP
+ IF obj.object_type = 'table' THEN
+ EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%I',
+ format('%s_%s', obj.schema_name, obj.object_name));
+ END IF;
+
+ INSERT INTO dropped_objects
+ (type, schema, object) VALUES
+ (obj.object_type, obj.schema_name, obj.object_identity);
+ END LOOP;
+END
+$$;
+CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON sql_drop
+ WHEN TAG IN ('drop table', 'drop function', 'drop view',
+ 'drop owned', 'drop schema', 'alter table')
+ EXECUTE PROCEDURE test_evtrig_dropped_objects();
+ALTER TABLE schema_one.table_one DROP COLUMN a;
+DROP SCHEMA schema_one, schema_two CASCADE;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table schema_two.table_two
+drop cascades to table schema_two.table_three
+drop cascades to function schema_two.add(integer,integer)
+drop cascades to function schema_two.newton(integer)
+drop cascades to table schema_one.table_one
+drop cascades to table schema_one."table two"
+drop cascades to table schema_one.table_three
+NOTICE: table "schema_two_table_two" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_two"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.audit_tbls_schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+ERROR: object audit_tbls.schema_two_table_three of type table cannot be dropped
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+DELETE FROM undroppable_objs WHERE object_identity = 'audit_tbls.schema_two_table_three';
+DROP SCHEMA schema_one, schema_two CASCADE;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table schema_two.table_two
+drop cascades to table schema_two.table_three
+drop cascades to function schema_two.add(integer,integer)
+drop cascades to function schema_two.newton(integer)
+drop cascades to table schema_one.table_one
+drop cascades to table schema_one."table two"
+drop cascades to table schema_one.table_three
+NOTICE: table "schema_two_table_two" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_two"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.audit_tbls_schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "schema_one_table_one" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_one_table_one"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "schema_one_table two" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls."schema_one_table two""
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "schema_one_table_three" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_one_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+ERROR: object schema_one.table_three of type table cannot be dropped
+DELETE FROM undroppable_objs WHERE object_identity = 'schema_one.table_three';
+DROP SCHEMA schema_one, schema_two CASCADE;
+NOTICE: drop cascades to 7 other objects
+DETAIL: drop cascades to table schema_two.table_two
+drop cascades to table schema_two.table_three
+drop cascades to function schema_two.add(integer,integer)
+drop cascades to function schema_two.newton(integer)
+drop cascades to table schema_one.table_one
+drop cascades to table schema_one."table two"
+drop cascades to table schema_one.table_three
+NOTICE: table "schema_two_table_two" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_two"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.audit_tbls_schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "schema_one_table_one" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_one_table_one"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "schema_one_table two" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls."schema_one_table two""
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+NOTICE: table "schema_one_table_three" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_one_table_three"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast';
+ type | schema | object
+--------------+------------+-------------------------------------
+ table column | schema_one | schema_one.table_one.a
+ schema | | schema_two
+ table | schema_two | schema_two.table_two
+ type | schema_two | schema_two.table_two
+ type | schema_two | schema_two.table_two[]
+ table | audit_tbls | audit_tbls.schema_two_table_three
+ type | audit_tbls | audit_tbls.schema_two_table_three
+ type | audit_tbls | audit_tbls.schema_two_table_three[]
+ table | schema_two | schema_two.table_three
+ type | schema_two | schema_two.table_three
+ type | schema_two | schema_two.table_three[]
+ function | schema_two | schema_two.add(integer,integer)
+ aggregate | schema_two | schema_two.newton(integer)
+ schema | | schema_one
+ table | schema_one | schema_one.table_one
+ type | schema_one | schema_one.table_one
+ type | schema_one | schema_one.table_one[]
+ table | schema_one | schema_one."table two"
+ type | schema_one | schema_one."table two"
+ type | schema_one | schema_one."table two"[]
+ table | schema_one | schema_one.table_three
+ type | schema_one | schema_one.table_three
+ type | schema_one | schema_one.table_three[]
+(23 rows)
+
+DROP OWNED BY regression_bob;
+NOTICE: table "audit_tbls_schema_one_table_two" does not exist, skipping
+CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.audit_tbls_schema_one_table_two"
+PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement
+SELECT * FROM dropped_objects WHERE type = 'schema';
+ type | schema | object
+--------+--------+------------
+ schema | | schema_two
+ schema | | schema_one
+ schema | | audit_tbls
+(3 rows)
+
+DROP ROLE regression_bob;
+DROP EVENT TRIGGER regress_event_trigger_drop_objects;
+DROP EVENT TRIGGER undroppable;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index a07dcd75546..11d2ce5c133 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -97,10 +97,112 @@ drop event trigger regress_event_trigger;
-- should fail, regression_bob owns regress_event_trigger2/3
drop role regression_bob;
+-- cleanup before next test
-- these are all OK; the second one should emit a NOTICE
drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2;
drop event trigger regress_event_trigger3;
drop event trigger regress_event_trigger_end;
-drop function test_event_trigger();
-drop role regression_bob;
+
+-- test support for dropped objects
+CREATE SCHEMA schema_one authorization regression_bob;
+CREATE SCHEMA schema_two authorization regression_bob;
+CREATE SCHEMA audit_tbls authorization regression_bob;
+SET SESSION AUTHORIZATION regression_bob;
+
+CREATE TABLE schema_one.table_one(a int);
+CREATE TABLE schema_one."table two"(a int);
+CREATE TABLE schema_one.table_three(a int);
+CREATE TABLE audit_tbls.schema_one_table_two(the_value text);
+
+CREATE TABLE schema_two.table_two(a int);
+CREATE TABLE schema_two.table_three(a int, b text);
+CREATE TABLE audit_tbls.schema_two_table_three(the_value text);
+
+CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql
+ CALLED ON NULL INPUT
+ AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$;
+CREATE AGGREGATE schema_two.newton
+ (BASETYPE = int, SFUNC = schema_two.add, STYPE = int);
+
+RESET SESSION AUTHORIZATION;
+
+CREATE TABLE undroppable_objs (
+ object_type text,
+ object_identity text
+);
+INSERT INTO undroppable_objs VALUES
+('table', 'schema_one.table_three'),
+('table', 'audit_tbls.schema_two_table_three');
+
+CREATE TABLE dropped_objects (
+ type text,
+ schema text,
+ object text
+);
+
+-- This tests errors raised within event triggers; the one in audit_tbls
+-- uses 2nd-level recursive invocation via test_evtrig_dropped_objects().
+CREATE OR REPLACE FUNCTION undroppable() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+DECLARE
+ obj record;
+BEGIN
+ PERFORM 1 FROM pg_tables WHERE tablename = 'undroppable_objs';
+ IF NOT FOUND THEN
+ RAISE NOTICE 'table undroppable_objs not found, skipping';
+ RETURN;
+ END IF;
+ FOR obj IN
+ SELECT * FROM pg_event_trigger_dropped_objects() JOIN
+ undroppable_objs USING (object_type, object_identity)
+ LOOP
+ RAISE EXCEPTION 'object % of type % cannot be dropped',
+ obj.object_identity, obj.object_type;
+ END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER undroppable ON sql_drop
+ EXECUTE PROCEDURE undroppable();
+
+CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+DECLARE
+ obj record;
+BEGIN
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
+ LOOP
+ IF obj.object_type = 'table' THEN
+ EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%I',
+ format('%s_%s', obj.schema_name, obj.object_name));
+ END IF;
+
+ INSERT INTO dropped_objects
+ (type, schema, object) VALUES
+ (obj.object_type, obj.schema_name, obj.object_identity);
+ END LOOP;
+END
+$$;
+
+CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON sql_drop
+ WHEN TAG IN ('drop table', 'drop function', 'drop view',
+ 'drop owned', 'drop schema', 'alter table')
+ EXECUTE PROCEDURE test_evtrig_dropped_objects();
+
+ALTER TABLE schema_one.table_one DROP COLUMN a;
+DROP SCHEMA schema_one, schema_two CASCADE;
+DELETE FROM undroppable_objs WHERE object_identity = 'audit_tbls.schema_two_table_three';
+DROP SCHEMA schema_one, schema_two CASCADE;
+DELETE FROM undroppable_objs WHERE object_identity = 'schema_one.table_three';
+DROP SCHEMA schema_one, schema_two CASCADE;
+
+SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast';
+
+DROP OWNED BY regression_bob;
+SELECT * FROM dropped_objects WHERE type = 'schema';
+
+DROP ROLE regression_bob;
+
+DROP EVENT TRIGGER regress_event_trigger_drop_objects;
+DROP EVENT TRIGGER undroppable;