Add sql_drop event for event triggers

This event takes place just before ddl_command_end, and is fired if and
only if at least one object has been dropped by the command.  (For
instance, DROP TABLE IF EXISTS of a table that does not in fact exist
will not lead to such a trigger firing).  Commands that drop multiple
objects (such as DROP SCHEMA or DROP OWNED BY) will cause a single event
to fire.  Some firings might be surprising, such as
ALTER TABLE DROP COLUMN.

The trigger is fired after the drop has taken place, because that has
been deemed the safest design, to avoid exposing possibly-inconsistent
internal state (system catalogs as well as current transaction) to the
user function code.  This means that careful tracking of object
identification is required during the object removal phase.

Like other currently existing events, there is support for tag
filtering.

To support the new event, add a new pg_event_trigger_dropped_objects()
set-returning function, which returns a set of rows comprising the
objects affected by the command.  This is to be used within the user
function code, and is mostly modelled after the recently introduced
pg_identify_object() function.

Catalog version bumped due to the new function.

Dimitri Fontaine and Álvaro Herrera
Review by Robert Haas, Tom Lane
This commit is contained in:
Alvaro Herrera 2013-03-27 16:02:10 -03:00
parent 593c39d156
commit 473ab40c8b
15 changed files with 1088 additions and 120 deletions

View File

@ -27,17 +27,19 @@
<para>
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 <literal>ddl_command_start</>
and <literal>ddl_command_end</>. Support for additional events may be
added in future releases.
supported events are
<literal>ddl_command_start</>,
<literal>ddl_command_end</>
and <literal>sql_drop</>.
Support for additional events may be added in future releases.
</para>
<para>
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
DDL commands targeting shared objects &mdash; databases, roles, and tablespaces
&mdash; 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
@ -45,6 +47,17 @@
event occurs just after the execution of this same set of commands.
</para>
<para>
The <literal>sql_drop</> event occurs just before the
<literal>ddl_command_end</> event trigger for any operation that drops
database objects. To list the objects that have been dropped, use the set
returning function <literal>pg_event_trigger_dropped_objects()</> from your
<literal>sql_drop</> 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
@ -99,6 +112,7 @@
<entry>command tag</entry>
<entry><literal>ddl_command_start</literal></entry>
<entry><literal>ddl_command_end</literal></entry>
<entry><literal>sql_drop</literal></entry>
</row>
</thead>
<tbody>
@ -106,401 +120,487 @@
<entry align="left"><literal>ALTER AGGREGATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER COLLATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER CONVERSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER DOMAIN</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER EXTENSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER FOREIGN DATA WRAPPER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER FOREIGN TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER FUNCTION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER LANGUAGE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR CLASS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR FAMILY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER SEQUENCE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER SERVER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH CONFIGURATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH DICTIONARY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH PARSER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH TEMPLATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TRIGGER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TYPE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER USER MAPPING</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE AGGREGATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE CAST</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE COLLATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE CONVERSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE DOMAIN</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE EXTENSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE FOREIGN DATA WRAPPER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE FOREIGN TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE FUNCTION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE INDEX</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE LANGUAGE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR CLASS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR FAMILY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE RULE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE SEQUENCE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE SERVER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TABLE AS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH CONFIGURATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH DICTIONARY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH PARSER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH TEMPLATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TRIGGER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TYPE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE USER MAPPING</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP AGGREGATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP CAST</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP COLLATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP CONVERSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP DOMAIN</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP EXTENSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP FOREIGN DATA WRAPPER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP FOREIGN TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP FUNCTION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP INDEX</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP LANGUAGE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR CLASS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR FAMILY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<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>
<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>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP SEQUENCE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP SERVER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH CONFIGURATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH DICTIONARY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH PARSER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH TEMPLATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TRIGGER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TYPE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP USER MAPPING</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>SELECT INTO</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
</tbody>
</tgroup>

View File

@ -15980,4 +15980,117 @@ FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
<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</>.
</para>
<para>
<function>pg_event_trigger_dropped_objects</> returns a list of all object
dropped by the command in whose <literal>sql_drop</> event it is called.
If called in any other context,
<function>pg_event_trigger_dropped_objects</> raises an error.
<function>pg_event_trigger_dropped_objects</> returns the following columns:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Name</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>classid</literal></entry>
<entry><type>Oid</type></entry>
<entry>OID of catalog the object belonged in</entry>
</row>
<row>
<entry><literal>objid</literal></entry>
<entry><type>Oid</type></entry>
<entry>OID the object had within the catalog</entry>
</row>
<row>
<entry><literal>objsubid</literal></entry>
<entry><type>int32</type></entry>
<entry>Object sub-id (e.g. attribute number for columns)</entry>
</row>
<row>
<entry><literal>object_type</literal></entry>
<entry><type>text</type></entry>
<entry>Type of the object</entry>
</row>
<row>
<entry><literal>schema_name</literal></entry>
<entry><type>text</type></entry>
<entry>
Name of the schema the object belonged in, if any; otherwise <literal>NULL</>.
No quoting is applied.
</entry>
</row>
<row>
<entry><literal>object_name</literal></entry>
<entry><type>text</type></entry>
<entry>
Name of the object, if the combination of schema and name can be
used as an unique identifier for the object; otherwise <literal>NULL</>.
No quoting is applied, and name is never schema-qualified.
</entry>
</row>
<row>
<entry><literal>object_identity</literal></entry>
<entry><type>text</type></entry>
<entry>
Text rendering of the object identity, schema-qualified. Each and every
identifier present in the identity is quoted if necessary.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</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.object_identity;
END LOOP;
END
$$;
CREATE EVENT TRIGGER test_event_trigger_for_drops
ON sql_drop
EXECUTE PROCEDURE test_event_trigger_for_drops();
</programlisting>
</para>
<para>
For more information about event triggers,
see <xref linkend="event-triggers">.
</para>
</sect1>
</chapter>

View File

@ -190,6 +190,44 @@ static bool stack_address_present_add_flags(const ObjectAddress *object,
ObjectAddressStack *stack);
/*
* 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).
@ -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,

View File

@ -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)
{
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 *cachelist;
ListCell *lc;
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,42 @@ EventTriggerDDLCommandStart(Node *parsetree)
void
EventTriggerDDLCommandEnd(Node *parsetree)
{
List *cachelist;
List *runlist = NIL;
ListCell *lc;
const char *tag;
List *runlist;
EventTriggerData trigdata;
/*
* See EventTriggerDDLCommandStart for a discussion about why event
* triggers are disabled in single user mode.
*/
if (!IsUnderPostmaster)
return;
runlist = EventTriggerCommonSetup(parsetree,
EVT_DDLCommandEnd, "ddl_command_end",
&trigdata);
if (runlist == NIL)
return;
/*
* Make sure anything the main command did will be visible to the
* event triggers.
*/
CommandCounterIncrement();
/* Run the triggers. */
EventTriggerInvoke(runlist, &trigdata);
/* Cleanup. */
list_free(runlist);
}
/*
* Fire sql_drop triggers.
*/
void
EventTriggerSQLDrop(Node *parsetree)
{
List *runlist;
EventTriggerData trigdata;
/*
@ -688,52 +774,25 @@ EventTriggerDDLCommandEnd(Node *parsetree)
return;
/*
* See EventTriggerDDLCommandStart for a discussion about why this check is
* important.
*
* 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.
*/
#ifdef USE_ASSERT_CHECKING
if (assert_enabled)
{
const char *dbgtag;
dbgtag = CreateCommandTag(parsetree);
if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
}
#endif
/* Use cache to find triggers for this event; fast exit if none. */
cachelist = EventCacheLookup(EVT_DDLCommandEnd);
if (cachelist == NULL)
if (!currentEventTriggerState ||
slist_is_empty(&currentEventTriggerState->SQLDropList))
return;
/* Get the command tag. */
tag = CreateCommandTag(parsetree);
runlist = EventTriggerCommonSetup(parsetree,
EVT_SQLDrop, "sql_drop",
&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.
* 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.
*/
foreach (lc, cachelist)
{
EventTriggerCacheItem *item = lfirst(lc);
if (filter_event_trigger(&tag, item))
{
/* We must plan to fire this trigger. */
runlist = lappend_oid(runlist, item->fnoid);
}
}
/* Construct event trigger data. */
trigdata.type = T_EventTriggerData;
trigdata.event = "ddl_command_end";
trigdata.parsetree = parsetree;
trigdata.tag = tag;
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;
}

View File

@ -4478,7 +4478,6 @@ DropTrigStmt:
*
* QUERIES :
* CREATE EVENT TRIGGER ...
* DROP EVENT TRIGGER ...
* ALTER EVENT TRIGGER ...
*
*****************************************************************************/

View File

@ -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();
}
/*

View File

@ -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;

View File

@ -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;

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201303201
#define CATALOG_VERSION_NO 201303271
#endif

View File

@ -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,

View File

@ -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 */

View File

@ -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);

View File

@ -19,7 +19,8 @@
typedef enum
{
EVT_DDLCommandStart,
EVT_DDLCommandEnd
EVT_DDLCommandEnd,
EVT_SQLDrop
} EventTriggerEvent;
typedef struct

View File

@ -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;

View File

@ -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;