Here's a new version of this patch, rebased on top of the new pg_identify_object() stuff. Note that the regression test doesn't work yet, because I didn't adjust to the new identity output definition (the docs need work, too). But that's a simple change to do. I'm leaving that for later.
-- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services
*** a/doc/src/sgml/event-trigger.sgml --- b/doc/src/sgml/event-trigger.sgml *************** *** 36,43 **** The <literal>ddl_command_start</> event occurs just before the execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>DROP</> command. As an exception, however, this event does not occur for ! DDL commands targeting shared objects - databases, roles, and tablespaces ! - or for command targeting event triggers themselves. The event trigger mechanism does not support these object types. <literal>ddl_command_start</> also occurs just before the execution of a <literal>SELECT INTO</literal> command, since this is equivalent to --- 36,43 ---- The <literal>ddl_command_start</> event occurs just before the execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>DROP</> command. As an exception, however, this event does not occur for ! DDL commands targeting shared objects — databases, roles, and tablespaces ! — or for command targeting event triggers themselves. The event trigger mechanism does not support these object types. <literal>ddl_command_start</> also occurs just before the execution of a <literal>SELECT INTO</literal> command, since this is equivalent to *************** *** 46,51 **** --- 46,61 ---- </para> <para> + To list all objects that have been deleted as part of executing a + command, use the set returning + function <literal>pg_event_trigger_dropped_objects()</> from + your <literal>ddl_command_end</> event trigger code (see + <xref linkend="functions-event-triggers">). Note that + the trigger is executed after the objects have been deleted from the + system catalogs, so it's not possible to look them up anymore. + </para> + + <para> Event triggers (like other functions) cannot be executed in an aborted transaction. Thus, if a DDL command fails with an error, any associated <literal>ddl_command_end</> triggers will not be executed. Conversely, *************** *** 433,438 **** --- 443,453 ---- <entry align="center"><literal>X</literal></entry> </row> <row> + <entry align="left"><literal>DROP OWNED</literal></entry> + <entry align="center"><literal>X</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> <entry align="left"><literal>DROP RULE</literal></entry> <entry align="center"><literal>X</literal></entry> <entry align="center"><literal>X</literal></entry> *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 15980,15983 **** FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); --- 15980,16033 ---- <xref linkend="SQL-CREATETRIGGER">. </para> </sect1> + + <sect1 id="functions-event-triggers"> + <title>Event Trigger Functions</title> + + <indexterm> + <primary>pg_event_trigger_dropped_objects</primary> + </indexterm> + + <para> + Currently <productname>PostgreSQL</> provides one built-in event trigger + helper function, <function>pg_event_trigger_dropped_objects</>, which + lists all object dropped by the command in whose <literal>ddl_command_end</> + event it is called. If the function is run in a context other than a + <literal>ddl_command_end</> event trigger function, or if it's run in the + <literal>ddl_command_end</> event of a command that does not drop objects, + it will return the empty set. + </para> + + <para> + The <function>pg_event_trigger_dropped_objects</> function can be used + in an event trigger like this: + <programlisting> + CREATE FUNCTION test_event_trigger_for_drops() + RETURNS event_trigger LANGUAGE plpgsql AS $$ + DECLARE + obj record; + BEGIN + FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + RAISE NOTICE '% dropped object: % %.% %', + tg_tag, + obj.object_type, + obj.schema_name, + obj.object_name, + obj.subobject_name; + END LOOP; + END + $$; + CREATE EVENT TRIGGER test_event_trigger_for_drops + ON ddl_command_end + EXECUTE PROCEDURE test_event_trigger_for_drops(); + </programlisting> + </para> + + <para> + For more information about event triggers, + see <xref linkend="event-triggers">. + </para> + </sect1> + </chapter> *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 257,262 **** performDeletion(const ObjectAddress *object, --- 257,268 ---- { ObjectAddress *thisobj = targetObjects->refs + i; + if ((!(flags & PERFORM_DELETION_INTERNAL)) && + EventTriggerSupportsObjectType(getObjectClass(thisobj))) + { + evtrig_sqldrop_add_object(thisobj); + } + deleteOneObject(thisobj, &depRel, flags); } *************** *** 339,344 **** performMultipleDeletions(const ObjectAddresses *objects, --- 345,356 ---- { ObjectAddress *thisobj = targetObjects->refs + i; + if ((!(flags & PERFORM_DELETION_INTERNAL)) && + EventTriggerSupportsObjectType(getObjectClass(thisobj))) + { + evtrig_sqldrop_add_object(thisobj); + } + deleteOneObject(thisobj, &depRel, flags); } *************** *** 356,361 **** performMultipleDeletions(const ObjectAddresses *objects, --- 368,377 ---- * 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, *** a/src/backend/commands/event_trigger.c --- b/src/backend/commands/event_trigger.c *************** *** 19,30 **** --- 19,32 ---- #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 "miscadmin.h" *************** *** 39,44 **** --- 41,50 ---- #include "utils/syscache.h" #include "tcop/utility.h" + /* -- Globally visible state -- */ + + EventTriggerQueryState *currentEventTriggerState = NULL; + typedef struct { const char *obtypename; *************** *** 89,94 **** static event_trigger_support_data event_trigger_support[] = { --- 95,110 ---- { NULL, false } }; + /* Support for dropped objects */ + typedef struct SQLDropObject + { + ObjectAddress address; + char *objidentity; + char *schemaname; + char *objecttype; + slist_node next; + } SQLDropObject; + static void AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId); *************** *** 151,158 **** CreateEventTrigger(CreateEventTrigStmt *stmt) } /* Validate tag list, if any. */ ! if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL) validate_ddl_tags("tag", tags); /* * Give user a nice error message if an event trigger of the same name --- 167,178 ---- } /* Validate tag list, if any. */ ! if ((strcmp(stmt->eventname, "ddl_command_start") == 0 || ! strcmp(stmt->eventname, "ddl_command_end") == 0) ! && tags != NULL) ! { validate_ddl_tags("tag", tags); + } /* * Give user a nice error message if an event trigger of the same name *************** *** 220,226 **** 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) return EVENT_TRIGGER_COMMAND_TAG_OK; /* --- 240,247 ---- 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, "DROP OWNED") == 0) return EVENT_TRIGGER_COMMAND_TAG_OK; /* *************** *** 832,834 **** EventTriggerSupportsObjectType(ObjectType obtype) --- 853,1086 ---- } return true; } + + /* + * Prepare event trigger for running a new query. + */ + EventTriggerQueryState * + EventTriggerBeginCompleteQuery(void) + { + EventTriggerQueryState *prevstate; + EventTriggerQueryState *state; + MemoryContext cxt; + + prevstate = currentEventTriggerState; + + 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)); + + currentEventTriggerState = state; + + return prevstate; + } + + /* + * Query completed (or errored out) -- clean up local state + * + * XXX we currently don't use "abort" for anything ... + */ + void + EventTriggerEndCompleteQuery(EventTriggerQueryState *prevstate, bool abort) + { + /* this avoids the need for retail pfree of SQLDropList items: */ + MemoryContextDelete(currentEventTriggerState->cxt); + + currentEventTriggerState = prevstate; + } + + /* + * 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 + evtrig_sqldrop_add_object(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); + } + } + } + + heap_close(catalog, AccessShareLock); + } + + /* object name */ + 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; + + /* XXX can this actually happen? */ + if (!currentEventTriggerState) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s can only be called when there's a command in execution", + "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[6]; + bool nulls[6]; + + 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_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; + } *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 370,375 **** ProcessUtility(Node *parsetree, --- 370,413 ---- } \ } 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 _isComplete = isComplete; \ + EventTriggerQueryState *_prevstate = NULL; \ + \ + if (_isComplete) \ + { \ + _prevstate = EventTriggerBeginCompleteQuery(); \ + } \ + \ + PG_TRY(); \ + { \ + /* avoid empty statement when followed by a semicolon */ \ + (void) 0 + + #define UTILITY_END_QUERY() \ + } \ + PG_CATCH(); \ + { \ + if (_isComplete) \ + { \ + EventTriggerEndCompleteQuery(_prevstate, true); \ + } \ + PG_RE_THROW(); \ + } \ + PG_END_TRY(); \ + if (_isComplete) \ + { \ + EventTriggerEndCompleteQuery(_prevstate, false); \ + } \ + } while (0) + void standard_ProcessUtility(Node *parsetree, const char *queryString, *************** *** 386,391 **** standard_ProcessUtility(Node *parsetree, --- 424,431 ---- if (completionTag) completionTag[0] = '\0'; + UTILITY_BEGIN_QUERY(isCompleteQuery); + switch (nodeTag(parsetree)) { /* *************** *** 856,861 **** standard_ProcessUtility(Node *parsetree, --- 896,904 ---- ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", atstmt->relation->relname))); + + if (isCompleteQuery) + EventTriggerDDLCommandEnd(parsetree); } break; *************** *** 1248,1255 **** standard_ProcessUtility(Node *parsetree, break; case T_DropOwnedStmt: ! /* no event triggers for global objects */ ! DropOwnedObjects((DropOwnedStmt *) parsetree); break; case T_ReassignOwnedStmt: --- 1291,1299 ---- break; case T_DropOwnedStmt: ! InvokeDDLCommandEventTriggers( ! parsetree, ! DropOwnedObjects((DropOwnedStmt *) parsetree)); break; case T_ReassignOwnedStmt: *************** *** 1372,1377 **** standard_ProcessUtility(Node *parsetree, --- 1416,1423 ---- (int) nodeTag(parsetree)); break; } + + UTILITY_END_QUERY(); } /* *** a/src/backend/utils/adt/regproc.c --- b/src/backend/utils/adt/regproc.c *************** *** 345,351 **** 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 (!force_qualify && FunctionIsVisible(procedure_oid)) nspname = NULL; --- 345,351 ---- /* * Would this proc be found (given the right args) by regprocedurein? ! * If not, we need to qualify it -- unless caller wants it bare. */ if (!force_qualify && FunctionIsVisible(procedure_oid)) nspname = NULL; *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 4693,4698 **** DATA(insert OID = 3473 ( spg_range_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0 --- 4693,4701 ---- 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,26,25,25,25}" "{o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_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, *** a/src/include/commands/event_trigger.h --- b/src/include/commands/event_trigger.h *************** *** 13,21 **** --- 13,29 ---- #ifndef EVENT_TRIGGER_H #define EVENT_TRIGGER_H + #include "catalog/objectaddress.h" #include "catalog/pg_event_trigger.h" + #include "lib/ilist.h" #include "nodes/parsenodes.h" + typedef struct EventTriggerQueryState + { + slist_head SQLDropList; + MemoryContext cxt; + } EventTriggerQueryState; + typedef struct EventTriggerData { NodeTag type; *************** *** 43,46 **** extern bool EventTriggerSupportsObjectType(ObjectType obtype); --- 51,59 ---- extern void EventTriggerDDLCommandStart(Node *parsetree); extern void EventTriggerDDLCommandEnd(Node *parsetree); + extern EventTriggerQueryState *EventTriggerBeginCompleteQuery(void); + extern void EventTriggerEndCompleteQuery(EventTriggerQueryState *prevstate, + bool abort); + extern void evtrig_sqldrop_add_object(ObjectAddress *object); + #endif /* EVENT_TRIGGER_H */ *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 1151,1156 **** extern Datum pg_identify_object(PG_FUNCTION_ARGS); --- 1151,1159 ---- /* 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); *** a/src/test/regress/expected/event_trigger.out --- b/src/test/regress/expected/event_trigger.out *************** *** 93,103 **** 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 -- 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; --- 93,224 ---- 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; ! -- 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.table_two(the_value schema_one.table_two); ! CREATE TABLE schema_two.table_two(a int); ! CREATE TABLE schema_two.table_three(a int, b text); ! CREATE TABLE audit_tbls.table_three(the_value schema_two.table_three); ! 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 dropped_objects (type text, ! schema text, ! object text, ! subobject text, ! curr_user text, ! sess_user text); ! 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.%s', ! obj.object_identity); ! END IF; ! ! INSERT INTO dropped_objects ! (type, schema, object, curr_user, sess_user) VALUES ! (obj.object_type, obj.schema_name, obj.object_identity, ! current_user, session_user); ! END LOOP; ! END ! $$; ! CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON ddl_command_end ! 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 9 other objects ! DETAIL: drop cascades to table schema_two.table_two ! drop cascades to table schema_two.table_three ! drop cascades to table audit_tbls.table_three column the_value ! 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 audit_tbls.table_two column the_value ! drop cascades to table schema_one.table_three ! NOTICE: table "table_two" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_three" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_one" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_one" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_two" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_three" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.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 | subobject | curr_user | sess_user ! --------------+------------+--------------------------+-----------+-----------+----------- ! table column | schema_one | table_one.a | | alvherre | alvherre ! schema | | schema_two | | alvherre | alvherre ! table | audit_tbls | table_two | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_two | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_two[] | | alvherre | alvherre ! table | schema_two | table_two | | alvherre | alvherre ! type | schema_two | schema_two.table_two | | alvherre | alvherre ! type | schema_two | schema_two.table_two[] | | alvherre | alvherre ! table | audit_tbls | table_three | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_three | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_three[] | | alvherre | alvherre ! table | schema_two | table_three | | alvherre | alvherre ! type | schema_two | schema_two.table_three | | alvherre | alvherre ! type | schema_two | schema_two.table_three[] | | alvherre | alvherre ! table column | audit_tbls | table_three.the_value | | alvherre | alvherre ! function | schema_two | add(integer,integer) | | alvherre | alvherre ! function | schema_two | newton(integer) | | alvherre | alvherre ! schema | | schema_one | | alvherre | alvherre ! table | schema_one | table_one | | alvherre | alvherre ! type | schema_one | schema_one.table_one | | alvherre | alvherre ! type | schema_one | schema_one.table_one[] | | alvherre | alvherre ! table | schema_one | table_two | | alvherre | alvherre ! type | schema_one | schema_one.table_two | | alvherre | alvherre ! type | schema_one | schema_one.table_two[] | | alvherre | alvherre ! table column | audit_tbls | table_two.the_value | | alvherre | alvherre ! table | schema_one | table_three | | alvherre | alvherre ! type | schema_one | schema_one.table_three | | alvherre | alvherre ! type | schema_one | schema_one.table_three[] | | alvherre | alvherre ! (28 rows) ! ! drop owned by regression_bob; ! SELECT * FROM dropped_objects WHERE type = 'schema'; ! type | schema | object | subobject | curr_user | sess_user ! --------+--------+------------+-----------+-----------+----------- ! schema | | schema_two | | alvherre | alvherre ! schema | | schema_one | | alvherre | alvherre ! schema | | audit_tbls | | alvherre | alvherre ! (3 rows) ! drop role regression_bob; + DROP EVENT TRIGGER regress_event_trigger_drop_objects; *** a/src/test/regress/sql/event_trigger.sql --- b/src/test/regress/sql/event_trigger.sql *************** *** 97,106 **** drop event trigger regress_event_trigger; -- should fail, regression_bob owns regress_event_trigger2/3 drop role regression_bob; -- 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; --- 97,172 ---- -- 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; ! ! -- 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.table_two(the_value schema_one.table_two); ! ! CREATE TABLE schema_two.table_two(a int); ! CREATE TABLE schema_two.table_three(a int, b text); ! CREATE TABLE audit_tbls.table_three(the_value schema_two.table_three); ! ! 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 dropped_objects (type text, ! schema text, ! object text, ! subobject text, ! curr_user text, ! sess_user text); ! ! 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.%s', ! obj.object_identity); ! END IF; ! ! INSERT INTO dropped_objects ! (type, schema, object, curr_user, sess_user) VALUES ! (obj.object_type, obj.schema_name, obj.object_identity, ! current_user, session_user); ! END LOOP; ! END ! $$; ! ! CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON ddl_command_end ! 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; ! ! 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;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers