diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 71241c8..bcb8bee 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -27,9 +27,9 @@
An event trigger fires whenever the event with which it is associated
occurs in the database in which it is defined. Currently, the only
- supported events are ddl_command_start>
- and ddl_command_end>. Support for additional events may be
- added in future releases.
+ supported events
+ are ddl_command_start>, ddl_command_end>. Support
+ for additional events may be added in future releases.
@@ -46,6 +46,14 @@
+ To list all objects that have been deleted as part of executing a
+ command, use the Set Returning
+ Function pg_event_trigger_dropped_objects()> from
+ your ddl_command_end> event trigger code. Note that happens
+ after the objects have been deleted, so no catalog lookup is possible.
+
+
+
Event triggers (like other functions) cannot be executed in an aborted
transaction. Thus, if a DDL command fails with an error, any associated
ddl_command_end> triggers will not be executed. Conversely,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 92a79d3..687dd94 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15688,9 +15688,55 @@ FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
choose a trigger name that comes after the name of any other trigger
you might have on the table.
-
+
For more information about creating triggers, see
.
+
+
+ Event Trigger Functions
+
+
+ pg_dropped_objects
+
+
+
+ Currently PostgreSQL> provides one built in event trigger
+ helper function, pg_event_trigger_dropped_objects>, which
+ will list all object dropped by a DROP> command. That
+ listing includes multiple targets of the command, as in DROP
+ TABLE a, b, c; and objects dropped because of
+ a CASCADE> dependency.
+
+
+
+ The pg_event_trigger_dropped_objects> function can be used
+ in an event trigger like this:
+
+create function test_event_trigger_for_sql_drop()
+ returns event_trigger as $$
+DECLARE
+ obj record;
+BEGIN
+ RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
+ LOOP
+ RAISE NOTICE '% dropped object: % % % %',
+ tg_tag,
+ obj.classId::regclass,
+ obj.classId, obj.objid, obj.objsubid;
+ END LOOP;
+END
+$$ language plpgsql;
+
+
+
+
+ For more information about event triggers,
+ see .
+
+
+
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 81d2687..ab0f13c 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -30,6 +30,7 @@
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
+#include "commands/event_trigger.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "executor/spi.h"
@@ -1955,6 +1956,7 @@ CommitTransaction(void)
AtEOXact_HashTables(true);
AtEOXact_PgStat(true);
AtEOXact_Snapshot(true);
+ AtEOXact_EventTrigger(true);
pgstat_report_xact_timestamp(0);
CurrentResourceOwner = NULL;
@@ -2208,6 +2210,7 @@ PrepareTransaction(void)
AtEOXact_HashTables(true);
/* don't call AtEOXact_PgStat here */
AtEOXact_Snapshot(true);
+ AtEOXact_EventTrigger(true);
CurrentResourceOwner = NULL;
ResourceOwnerDelete(TopTransactionResourceOwner);
@@ -2382,6 +2385,7 @@ CleanupTransaction(void)
*/
AtCleanup_Portals(); /* now safe to release portal memory */
AtEOXact_Snapshot(false); /* and release the transaction's snapshots */
+ AtEOXact_EventTrigger(false); /* and reset Event Trigger internal state */
CurrentResourceOwner = NULL; /* and resource owner */
if (TopTransactionResourceOwner)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d203725..0543076 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -347,7 +347,15 @@ performMultipleDeletions(const ObjectAddresses *objects,
*/
for (i = 0; i < targetObjects->numrefs; i++)
{
- ObjectAddress *thisobj = targetObjects->refs + i;
+ ObjectAddress *thisobj;
+
+ thisobj = targetObjects->refs + i;
+
+ if (EventTriggerSQLDropInProgress &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ add_exact_object_address(thisobj, EventTriggerSQLDropList);
+ }
deleteOneObject(thisobj, &depRel, flags);
}
@@ -2175,6 +2183,18 @@ record_object_address_dependencies(const ObjectAddress *depender,
behavior);
}
+int
+get_object_addresses_numelements(const ObjectAddresses *addresses)
+{
+ return addresses->numrefs;
+}
+
+ObjectAddress *
+get_object_addresses_element(const ObjectAddresses *addresses, int i)
+{
+ return addresses->refs + i;
+}
+
/*
* Clean up when done with an ObjectAddresses array.
*/
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 18b3753..9ed5715 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -25,6 +25,7 @@
#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,6 +40,10 @@
#include "utils/syscache.h"
#include "tcop/utility.h"
+/* Globally visible state variables */
+bool EventTriggerSQLDropInProgress = false;
+ObjectAddresses *EventTriggerSQLDropList = NULL;
+
typedef struct
{
const char *obtypename;
@@ -150,8 +155,12 @@ 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)
+ && tags != NULL)
+ {
validate_ddl_tags("tag", tags);
+ }
/*
* Give user a nice error message if an event trigger of the same name
@@ -739,6 +748,14 @@ EventTriggerDDLCommandEnd(Node *parsetree)
/* Cleanup. */
list_free(runlist);
+
+ if (EventTriggerSQLDropInProgress)
+ {
+ free_object_addresses(EventTriggerSQLDropList);
+
+ EventTriggerSQLDropInProgress = false;
+ EventTriggerSQLDropList = NULL;
+ }
}
/*
@@ -825,3 +842,107 @@ EventTriggerSupportsObjectType(ObjectType obtype)
}
return true;
}
+
+/*
+ * SQL DROP event support functions
+ */
+void
+EventTriggerInitDropList(void)
+{
+ EventTriggerSQLDropInProgress = true;
+ EventTriggerSQLDropList = new_object_addresses();
+}
+
+/*
+ * AtEOXact_EventTrigger
+ * Event Trigger's cleanup function for end of transaction
+ */
+void
+AtEOXact_EventTrigger(bool isCommit)
+{
+ /* even on success we want to reset EventTriggerSQLDropInProgress */
+ EventTriggerSQLDropInProgress = false;
+}
+
+/*
+ * 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;
+ int i;
+
+ /*
+ * This function is meant to be called from within an event trigger in
+ * order to get the list of objects dropped, if any.
+ */
+ if (!EventTriggerSQLDropInProgress)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("pg_dropped_objects() can only be called from an event trigger function")));
+
+ /* 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);
+
+ for (i = 0; i < get_object_addresses_numelements(EventTriggerSQLDropList); i++)
+ {
+ ObjectAddress *object;
+ Datum values[3];
+ bool nulls[3];
+
+ /* Emit result row */
+ object = get_object_addresses_element(EventTriggerSQLDropList, i);
+
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, 0, sizeof(nulls));
+
+ /* classid */
+ values[0] = ObjectIdGetDatum(object->classId);
+
+ /* objid */
+ values[1] = ObjectIdGetDatum(object->objectId);
+
+ /* objsubid */
+ if (OidIsValid(object->objectSubId))
+ values[2] = ObjectIdGetDatum(object->objectSubId);
+ else
+ nulls[2] = true;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 8904c6f..7ed05d3 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -698,18 +698,33 @@ standard_ProcessUtility(Node *parsetree,
{
DropStmt *stmt = (DropStmt *) parsetree;
- if (isCompleteQuery
- && EventTriggerSupportsObjectType(stmt->removeType))
+ /*
+ * don't run any event trigger when we require not to have open
+ * a transaction
+ */
+ if (stmt->removeType == OBJECT_INDEX && stmt->concurrent)
+ PreventTransactionChain(isTopLevel,
+ "DROP INDEX CONCURRENTLY");
+
+ if (isCompleteQuery &&
+ EventTriggerSupportsObjectType(stmt->removeType))
+ {
EventTriggerDDLCommandStart(parsetree);
+ /*
+ * cater with multiple targets and cascading drops.
+ *
+ * Initialize that after having called the
+ * ddl_command_start triggers so that
+ * EventTriggerSQLDropInProgress is still false there, as
+ * that protects pg_dropped_objects() calls.
+ */
+ EventTriggerInitDropList();
+ }
+
switch (stmt->removeType)
{
case OBJECT_INDEX:
- if (stmt->concurrent)
- PreventTransactionChain(isTopLevel,
- "DROP INDEX CONCURRENTLY");
- /* fall through */
-
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
@@ -723,8 +738,9 @@ standard_ProcessUtility(Node *parsetree,
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
+ {
EventTriggerDDLCommandEnd(parsetree);
-
+ }
break;
}
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8e0837f..846726c 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -191,6 +191,11 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
ObjectAddresses *referenced,
DependencyType behavior);
+extern int get_object_addresses_numelements(const ObjectAddresses *addresses);
+
+extern ObjectAddress *get_object_addresses_element(const ObjectAddresses *addresses,
+ int i);
+
extern void free_object_addresses(ObjectAddresses *addrs);
/* in pg_depend.c */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 028e168..3981513 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4679,7 +4679,9 @@ DESCR("SP-GiST support for quad tree over range");
DATA(insert OID = 3473 ( spg_range_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ spg_range_quad_leaf_consistent _null_ _null_ _null_ ));
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}" "{o,o,o}" "{classid, objid, objsubid}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
+DESCR("list an extension's version update paths");
/*
* 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 74c150b..7ed92b0 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -13,9 +13,23 @@
#ifndef EVENT_TRIGGER_H
#define EVENT_TRIGGER_H
+#include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_event_trigger.h"
#include "nodes/parsenodes.h"
+/*
+ * Global objects that we need to keep track of for benefits of Event Triggers.
+ *
+ * The EventTriggerSQLDropList is a list of ObjectAddress filled in from
+ * dependency.c doDeletion() function. Only objects that are supported as in
+ * EventTriggerSupportsObjectType() get appended here. ProcessUtility is
+ * responsible for resetting this list to NIL at the beginning of any DROP
+ * operation.
+ */
+extern bool EventTriggerSQLDropInProgress;
+extern ObjectAddresses *EventTriggerSQLDropList;
+
typedef struct EventTriggerData
{
NodeTag type;
@@ -43,4 +57,11 @@ extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
+extern void EventTriggerInitDropList(void);
+extern List *EventTriggerAppendToDropList(ObjectAddress *object);
+extern void EventTriggerSQLDrop(Node *parsetree);
+
+extern void AtEOXact_EventTrigger(bool isCommit);
+
+
#endif /* EVENT_TRIGGER_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 533539c..d51b829 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1146,6 +1146,9 @@ extern Datum pg_describe_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/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index bf020de..75d4ee7 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -93,11 +93,74 @@ 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
+-- now test pg_event_trigger_dropped_objects()
+create function test_event_trigger_dropped_objects() returns event_trigger as $$
+DECLARE
+ obj record;
+BEGIN
+ RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
+ LOOP
+ -- we can't output the full data that we have here because the OID
+ -- would change each time we run the regression tests.
+ --
+ -- obj.classId, obj.objid, obj.objsubid;
+ RAISE NOTICE '% dropped object: %', tg_tag, obj.classId::regclass;
+ END LOOP;
+END
+$$ language plpgsql;
+NOTICE: test_event_trigger: ddl_command_start CREATE FUNCTION
+NOTICE: test_event_trigger: ddl_command_end CREATE FUNCTION
+-- OK
+create event trigger regress_event_trigger_drop_objects on ddl_command_end
+ when tag in ('drop table', 'drop function', 'drop view')
+ execute procedure test_event_trigger_dropped_objects();
+-- a simple enough test: cascade
+create table evt_a(id serial);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
+create view evt_a_v as select id from evt_a;
+NOTICE: test_event_trigger: ddl_command_end CREATE VIEW
+drop table evt_a cascade;
+NOTICE: drop cascades to view evt_a_v
+NOTICE: test_event_trigger: ddl_command_end DROP TABLE
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_rewrite
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_class
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_class
+NOTICE: DROP TABLE dropped object: pg_class
+NOTICE: test_event_trigger: ddl_command_end DROP TABLE
+-- another test with multiple targets
+create table evt_a(id serial);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
+create table evt_b(id serial);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
+drop table evt_a, evt_b;
+NOTICE: test_event_trigger: ddl_command_end DROP TABLE
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_class
+NOTICE: DROP TABLE dropped object: pg_class
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_type
+NOTICE: DROP TABLE dropped object: pg_class
+NOTICE: DROP TABLE dropped object: pg_class
+NOTICE: test_event_trigger: ddl_command_end DROP TABLE
+-- these are all OK; the third one should emit a NOTICE
+drop event trigger if exists regress_event_trigger_drop_objects;
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 function test_event_trigger_dropped_objects();
drop role regression_bob;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index a07dcd7..4d92071 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -97,10 +97,45 @@ 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
+-- now test pg_event_trigger_dropped_objects()
+create function test_event_trigger_dropped_objects() returns event_trigger as $$
+DECLARE
+ obj record;
+BEGIN
+ RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()
+ LOOP
+ -- we can't output the full data that we have here because the OID
+ -- would change each time we run the regression tests.
+ --
+ -- obj.classId, obj.objid, obj.objsubid;
+ RAISE NOTICE '% dropped object: %', tg_tag, obj.classId::regclass;
+ END LOOP;
+END
+$$ language plpgsql;
+
+-- OK
+create event trigger regress_event_trigger_drop_objects on ddl_command_end
+ when tag in ('drop table', 'drop function', 'drop view')
+ execute procedure test_event_trigger_dropped_objects();
+
+-- a simple enough test: cascade
+create table evt_a(id serial);
+create view evt_a_v as select id from evt_a;
+drop table evt_a cascade;
+
+-- another test with multiple targets
+create table evt_a(id serial);
+create table evt_b(id serial);
+drop table evt_a, evt_b;
+
+-- these are all OK; the third one should emit a NOTICE
+drop event trigger if exists regress_event_trigger_drop_objects;
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 function test_event_trigger_dropped_objects();
+
drop role regression_bob;