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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers