sql_drop Event Trigger
Hi,
I took the liberty to create a new thread for $subject, because the main
comments I've been receiving about Event Triggers at this point is how
difficult it is to try and follow our discussions about them.
In order for everybody interested to be able to easily get the important
bits of information from this patch series and review, I'm now going to
work on a wiki page that I will then update when needed.
The important messages you will need to refer to for more context in
this thread are:
/messages/by-id/m21udesaz3.fsf@2ndQuadrant.fr
/messages/by-id/CA+TgmoZz6MXQ5zX6dopc_xaHVkdwhEhgDFJeAWsRNs+N7e_ueA@mail.gmail.com
So please find attached to this email an implementation of the sql_drop
event trigger, that refrains on exposing any new information to the
users.
COLUMNS=72 git diff --stat master..
doc/src/sgml/event-trigger.sgml | 98 +++++++-
doc/src/sgml/func.sgml | 47 +++-
src/backend/catalog/dependency.c | 7 +
src/backend/commands/event_trigger.c | 233 ++++++++++++++++++-
src/backend/tcop/utility.c | 23 +-
src/backend/utils/cache/evtcache.c | 2 +
src/include/catalog/pg_proc.h | 4 +-
src/include/commands/event_trigger.h | 18 ++
src/include/utils/builtins.h | 3 +
src/include/utils/evtcache.h | 3 +-
src/test/regress/expected/event_trigger.out | 53 +++++
src/test/regress/sql/event_trigger.sql | 37 +++
12 files changed, 519 insertions(+), 9 deletions(-)
The implementation follows Robert ideas in that we accumulate
information about objects we are dropping then provide it to the Event
Trigger User Function. The way to provide it is using a Set Returning
Function called pg_dropped_objects() and that is only available when
running a "sql_drop" event trigger.
This functions returns a set of classid, objid, objsubid (as in
pg_depend), but you have to remember that you can't use them for catalog
lookups as the objects are already dropped: we can't both decide not to
add any concurrency hazards, no new lookups and locking nor extra work
in dependency.c *and* get at the OID of the DROP CASCADEd objects before
the drop happens, as far as I understand it.
I hope to complement the information available in a follow-up patch,
where I intend to provide object name, id, kind, schema name and
operation name in all supported operations.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
So please find attached to this email an implementation of the sql_drop
event trigger, that refrains on exposing any new information to the
users.
Already a v1 of that patch, per comments from Álvaro I reuse the
ObjectAddresses facility rather than building my own List of object
addresses.
Note that with that in place it's now easy to also add support for the
DROP OWNED BY command, but that's left for a future patch as I expect
some amount of discussion to go about it.
Also, I removed the code that was doing de-deduplication of the object
addresses we collect, now trusting performMultipleDeletions() not to
screw us up. There's a use case that needs particular attention here,
though:
DROP TABLE foo, foo;
I'm not sure we want to deduplicate foo in the pg_dropped_objects()
output in that case, so I've not done so in this version of the patch.
Also, Álvaro is concerned that the cost of deduplicating might be higher
than what we want to take here.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
On Wed, Jan 30, 2013 at 12:59 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
So please find attached to this email an implementation of the sql_drop
event trigger, that refrains on exposing any new information to the
users.Already a v1 of that patch, per comments from Álvaro I reuse the
ObjectAddresses facility rather than building my own List of object
addresses.Note that with that in place it's now easy to also add support for the
DROP OWNED BY command, but that's left for a future patch as I expect
some amount of discussion to go about it.Also, I removed the code that was doing de-deduplication of the object
addresses we collect, now trusting performMultipleDeletions() not to
screw us up. There's a use case that needs particular attention here,
though:DROP TABLE foo, foo;
I'm not sure we want to deduplicate foo in the pg_dropped_objects()
output in that case, so I've not done so in this version of the patch.
Also, Álvaro is concerned that the cost of deduplicating might be higher
than what we want to take here.
Taking a first look at this, I think the idea of pg_dropped_objects()
is really pretty clever. I like it. I assure we will end up with
several functions of this type eventually, so it might be good to
adopt some kind of distinguishing naming convention for this type of
function. pg_event_trigger_context_dropped_objects() seems far too
verbose, but that's the idea.
With this approach, there's no real need to introduce a new event
type. We could just make ddl_command_end triggers able to use this,
and we're done. The point of sql_drop was that it would have been
called once *per dropped object*, not once per command. But,
actually, thinking about this, for something like Slony, exposing
pg_dropped_objects() to ddl_command_end triggers should be just as
good, and maybe a whole lot better, than what I was proposing.
Does it work for you to rip out sql_drop and just make
pg_dropped_objects() - perhaps renamed - available in ddl_command_end?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Taking a first look at this, I think the idea of pg_dropped_objects()
is really pretty clever. I like it. I assure we will end up with
several functions of this type eventually, so it might be good to
adopt some kind of distinguishing naming convention for this type of
function. pg_event_trigger_context_dropped_objects() seems far too
verbose, but that's the idea.
Thanks. Agreed that we will have more of them. In the attached version 3
of the patch, it got renamed to pg_event_trigger_dropped_objects().
With this approach, there's no real need to introduce a new event
type. We could just make ddl_command_end triggers able to use this,
and we're done. The point of sql_drop was that it would have been
called once *per dropped object*, not once per command. But,
Well, from the beginning of the sql_drop discussion, it's been clear
that it's meant to allow for users to easily attach their function to
any drop that might appear, whatever the command at origin of that drop.
So in the attached, I've made the sql_drop an event triggers called once
per object, which means that currently you only know an object is
getting DROPed as part of a DROP TABLE command.
actually, thinking about this, for something like Slony, exposing
pg_dropped_objects() to ddl_command_end triggers should be just as
good, and maybe a whole lot better, than what I was proposing.
It also changes the protocol to use for getting at the information
related to the objects. I think we will have to have the out parameters
of the function to grow to include the next information we're going to
make available to TG_* variables in the next patch of the series.
Does it work for you to rip out sql_drop and just make
pg_dropped_objects() - perhaps renamed - available in ddl_command_end?
I did rename pg_dropped_objects() and it's now available in
ddl_command_end, but I kept the new "sql_drop" event, which is now
called once per object dropped, as intended (but without any useful
information yet).
What do you think?
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
On Mon, Feb 4, 2013 at 11:59 AM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Thanks. Agreed that we will have more of them. In the attached version 3
of the patch, it got renamed to pg_event_trigger_dropped_objects().
Works for me.
With this approach, there's no real need to introduce a new event
type. We could just make ddl_command_end triggers able to use this,
and we're done. The point of sql_drop was that it would have been
called once *per dropped object*, not once per command. But,Well, from the beginning of the sql_drop discussion, it's been clear
that it's meant to allow for users to easily attach their function to
any drop that might appear, whatever the command at origin of that drop.
What precludes us from doing that in ddl_command_end? ISTM we can
just extend the ddl_command_start/end triggers to a slightly broader
range of commands and be done with it.
actually, thinking about this, for something like Slony, exposing
pg_dropped_objects() to ddl_command_end triggers should be just as
good, and maybe a whole lot better, than what I was proposing.It also changes the protocol to use for getting at the information
related to the objects. I think we will have to have the out parameters
of the function to grow to include the next information we're going to
make available to TG_* variables in the next patch of the series.Does it work for you to rip out sql_drop and just make
pg_dropped_objects() - perhaps renamed - available in ddl_command_end?I did rename pg_dropped_objects() and it's now available in
ddl_command_end, but I kept the new "sql_drop" event, which is now
called once per object dropped, as intended (but without any useful
information yet).What do you think?
Well, having spent a year or more trying to convince you that we need
sql_drop - mostly because of the complexities of passing an array of
arguments to the trigger function - I now think we don't, because the
pg_event_trigger_dropped_objects() bit solves that problem rather
elegantly. It seems to me with just a little bit of hacking we should
be able to make this work by adding that function and changing nothing
else. I might be wrong, of course.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Well, having spent a year or more trying to convince you that we need
sql_drop - mostly because of the complexities of passing an array of
arguments to the trigger function - I now think we don't, because the
pg_event_trigger_dropped_objects() bit solves that problem rather
elegantly. It seems to me with just a little bit of hacking we should
be able to make this work by adding that function and changing nothing
else. I might be wrong, of course.
It's not exactly like we don't need to add anything else than just the
function to support the feature, but it's not that much either. Please
see attached.
doc/src/sgml/event-trigger.sgml | 15 +-
doc/src/sgml/func.sgml | 48 ++++++-
src/backend/access/transam/xact.c | 8 +-
src/backend/catalog/dependency.c | 38 +----
src/backend/commands/event_trigger.c | 123 ++++++++++++++++-
src/backend/tcop/utility.c | 28 +++-
src/include/catalog/dependency.h | 31 ++++-
src/include/catalog/pg_proc.h | 4 +-
src/include/commands/event_trigger.h | 21 +++
src/include/utils/builtins.h | 3 +
src/test/regress/expected/event_trigger.out | 67 ++++++++-
src/test/regress/sql/event_trigger.sql | 39 +++++-
12 files changed, 374 insertions(+), 51 deletions(-)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
And already a v1.
Álvaro did spot a line I did remove by mistake in the docs, and some
extra whitespace changes that pgindent will change anyway and that as
such I shouldn't force you to read and discard.
It's a 3 lines change set from before.
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Robert Haas <robertmhaas@gmail.com> writes:
Well, having spent a year or more trying to convince you that we need
sql_drop - mostly because of the complexities of passing an array of
arguments to the trigger function - I now think we don't, because the
pg_event_trigger_dropped_objects() bit solves that problem rather
elegantly. It seems to me with just a little bit of hacking we should
be able to make this work by adding that function and changing nothing
else. I might be wrong, of course.It's not exactly like we don't need to add anything else than just the
function to support the feature, but it's not that much either. Please
see attached.doc/src/sgml/event-trigger.sgml | 15 +-
doc/src/sgml/func.sgml | 48 ++++++-
src/backend/access/transam/xact.c | 8 +-
src/backend/catalog/dependency.c | 38 +----
src/backend/commands/event_trigger.c | 123 ++++++++++++++++-
src/backend/tcop/utility.c | 28 +++-
src/include/catalog/dependency.h | 31 ++++-
src/include/catalog/pg_proc.h | 4 +-
src/include/commands/event_trigger.h | 21 +++
src/include/utils/builtins.h | 3 +
src/test/regress/expected/event_trigger.out | 67 ++++++++-
src/test/regress/sql/event_trigger.sql | 39 +++++-
12 files changed, 374 insertions(+), 51 deletions(-)Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
Robert Haas escribió:
On Mon, Feb 4, 2013 at 11:59 AM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:Thanks. Agreed that we will have more of them. In the attached version 3
of the patch, it got renamed to pg_event_trigger_dropped_objects().Works for me.
With this approach, there's no real need to introduce a new event
type. We could just make ddl_command_end triggers able to use this,
and we're done. The point of sql_drop was that it would have been
called once *per dropped object*, not once per command. But,Well, from the beginning of the sql_drop discussion, it's been clear
that it's meant to allow for users to easily attach their function to
any drop that might appear, whatever the command at origin of that drop.What precludes us from doing that in ddl_command_end? ISTM we can
just extend the ddl_command_start/end triggers to a slightly broader
range of commands and be done with it.
I thought there was the idea that the list of objects to drop was to be
acquired before actually doing the deletion; so that the trigger
function could, for instance, get the name of the table being dropped.
I don't see that it works if we only provide
pg_event_trigger_dropped_objects to ddl_command_end events. Am I
missing something?
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
And already a v1.
Álvaro did spot a line I did remove by mistake in the docs, and some
extra whitespace changes that pgindent will change anyway and that as
such I shouldn't force you to read and discard.
The bigger change I mentioned was the stuff in dependency.c -- I wasn't
too happy about exposing the whole ObjectAddresses stuff to the outside
world. The attached version only exposes simple accessors to let an
external user of that to iterate on such arrays. Some more commentary
is probably needed on those new functions. Also, if we're going to
extend things in this way we probably need to get "extra" out alongside
the object array itself.
A larger issue with the patch is handling of subxacts. A quick test
doesn't reveal any obvious misbehavior, but having the list of objects
dropped by a global variable might be problematic. What if, say, the
event trigger function does something funny in an EXCEPTION block and it
fails? Some clever test case is needed here, I think. Also, if we
reset the variable at EOXact, do we also need to do something at
EOSubXact?
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.2.patchtext/x-diff; charset=us-asciiDownload
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 @@
<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</>. Support
+ for additional events may be added in future releases.
</para>
<para>
@@ -46,6 +46,14 @@
</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. Note that happens
+ after the objects have been deleted, so no catalog lookup is possible.
+ </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,
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.
</para>
- <para>
+ <para>
For more information about creating triggers, see
<xref linkend="SQL-CREATETRIGGER">.
</para>
</sect1>
+
+ <sect1 id="functions-trigger">
+ <title>Event Trigger Functions</title>
+
+ <indexterm>
+ <primary>pg_dropped_objects</primary>
+ </indexterm>
+
+ <para>
+ Currently <productname>PostgreSQL</> provides one built in event trigger
+ helper function, <function>pg_event_trigger_dropped_objects</>, which
+ will list all object dropped by a <listeral>DROP</> command. That
+ listing includes multiple targets of the command, as in <command>DROP
+ TABLE a, b, c;</command> and objects dropped because of
+ a <literal>CASCADE</> dependency.
+ </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_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;
+</programlisting>
+ </para>
+
+ <para>
+ For more information about event triggers,
+ see <xref linkend="event-triggers">.
+ </para>
+ </sect1>
+
</chapter>
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;
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
I thought there was the idea that the list of objects to drop was to be
acquired before actually doing the deletion; so that the trigger
function could, for instance, get the name of the table being dropped.
I don't see that it works if we only provide
pg_event_trigger_dropped_objects to ddl_command_end events. Am I
missing something?
Tom and Robert have been rightfully insisting on how delicate it has
been to come up with the right behavior for performMultipleDeletions,
and that's not something we can easily reorganise.
So the only way to get at the information seems to be what Robert
insisted that I do, that is storing the information about the objects
being dropped for later processing.
Of course, later processing means that the objects are already dropped
and that you can't do much. The idea is to provide more than just the
OID of the object, we have yet to decide if adding a catalog cache
lookup within performMultipleDeletions() is ok. If it is, we will extend
the pg_event_trigger_dropped_objects() definition to also return the
object name and its schema name, at a minimum.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
The bigger change I mentioned was the stuff in dependency.c -- I wasn't
too happy about exposing the whole ObjectAddresses stuff to the outside
world. The attached version only exposes simple accessors to let an
external user of that to iterate on such arrays. Some more commentary
is probably needed on those new functions. Also, if we're going to
extend things in this way we probably need to get "extra" out alongside
the object array itself.
Thanks for that.
I added some comments on those new functions, and made it so that we
call get_object_addresses_numelements() only once per loop rather than
at each step in the loop. See attached.
A larger issue with the patch is handling of subxacts. A quick test
doesn't reveal any obvious misbehavior, but having the list of objects
dropped by a global variable might be problematic. What if, say, the
event trigger function does something funny in an EXCEPTION block and it
fails? Some clever test case is needed here, I think. Also, if we
reset the variable at EOXact, do we also need to do something at
EOSubXact?
Now that you mention it, the case I'd be worried about is:
BEGIN;
SAVEPOINT a;
DROP TABLE foo;
ROLLBACK TO SAVEPOINT a;
DROP TABLE bar;
COMMIT;
As we currently have no support for on-commit triggers or the like, the
user function is going to run "as part" of the DROP TABLE foo; command,
and its effects are all going to disappear at the next ROLLBACK TO
SAVEPOINT anyway.
If the event trigger user function fails in an EXCEPTION block, I
suppose that the whole transaction is going to get a ROLLBACK, which
will call AbortTransaction() or CleanupTransaction(), which will reset
the static variable EventTriggerSQLDropInProgress. And the list itself
is gone away with the memory context reset.
I think the only missing detail is to force EventTriggerSQLDropList back
to NULL from within AtEOXact_EventTrigger(), and I've done so in the
attached. As we're only looking at the list when the protecting boolean
is true, I don't think it's offering anything else than clarity, which
makes it worthwile already.
You will find both the patch-on-top-of-your-patch (named .2.b) and the
new whole patch attached (named .3), as it makes things way easier IME.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
dropped_objects.2.b.patchtext/x-patchDownload
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6f95f50..3732fc8 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2152,12 +2152,24 @@ record_object_address_dependencies(const ObjectAddress *depender,
behavior);
}
+/*
+ * get_object_addresses_numelements
+ *
+ * Return the number of object addresses in the given ObjectAddresses, allowing
+ * external modules to loop over the array.
+ */
int
get_object_addresses_numelements(const ObjectAddresses *addresses)
{
return addresses->numrefs;
}
+/*
+ * get_object_addresses_element
+ *
+ * Return the ObjectAddress at position i, allowing to fetch it from an
+ * external module.
+ */
ObjectAddress *
get_object_addresses_element(const ObjectAddresses *addresses, int i)
{
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9ed5715..4e112af 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -862,6 +862,8 @@ AtEOXact_EventTrigger(bool isCommit)
{
/* even on success we want to reset EventTriggerSQLDropInProgress */
EventTriggerSQLDropInProgress = false;
+ /* the list is palloc()ed and has already been taken care of */
+ EventTriggerSQLDropList = NULL;
}
/*
@@ -878,7 +880,7 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
Tuplestorestate *tupstore;
MemoryContext per_query_ctx;
MemoryContext oldcontext;
- int i;
+ int i, n;
/*
* This function is meant to be called from within an event trigger in
@@ -914,7 +916,10 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
MemoryContextSwitchTo(oldcontext);
- for (i = 0; i < get_object_addresses_numelements(EventTriggerSQLDropList); i++)
+ /* only call the get_object_addresses_numelements accessor function once */
+ n = get_object_addresses_numelements(EventTriggerSQLDropList);
+
+ for (i = 0; i < n; i++)
{
ObjectAddress *object;
Datum values[3];
Dimitri Fontaine escribió:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
I thought there was the idea that the list of objects to drop was to be
acquired before actually doing the deletion; so that the trigger
function could, for instance, get the name of the table being dropped.
I don't see that it works if we only provide
pg_event_trigger_dropped_objects to ddl_command_end events. Am I
missing something?Tom and Robert have been rightfully insisting on how delicate it has
been to come up with the right behavior for performMultipleDeletions,
and that's not something we can easily reorganise.
Well, I don't necessarily suggest that. But how about something like
this in performMultipleDeletions:
/*
* Fire event triggers for all objects to be dropped
*/
if (EventTriggerSQLDropInProgress)
{
for (i = 0; i < targetObjects->numrefs; i++)
{
ObjectAddress *thisobj;
thisobj = targetObjects->refs + i;
if (EventTriggerSQLDropInProgress &&
EventTriggerSupportsObjectType(getObjectClass(thisobj)))
{
add_exact_object_address(thisobj, EventTriggerSQLDropList);
}
}
/* invoke sql_drop triggers */
EventTriggerSQLDrop();
/* EventTriggerSQLDropList remains set for ddl_command_end triggers */
}
/* and delete them */
for (i = 0; i < targetObjects->numrefs; i++)
{
ObjectAddress *thisobj;
thisobj = targetObjects->refs + i;
deleteOneObject(thisobj, &depRel, flags);
}
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Well, I don't necessarily suggest that. But how about something like
this in performMultipleDeletions:
[edited snippet of code]
/* invoke sql_drop triggers */
EventTriggerSQLDrop();/* EventTriggerSQLDropList remains set for ddl_command_end triggers */
}/* and delete them */
for (i = 0; i < targetObjects->numrefs; i++)
...
deleteOneObject(thisobj, &depRel, flags);
My understanding of Tom and Robert comments is that it is very unsafe to
run random user code at this point, so that can not be an Event Trigger
call point.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Well, I don't necessarily suggest that. But how about something like
this in performMultipleDeletions:[edited snippet of code]
/* invoke sql_drop triggers */
EventTriggerSQLDrop();/* EventTriggerSQLDropList remains set for ddl_command_end triggers */
}/* and delete them */
for (i = 0; i < targetObjects->numrefs; i++)...
deleteOneObject(thisobj, &depRel, flags);
My understanding of Tom and Robert comments is that it is very unsafe to
run random user code at this point, so that can not be an Event Trigger
call point.
Hmm, quoth
/messages/by-id/23345.1358476518@sss.pgh.pa.us :
I'd really like to get to a point where we can
define things as happening like this:* collect information needed to interpret the DDL command
(lookup and lock objects, etc)
* fire "before" event triggers, if any (and if so, recheck info)
* do the command
* fire "after" event triggers, if any
Note that in the snippet I posted above objects have already been looked
up and locked (first phase above).
One thing worth considering, of course, is the "if so, recheck info"
parenthical remark; for example, what happens if the called function
decides to, say, add a column to every dropped table? Or, worse, what
happens if the event trigger function adds a column to table "foo_audit"
when table "foo" is dropped, and you happen to drop both in the same
command. Also, what if the function decides to drop table "foo_audit"
when table "foo" is dropped? I think these things are all worth adding
to a regress test for this feature, to ensure that behavior is sane, and
also to verify that system catalogs after this remains consistent (for
example we don't leave dangling pg_attribute rows, etc). Maybe the
answer to all this is to run the lookup algorithm all over again and
ensure that the two lists are equal, and throw an error otherwise, i.e.
all scenarios above should be considered unsupported. That seems
safest.
But I don't think "code structure convenience" is the only reason to do
things this way instead of postponing firing the trigger until the end.
I think complete support for drop event triggers really needs to have
the objects to be dropped still in catalogs, so that they can be looked
up; for instance, user code might want to check the names of the
columns and take particular actions if particular names or particular
types are present. That doesn't seem an easy thing to do if all you get
is pg_dropped_objects(), because how do you know which columns belong to
which tables?
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Hmm, quoth
/messages/by-id/23345.1358476518@sss.pgh.pa.us :I'd really like to get to a point where we can
define things as happening like this:* collect information needed to interpret the DDL command
(lookup and lock objects, etc)
* fire "before" event triggers, if any (and if so, recheck info)
* do the command
* fire "after" event triggers, if anyNote that in the snippet I posted above objects have already been looked
up and locked (first phase above).
Ok, I like what you did, but what you did is reinstall the "sql_drop"
event and is not complete, as you mention we have some hard problems to
solve there.
But I don't think "code structure convenience" is the only reason to do
things this way instead of postponing firing the trigger until the end.
I think complete support for drop event triggers really needs to have
the objects to be dropped still in catalogs, so that they can be looked
up; for instance, user code might want to check the names of the
columns and take particular actions if particular names or particular
types are present. That doesn't seem an easy thing to do if all you get
is pg_dropped_objects(), because how do you know which columns belong to
which tables?
Agreed.
In terms of Robert's reviewing, though, I think you're talking about
another patch entirely, that will get worked on in the 9.4 cycle.
And in my terms, doing all that work now is useless anyway because we
are not exposing any object specific information that allow the user to
do any actual catalog lookup anyway, yet.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Feb 5, 2013 at 10:42 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
A larger issue with the patch is handling of subxacts. A quick test
doesn't reveal any obvious misbehavior, but having the list of objects
dropped by a global variable might be problematic. What if, say, the
event trigger function does something funny in an EXCEPTION block and it
fails? Some clever test case is needed here, I think. Also, if we
reset the variable at EOXact, do we also need to do something at
EOSubXact?
This is an awfully good point, although I think the issue has to do
with command boundaries more than subtransactions. Suppose you create
two ddl_command_end event triggers, A and B. A contains a DROP IF
EXISTS command. Someone runs a toplevel DROP command. Now, A is
going to fire first, and that's going to recursively invoke A (which
will do nothing the second time) and then B; on return from B, we'll
finish running the event triggers for the toplevel command, executing
B again. If the list of dropped objects is stored in a global
variable, it seems like there are a number of ways this can go wrong.
I have not tested the actual behavior of the latest patch, but I think
we want to define things so that the
pg_event_trigger_dropped_objects() function returns, specifically, the
list of objects dropped by the command which caused the event trigger
to fire. In other words, in the above example, the first, recursive
invocation of B should see the object removed by A's DROP-IF-EXISTS,
and the second invocation should see the object removed by the
toplevel command.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
variable, it seems like there are a number of ways this can go wrong.
Yeah, I think the current behavior might be surprising.
I have not tested the actual behavior of the latest patch, but I think
we want to define things so that the
pg_event_trigger_dropped_objects() function returns, specifically, the
list of objects dropped by the command which caused the event trigger
to fire. In other words, in the above example, the first, recursive
invocation of B should see the object removed by A's DROP-IF-EXISTS,
and the second invocation should see the object removed by the
toplevel command.
I disagree with that. I don't see why the enclosing event trigger
shouldn't be aware of all the objects dropped by the command that just
ran to completion, *including* the effects of any event trigger fired
recursively or not.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 6, 2013 at 9:36 AM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
variable, it seems like there are a number of ways this can go wrong.
Yeah, I think the current behavior might be surprising.
I have not tested the actual behavior of the latest patch, but I think
we want to define things so that the
pg_event_trigger_dropped_objects() function returns, specifically, the
list of objects dropped by the command which caused the event trigger
to fire. In other words, in the above example, the first, recursive
invocation of B should see the object removed by A's DROP-IF-EXISTS,
and the second invocation should see the object removed by the
toplevel command.I disagree with that. I don't see why the enclosing event trigger
shouldn't be aware of all the objects dropped by the command that just
ran to completion, *including* the effects of any event trigger fired
recursively or not.
Well, that could result in some DROP events being reported more than
once, which I assume would be undesirable for someone hoping to use
this for replication.
(Eventually, we'll have to face the same problem for CREATE events, too.)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Robert Haas <robertmhaas@gmail.com> writes:
I have not tested the actual behavior of the latest patch, but I think
we want to define things so that the
pg_event_trigger_dropped_objects() function returns, specifically, the
list of objects dropped by the command which caused the event trigger
to fire. In other words, in the above example, the first, recursive
invocation of B should see the object removed by A's DROP-IF-EXISTS,
and the second invocation should see the object removed by the
toplevel command.I disagree with that. I don't see why the enclosing event trigger
shouldn't be aware of all the objects dropped by the command that just
ran to completion, *including* the effects of any event trigger fired
recursively or not.
Not sure about that. If the trigger records objects dropped in a table,
aren't they going to show up there twice if you do that?
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
I disagree with that. I don't see why the enclosing event trigger
shouldn't be aware of all the objects dropped by the command that just
ran to completion, *including* the effects of any event trigger fired
recursively or not.Well, that could result in some DROP events being reported more than
once, which I assume would be undesirable for someone hoping to use
this for replication.
Any command might have an event trigger attached doing a DROP, so that
you don't know where to expect it, and it's well possible that in your
example both the event triggers have been installed by different tools.
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
I thought there was the idea that the list of objects to drop was to be
acquired before actually doing the deletion; so that the trigger
function could, for instance, get the name of the table being dropped.
Tom and Robert have been rightfully insisting on how delicate it has
been to come up with the right behavior for performMultipleDeletions,
and that's not something we can easily reorganise.
So the only way to get at the information seems to be what Robert
insisted that I do, that is storing the information about the objects
being dropped for later processing.
I might be forgetting something, but doesn't dependency.c work by first
constructing a list of all the objects it's going to drop, and only then
dropping them? Could we inject a "pre deletion" event trigger call at
the point where the list is completed?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> writes:
I might be forgetting something, but doesn't dependency.c work by first
constructing a list of all the objects it's going to drop, and only then
dropping them? Could we inject a "pre deletion" event trigger call at
the point where the list is completed?
What happens if the event trigger itself deletes objects? From the list?
Then we have to redo all the preparatory steps, and I don't think we
agreed on a way to do it. Plus, as we seem to be chasing minimal set of
features per patch, I would think that getting the list of Objects OIDs
that are already dropped is a well enough defined set of minimal feature
for this very patch, that we will be in a position to extend later given
some time.
I still think we should think about the set of information we're going
to be publishing first, because without publishing some more all we're
doing here is moot anyway. Also, for most cases that I can think of,
it's not a problem for the dropped object to not exist anymore in the
catalogs by the time you get the information, if you get the object's
name and schema and maybe some other properties.
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Tom Lane <tgl@sss.pgh.pa.us> writes:
I might be forgetting something, but doesn't dependency.c work by first
constructing a list of all the objects it's going to drop, and only then
dropping them? Could we inject a "pre deletion" event trigger call at
the point where the list is completed?
Yes, this is what I'm proposing.
What happens if the event trigger itself deletes objects? From the list?
I replied to this above: raise an error. (How to do that is an open
implementation question, but I proposed a simple idea upthread).
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Dimitri Fontaine escribió:
Tom Lane <tgl@sss.pgh.pa.us> writes:
I might be forgetting something, but doesn't dependency.c work by first
constructing a list of all the objects it's going to drop, and only then
dropping them? Could we inject a "pre deletion" event trigger call at
the point where the list is completed?Yes, this is what I'm proposing.
So, I add back the "sql_drop" event and implement it in dependency.c,
and keep the list for later processing from "ddl_command_end", right?
Robert, you specifically opposed to "sql_drop" and I just removed it
from the patch. What do you think now? Also, should that be a follow-up
patch to the current one for your reviewing purposes?
What happens if the event trigger itself deletes objects? From the list?
I replied to this above: raise an error. (How to do that is an open
implementation question, but I proposed a simple idea upthread).
Well, can the list of objects that get dropped for CASCADE change in
between releases? If it ever does, that means that your event trigger
that used to work before is not broken after upgrade. Is that ok?
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Tom Lane <tgl@sss.pgh.pa.us> writes:
I might be forgetting something, but doesn't dependency.c work by first
constructing a list of all the objects it's going to drop, and only then
dropping them? Could we inject a "pre deletion" event trigger call at
the point where the list is completed?
What happens if the event trigger itself deletes objects? From the list?
Throw an error. Per previous discussion, the trigger does not get to do
anything that would affect the results of "parse analysis" of the
command, and that list is exactly those results.
Plus, as we seem to be chasing minimal set of
features per patch, I would think that getting the list of Objects OIDs
that are already dropped is a well enough defined set of minimal feature
for this very patch, that we will be in a position to extend later given
some time.
Well, a list of object OIDs is of exactly zero use once the command
has been carried out. So I don't think that that represents a useful
or even very testable feature on its own, if there's no provision to
fire user code while the OIDs are still in the catalogs.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> writes:
Well, a list of object OIDs is of exactly zero use once the command
has been carried out. So I don't think that that represents a useful
or even very testable feature on its own, if there's no provision to
fire user code while the OIDs are still in the catalogs.
For the same reason I want to talk about which information we publish. I
can see why having access to the catalogs is a more general answer here
though.
Now, I've just been asked to remove "sql_drop" and I don't want to be
adding it again before I know that whoever will commit the patch agrees
with an explicit return of that feature from the dead. Please…
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 6, 2013 at 9:44 AM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
I disagree with that. I don't see why the enclosing event trigger
shouldn't be aware of all the objects dropped by the command that just
ran to completion, *including* the effects of any event trigger fired
recursively or not.Well, that could result in some DROP events being reported more than
once, which I assume would be undesirable for someone hoping to use
this for replication.Any command might have an event trigger attached doing a DROP, so that
you don't know where to expect it, and it's well possible that in your
example both the event triggers have been installed by different tools.
It certainly is; in fact, it's likely. So let's say that B is a
replication trigger. Don't you want it to hear about each drop
exactly once? If not, how will you avoid errors when you go to replay
the events you've captured on another machine?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 6, 2013 at 11:04 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
I thought there was the idea that the list of objects to drop was to be
acquired before actually doing the deletion; so that the trigger
function could, for instance, get the name of the table being dropped.Tom and Robert have been rightfully insisting on how delicate it has
been to come up with the right behavior for performMultipleDeletions,
and that's not something we can easily reorganise.So the only way to get at the information seems to be what Robert
insisted that I do, that is storing the information about the objects
being dropped for later processing.I might be forgetting something, but doesn't dependency.c work by first
constructing a list of all the objects it's going to drop, and only then
dropping them? Could we inject a "pre deletion" event trigger call at
the point where the list is completed?
In theory, sure, but in practice, there are multiple ways that can
break things. The possibility that the event trigger that runs at
that point might drop one of the objects involved has already been
mentioned, but there are others. For example, it might *create* a new
object that depends on one of the objects to be dropped, potentially
leading to an inconsistent pg_depend. It could also ALTER the object,
or something that the object depends on. For example, suppose we're
running an event trigger in the middle of a DROP TABLE command, and
the event trigger creates a _SELECT rule on the table, transforming it
into a view. Or, suppose it opens (and leaves open) a cursor scanning
that table (normally, CheckTableNotInUse prevents that, but here we've
already done that). Alternatively, suppose we're dropping a view
which the event trigger redefines to depend on a completely different
set of objects.
I don't deny that code can be written to handle all of those cases
correctly. But it's going to be a major refactoring, and the idea
that we should be starting to design it in February seems ludicrous to
me. It'll be May by the time we get this one patch right. Of 2014.
And there are more than 70 other patches that need attention in this
CommitFest. I have thus far mostly avoided getting sucked into the
annual argument about which things should be evicted from this
CommitFest because, frankly, I have better things to do with my time
than argue about what the feature freeze date is with people who
should know better. But the problem isn't going to go away because we
don't talk about it. Has anyone else noticed that "final triage" is
scheduled to end tomorrow, and we haven't done any triage of any kind
and, at least with respect to this feature, are effectively still
receiving new patch submissions?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 6, 2013 at 12:05 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Feb 6, 2013 at 9:44 AM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
I disagree with that. I don't see why the enclosing event trigger
shouldn't be aware of all the objects dropped by the command that just
ran to completion, *including* the effects of any event trigger fired
recursively or not.Well, that could result in some DROP events being reported more than
once, which I assume would be undesirable for someone hoping to use
this for replication.Any command might have an event trigger attached doing a DROP, so that
you don't know where to expect it, and it's well possible that in your
example both the event triggers have been installed by different tools.It certainly is; in fact, it's likely. So let's say that B is a
replication trigger. Don't you want it to hear about each drop
exactly once? If not, how will you avoid errors when you go to replay
the events you've captured on another machine?
In this case, the "hygenic" change that we're thinking of making to Slony,
at least initially, is for the trigger to check to see if the table is
replicated,
and raise an exception if it is.
That forces the Gentle User to submit the Slony SET DROP TABLE
command <http://slony.info/documentation/2.1/stmtsetdroptable.html>.
Now, if we stipulate that imposition, then, for this kind of event, it
becomes unnecessary for event triggers to get *overly* concerned about
capturing more about dropping tables. After all, SET DROP TABLE
already knows how to replicate its action, so what happens, in that
case is:
- User submits SET DROP TABLE
- SET DROP TABLE drops the triggers for the table, cleans out
Slony configuration surrounding the table, forwards request
to other nodes
- User submits DROP TABLE
- Slony is no longer involved with that table; there's nothing special
anymore about replicating this; perhaps we capture and forward
it via event trigger.
I'm not sure if that makes thinking about this easier, I hope so :-).
--
When confronted by a difficult problem, solve it by reducing it to the
question, "How would the Lone Ranger handle this?"
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 6, 2013 at 11:30 AM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Dimitri Fontaine escribió:
Tom Lane <tgl@sss.pgh.pa.us> writes:
I might be forgetting something, but doesn't dependency.c work by first
constructing a list of all the objects it's going to drop, and only then
dropping them? Could we inject a "pre deletion" event trigger call at
the point where the list is completed?Yes, this is what I'm proposing.
So, I add back the "sql_drop" event and implement it in dependency.c,
and keep the list for later processing from "ddl_command_end", right?Robert, you specifically opposed to "sql_drop" and I just removed it
from the patch. What do you think now? Also, should that be a follow-up
patch to the current one for your reviewing purposes?
Well, if it has a different firing point than ddl_command_end, then
there could well be some point to having it after all. But I'm far
from convinced that the proposed firing point can be made safe without
a major refactoring. I think this is the sort of things where "design
before code" ought to be the cardinal rule.
I replied to this above: raise an error. (How to do that is an open
implementation question, but I proposed a simple idea upthread).Well, can the list of objects that get dropped for CASCADE change in
between releases? If it ever does, that means that your event trigger
that used to work before is not broken after upgrade. Is that ok?
That particular problem does not bother me, personally. But the
possibility of ending up with corrupt system catalog contents does.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Feb 6, 2013 at 12:28 PM, Christopher Browne <cbbrowne@gmail.com> wrote:
On Wed, Feb 6, 2013 at 12:05 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Feb 6, 2013 at 9:44 AM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
I disagree with that. I don't see why the enclosing event trigger
shouldn't be aware of all the objects dropped by the command that just
ran to completion, *including* the effects of any event trigger fired
recursively or not.Well, that could result in some DROP events being reported more than
once, which I assume would be undesirable for someone hoping to use
this for replication.Any command might have an event trigger attached doing a DROP, so that
you don't know where to expect it, and it's well possible that in your
example both the event triggers have been installed by different tools.It certainly is; in fact, it's likely. So let's say that B is a
replication trigger. Don't you want it to hear about each drop
exactly once? If not, how will you avoid errors when you go to replay
the events you've captured on another machine?In this case, the "hygenic" change that we're thinking of making to Slony,
at least initially, is for the trigger to check to see if the table is
replicated,
and raise an exception if it is.That forces the Gentle User to submit the Slony SET DROP TABLE
command <http://slony.info/documentation/2.1/stmtsetdroptable.html>.Now, if we stipulate that imposition, then, for this kind of event, it
becomes unnecessary for event triggers to get *overly* concerned about
capturing more about dropping tables. After all, SET DROP TABLE
already knows how to replicate its action, so what happens, in that
case is:- User submits SET DROP TABLE
- SET DROP TABLE drops the triggers for the table, cleans out
Slony configuration surrounding the table, forwards request
to other nodes- User submits DROP TABLE
- Slony is no longer involved with that table; there's nothing special
anymore about replicating this; perhaps we capture and forward
it via event trigger.I'm not sure if that makes thinking about this easier, I hope so :-).
Well, that means you wouldn't necessarily mind getting duplicate DROP
events for the same object, but they don't benefit you in any way,
either. And, I'm not sure we can conclude from this that duplicate
events will be OK in every use case. For example, for a logging hook,
it's pessimal, because now you're logging duplicate messages for the
same drops and there's no easy way to fix it so you don't.
Also, it means you're really going to need the schema name and table
name for this to be useful; Tom was just complaining upthread that
without that information the features isn't useful, so perhaps we
should conclude from your email that he is correct.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Robert, you specifically opposed to "sql_drop" and I just removed it
from the patch. What do you think now? Also, should that be a follow-up
patch to the current one for your reviewing purposes?Well, if it has a different firing point than ddl_command_end, then
there could well be some point to having it after all. But I'm far
from convinced that the proposed firing point can be made safe without
a major refactoring. I think this is the sort of things where "design
before code" ought to be the cardinal rule.
Ok se we are in agreement here. I think we should see about getting the
dropped_objects.3.patch.gz in (pending review), then continue with that
patch series:
- publishing some object specific information in TG_*
- deciding on a design for generated commands, maybe commit it if it
happens to look a lot like what I already have done those past years
- adding a function pg_get_normalized_command_string(parsetree) that
takes as input internal (Node *) and provide a text as output
Note: all that code exists already, in a more or less complete form, and
has been around for between 1 and 2 years. I'm *not* trying to push *new*
things in the current commit fest, only to make it so that the current
patch series deliver a minimum set of features that is usable by itself.
Have a look at my slides from FOSDEM where I tried to share my vision
here. I don't have a use case for Event Triggers without them publishing
object or command specific information, as is currently the case in our
tree:
http://tapoueh.org/images/confs/Fosdem2013_Event_Triggers.pdf
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Feb 11, 2013 at 9:53 AM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Robert, you specifically opposed to "sql_drop" and I just removed it
from the patch. What do you think now? Also, should that be a follow-up
patch to the current one for your reviewing purposes?Well, if it has a different firing point than ddl_command_end, then
there could well be some point to having it after all. But I'm far
from convinced that the proposed firing point can be made safe without
a major refactoring. I think this is the sort of things where "design
before code" ought to be the cardinal rule.Ok se we are in agreement here. I think we should see about getting the
dropped_objects.3.patch.gz in (pending review), ...
Wait, I'm confused. I had a note to myself to come back and review
this, but now that I look at it, I didn't think that patch was pending
review. Alvaro, Tom, and I all made comments that seems to impinge
upon that design rather heavily. No?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Wait, I'm confused. I had a note to myself to come back and review
this, but now that I look at it, I didn't think that patch was pending
review. Alvaro, Tom, and I all made comments that seems to impinge
upon that design rather heavily. No?
The current design follows exactly your comments and design requests.
Tom and Álvaro comments are the ones you did answer to saying that it's
not 9.3 material, but next release at best, subject to heavy refactoring.
What did I miss?
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Feb 14, 2013 at 3:39 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Wait, I'm confused. I had a note to myself to come back and review
this, but now that I look at it, I didn't think that patch was pending
review. Alvaro, Tom, and I all made comments that seems to impinge
upon that design rather heavily. No?The current design follows exactly your comments and design requests.
Tom and Álvaro comments are the ones you did answer to saying that it's
not 9.3 material, but next release at best, subject to heavy refactoring.What did I miss?
Well, there's this, upon which we surely have not achieved consensus:
/messages/by-id/CA+TgmobQ6NGsxGuiHWqcygF0Q+7Y9zHNERePo3S1vsWKKNw2TQ@mail.gmail.com
And then Tom also wrote this, which is kind of a good point, too:
Well, a list of object OIDs is of exactly zero use once the command
has been carried out. So I don't think that that represents a useful
or even very testable feature on its own, if there's no provision to
fire user code while the OIDs are still in the catalogs.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Well, there's this, upon which we surely have not achieved consensus:
/messages/by-id/CA+TgmobQ6NGsxGuiHWqcygF0Q+7Y9zHNERePo3S1vsWKKNw2TQ@mail.gmail.com
Sub-Transaction Handling. I fail to come up with a regression test
showing any problem here, and Álvaro is now working on the patch so he
might be finding both a failure test and a fix.
And then Tom also wrote this, which is kind of a good point, too:
Well, a list of object OIDs is of exactly zero use once the command
has been carried out. So I don't think that that represents a useful
or even very testable feature on its own, if there's no provision to
fire user code while the OIDs are still in the catalogs.
That's the reason why I've been proposing that we first add some
information to the event triggers, then see about the DROP support.
You might want to realize that the current event triggers implementation
is not even publishing the object ID now, only the command tag and the
name of the event.
We still don't have any way to make any use of the whole thing, apart
from maybe "block all commands" (but all is in fact a subset of known
DDL). I don't think we still are able to solve *any* use case with
what's currently commited. At all.
Given that, it's really hard for me to get excited on that very point.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Feb 17, 2013 at 4:12 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Well, a list of object OIDs is of exactly zero use once the command
has been carried out. So I don't think that that represents a useful
or even very testable feature on its own, if there's no provision to
fire user code while the OIDs are still in the catalogs.That's the reason why I've been proposing that we first add some
information to the event triggers, then see about the DROP support.
I think the question of the interface to the data and the data to
expose are pretty tightly related. You can't exactly get one right
and the other one wrong and say, OK, we'll fix it later.
You might want to realize that the current event triggers implementation
is not even publishing the object ID now, only the command tag and the
name of the event.
I know that. I also know that after I committed this patch in July,
many months went by before we had any further discussion of next
steps. I'll admit that some of this stuff was on the table for the
November CommitFest, but I also won't accept complete blame for the
fact that we're not further along than we are.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Here's an implementation of what we discussed: the event is now DDL_DROP
and is called inside performDeletion and performMultipleDeletions; it
receives the list of objects via pg_dropped_objects (though it now has a
more verbose name), and the objects are still catalogued when the
trigger is called. This event does *not* receive the parsetree (as
opposed to ddl_command_start/end); also, there's no tag filtering
support, so if you install a function in that event, it will run every
time a DROP of any kind occurs.
I have added some protections so that these do not fire on undesirable
events (such as dropping an event trigger); also event triggers and
other object types are filtered out of pg_dropped_objects, in case
something like DROP OWNED happens. (So for instance you could get an
empty pg_dropped_objects if you did DROP OWNED and the mentioned user
only owns an event trigger). Maybe this needs to be reconsidered.
There's also code to re-obtain the list of objects to drop after the
event trigger functions have run; the second list is compared to the
first one, and if they differ, an error is raised.
It's rather annoying that performMultipleDeletions and performDeletion
contain almost exactly the same code. I think maybe it's time to morph
the latter into a thin layer on top of the former.
(small remaiing issue: docs need updated).
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
ddl-drop-3.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 71241c8..5263238 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -27,9 +27,10 @@
<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>
@@ -46,6 +47,16 @@
</para>
<para>
+ The <literal>sql_drop</> event occurs just before
+ the <literal>ddl_command_end</> event trigger for a <literal>DROP</>
+ operation, once per object. Note that happens after the objects have
+ been deleted, so no catalog lookup is possible. To list all objects that
+ have been deleted, use the Set Returning
+ Function <literal>pg_event_trigger_dropped_objects()</> from
+ your <literal>ddl_command_end</> event trigger code.
+ </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,
@@ -99,6 +110,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 +118,481 @@
<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>-</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>-</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 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>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 92a79d3..19e1921 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15688,9 +15688,54 @@ 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.
</para>
- <para>
+ <para>
For more information about creating triggers, see
<xref linkend="SQL-CREATETRIGGER">.
</para>
</sect1>
+
+ <sect1 id="functions-trigger">
+ <title>Event Trigger Functions</title>
+
+ <indexterm>
+ <primary>pg_dropped_objects</primary>
+ </indexterm>
+
+ <para>
+ Currently <productname>PostgreSQL</> provides one built in event trigger
+ helper function, <function>pg_event_trigger_dropped_objects</>, which
+ will list all object dropped by a <listeral>DROP</> command. That
+ listing includes multiple targets of the command, as in <command>DROP
+ TABLE a, b, c;</command> and objects dropped because of
+ a <literal>CASCADE</> dependency.
+ </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_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 'sql_drop event trigger: % % % %',
+ obj.classId::regclass,
+ obj.classId, obj.objid, obj.objsubid;
+ END LOOP;
+END
+$$ language plpgsql;
+</programlisting>
+ </para>
+
+ <para>
+ For more information about event triggers,
+ see <xref linkend="event-triggers">.
+ </para>
+ </sect1>
+
</chapter>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d203725..d670f5e 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -261,6 +261,44 @@ performDeletion(const ObjectAddress *object,
object);
/*
+ * When event triggers have been configured to run on drop events,
+ * we run the triggers, then recompute the list of objects to delete; we
+ * can then verify that the list of objects to delete after the triggers
+ * are run is the same as the list of objects we determined at the
+ * beggining. If they differ, raise an error, aborting the transaction.
+ * This is to ensure that the triggers don't try to modify what we're
+ * dropping, because that could leave us with inconsistent catalogs.
+ *
+ * See performMultipleDeletion if you change this.
+ */
+ if (AreThereDDLDropEventTriggers())
+ {
+ ObjectAddresses *recheckObjects;
+
+ /* Fire DDL_DROP event trigger here, using targetObjects as list */
+ EventTriggerDDLDrop(targetObjects);
+
+ CommandCounterIncrement();
+
+ recheckObjects = new_object_addresses();
+
+ findDependentObjects(object,
+ DEPFLAG_ORIGINAL,
+ NULL, /* empty stack */
+ recheckObjects,
+ NULL,
+ &depRel);
+
+ /* verify that the lists contain the same elements. */
+ if (!object_addresses_identical(targetObjects, recheckObjects))
+ ereport(ERROR,
+ (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
+ errmsg("event trigger function on ddl_drop event must not change list of objects to drop")));
+
+ free_object_addresses(recheckObjects);
+ }
+
+ /*
* Delete all the objects in the proper order.
*/
for (i = 0; i < targetObjects->numrefs; i++)
@@ -343,6 +381,43 @@ performMultipleDeletions(const ObjectAddresses *objects,
(objects->numrefs == 1 ? objects->refs : NULL));
/*
+ * See comment in performDeletion about ddl_drop event triggers
+ */
+ if (AreThereDDLDropEventTriggers())
+ {
+ ObjectAddresses *recheckObjects;
+
+ /* Fire DDL_DROP event trigger here, using targetObjects as list */
+ EventTriggerDDLDrop(targetObjects);
+
+ CommandCounterIncrement();
+
+ recheckObjects = new_object_addresses();
+
+ for (i = 0; i < objects->numrefs; i++)
+ {
+ const ObjectAddress *thisobj = objects->refs + i;
+
+ AcquireDeletionLock(thisobj, flags);
+
+ findDependentObjects(thisobj,
+ DEPFLAG_ORIGINAL,
+ NULL, /* empty stack */
+ recheckObjects,
+ objects,
+ &depRel);
+ }
+
+ /* verify that the lists contain the same elements. */
+ if (!object_addresses_identical(targetObjects, recheckObjects))
+ ereport(ERROR,
+ (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
+ errmsg("event trigger function on ddl_drop event must not change list of objects to drop")));
+
+ free_object_addresses(recheckObjects);
+ }
+
+ /*
* Delete all the objects in the proper order.
*/
for (i = 0; i < targetObjects->numrefs; i++)
@@ -366,6 +441,9 @@ 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.
+ *
+ * XXX this currently does not fire DDL_DROP triggers. If more callers are
+ * added, this might need to be reconsidered.
*/
void
deleteWhatDependsOn(const ObjectAddress *object,
@@ -2175,6 +2253,76 @@ record_object_address_dependencies(const ObjectAddress *depender,
behavior);
}
+static ObjectAddresses *
+object_addresses_copy(const ObjectAddresses *src)
+{
+ ObjectAddresses *dst;
+
+ dst = palloc(sizeof(ObjectAddresses));
+
+ dst->numrefs = src->numrefs;
+ dst->maxrefs = src->numrefs;
+ dst->refs = (ObjectAddress *)
+ palloc(dst->maxrefs * sizeof(ObjectAddress));
+ dst->extras = NULL;
+
+ memcpy(dst->refs, src->refs, sizeof(ObjectAddress) * src->numrefs);
+
+ return dst;
+}
+
+/*
+ * Compare two ObjectAddresses arrays and determine whether they have exactly
+ * the same elements.
+ *
+ * We don't scribble on the const argument, but the second one might be sorted
+ * after this function is called.
+ */
+bool
+object_addresses_identical(const ObjectAddresses *a,
+ ObjectAddresses *b)
+{
+ ObjectAddresses *a_copy;
+ int i;
+
+ /* If they don't have the same number of elements, return quickly */
+ if (a->numrefs != b->numrefs)
+ return false;
+
+ /* prepare a copy we can scribble on */
+ a_copy = object_addresses_copy(a);
+
+ qsort((void *) b->refs, b->numrefs, sizeof(ObjectAddress),
+ object_address_comparator);
+ qsort((void *) a_copy->refs, a_copy->numrefs, sizeof(ObjectAddress),
+ object_address_comparator);
+
+ for (i = 0; i < a->numrefs; i++)
+ {
+ if (object_address_comparator((void *) (a_copy->refs + i),
+ (void *) (b->refs + i)) != 0)
+ {
+ free_object_addresses(a_copy);
+ return false;
+ }
+ }
+
+ free_object_addresses(a_copy);
+ return true;
+}
+
+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..5697a1b 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -18,13 +18,18 @@
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_members.h"
+#include "catalog/pg_database.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_tablespace.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,6 +44,11 @@
#include "utils/syscache.h"
#include "tcop/utility.h"
+/* Globally visible state variables */
+bool EventTriggerDDLDropInProgress = false;
+ObjectAddresses *EventTriggerDDLDropList = NULL;
+bool isIgnoredDDLDropEvent = false;
+
typedef struct
{
const char *obtypename;
@@ -126,7 +136,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, "ddl_drop") != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized event name \"%s\"",
@@ -150,8 +161,17 @@ 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"))
+ && tags != NULL)
+ {
validate_ddl_tags("tag", tags);
+ }
+ if ((strcmp(stmt->eventname, "ddl_drop") == 0) &&
+ tags != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("DDL_DROP command triggers do not support tag filtering")));
/*
* Give user a nice error message if an event trigger of the same name
@@ -742,6 +762,105 @@ EventTriggerDDLCommandEnd(Node *parsetree)
}
/*
+ * Return whether there are any DDL_DROP triggers installed.
+ *
+ * This is useful because there is a cost to running with them enabled.
+ */
+bool
+AreThereDDLDropEventTriggers(void)
+{
+ return list_length(EventCacheLookup(EVT_DDLDrop)) > 0;
+}
+
+/*
+ * Fire ddl_drop event triggers for the given dropped objects.
+ */
+void
+EventTriggerDDLDrop(ObjectAddresses *objects)
+{
+ List *cachelist;
+ List *runlist = NIL;
+ ListCell *lc;
+ EventTriggerData trigdata;
+ ObjectAddresses *save_EventTriggerDDLDropList;
+ bool save_EventTriggerDDLDropInProgress;
+
+ /*
+ * See EventTriggerDDLCommandStart for a discussion about why event
+ * triggers are disabled in single user mode.
+ */
+ if (!IsUnderPostmaster)
+ return;
+
+ /* these do not fire when running commands for unsupported object types */
+ if (isIgnoredDDLDropEvent)
+ return;
+
+ /*
+ * Other command triggers have a consistency check for the tag here.
+ * We don't do that because we don't have the parsetree, for one thing,
+ * and we don't support filtering by tags for DDL_DROP events anyway.
+ */
+
+ cachelist = EventCacheLookup(EVT_DDLDrop);
+ if (cachelist == NIL)
+ return;
+
+ /*
+ * No tag filtering here; just copy the function OIDs into the new list.
+ */
+ foreach(lc, cachelist)
+ {
+ EventTriggerCacheItem *item = lfirst(lc);
+
+ runlist = lappend_oid(runlist, item->fnoid);
+ }
+
+ /* Construct event trigger data. */
+ trigdata.type = T_EventTriggerData;
+ trigdata.event = "ddl_drop";
+ trigdata.parsetree = NULL; /* no parse tree here */
+ trigdata.tag = NULL; /* no command tag, either */
+
+ /*
+ * We purposefully do not call CommandCounterIncrement here.
+ */
+
+ /*
+ * Now run the triggers. Store the list of dropped objects where the
+ * triggers can see it, but save the existing list, so that we can restore
+ * it afterwards, in case we've been called reentrantly.
+ *
+ * Use a PG_TRY block here to ensure we reset the global variables properly
+ * even in case of an error.
+ */
+ save_EventTriggerDDLDropInProgress = EventTriggerDDLDropInProgress;
+ save_EventTriggerDDLDropList = EventTriggerDDLDropList;
+
+ PG_TRY();
+ {
+ EventTriggerDDLDropInProgress = true;
+ EventTriggerDDLDropList = objects;
+
+ EventTriggerInvoke(runlist, &trigdata);
+ }
+ PG_CATCH();
+ {
+ EventTriggerDDLDropInProgress = save_EventTriggerDDLDropInProgress;
+ EventTriggerDDLDropList = save_EventTriggerDDLDropList;
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ EventTriggerDDLDropInProgress = save_EventTriggerDDLDropInProgress;
+ EventTriggerDDLDropList = save_EventTriggerDDLDropList;
+
+ /* Cleanup. */
+ list_free(runlist);
+}
+
+/*
* Invoke each event trigger in a list of event triggers.
*/
static void
@@ -806,6 +925,8 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
/*
* Do event triggers support this object type?
+ *
+ * Note: see also the exclusion list in pg_event_trigger_dropped_objects.
*/
bool
EventTriggerSupportsObjectType(ObjectType obtype)
@@ -825,3 +946,97 @@ EventTriggerSupportsObjectType(ObjectType obtype)
}
return true;
}
+
+/*
+ * 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 any Event Trigger in
+ * order to get the list of objects dropped, if any.
+ */
+ if (!EventTriggerDDLDropInProgress)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s can only be called from an 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);
+
+ for (i = 0;
+ i < get_object_addresses_numelements(EventTriggerDDLDropList);
+ i++)
+ {
+ ObjectAddress *object;
+ Datum values[3];
+ bool nulls[3];
+
+ object = get_object_addresses_element(EventTriggerDDLDropList, i);
+
+ /*
+ * Skip objects which are not supposed to fire event triggers. This
+ * list must match EventTriggerSupportsObjectType.
+ */
+ if (object->classId == EventTriggerRelationId ||
+ object->classId == DatabaseRelationId ||
+ object->classId == TableSpaceRelationId ||
+ object->classId == AuthIdRelationId ||
+ object->classId == AuthMemRelationId)
+ continue;
+
+ /* Emit result row */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ /* classid */
+ values[0] = ObjectIdGetDatum(object->classId);
+
+ /* objid */
+ values[1] = ObjectIdGetDatum(object->objectId);
+
+ /* objsubid */
+ values[2] = Int32GetDatum(object->objectSubId);
+
+ 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..66bce04 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -697,29 +697,46 @@ standard_ProcessUtility(Node *parsetree,
case T_DropStmt:
{
DropStmt *stmt = (DropStmt *) parsetree;
+ bool save_isIgnoredDDLDropEvent;
+
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
EventTriggerDDLCommandStart(parsetree);
- switch (stmt->removeType)
+ save_isIgnoredDDLDropEvent = isIgnoredDDLDropEvent;
+ isIgnoredDDLDropEvent =
+ !EventTriggerSupportsObjectType(stmt->removeType);
+
+ PG_TRY();
{
- case OBJECT_INDEX:
- if (stmt->concurrent)
- PreventTransactionChain(isTopLevel,
- "DROP INDEX CONCURRENTLY");
- /* fall through */
+ 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:
+ case OBJECT_FOREIGN_TABLE:
+ RemoveRelations((DropStmt *) parsetree);
+ break;
+ default:
+ RemoveObjects((DropStmt *) parsetree);
+ break;
+ }
+ }
+ PG_CATCH();
+ {
+ isIgnoredDDLDropEvent = save_isIgnoredDDLDropEvent;
- case OBJECT_TABLE:
- case OBJECT_SEQUENCE:
- case OBJECT_VIEW:
- case OBJECT_FOREIGN_TABLE:
- RemoveRelations((DropStmt *) parsetree);
- break;
- default:
- RemoveObjects((DropStmt *) parsetree);
- break;
+ PG_RE_THROW();
}
+ PG_END_TRY();
+ isIgnoredDDLDropEvent = save_isIgnoredDDLDropEvent;
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index 34c6128..0fec1b1 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -68,7 +68,7 @@ EventCacheLookup(EventTriggerEvent event)
if (EventTriggerCacheState != ETCS_VALID)
BuildEventTriggerCache();
entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
- return entry != NULL ? entry->triggerlist : NULL;
+ return entry != NULL ? entry->triggerlist : NIL;
}
/*
@@ -169,6 +169,8 @@ BuildEventTriggerCache(void)
event = EVT_DDLCommandStart;
else if (strcmp(evtevent, "ddl_command_end") == 0)
event = EVT_DDLCommandEnd;
+ else if (strcmp(evtevent, "ddl_drop") == 0)
+ event = EVT_DDLDrop;
else
continue;
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8e0837f..c1fbe05 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -191,6 +191,14 @@ extern void record_object_address_dependencies(const ObjectAddress *depender,
ObjectAddresses *referenced,
DependencyType behavior);
+extern int get_object_addresses_numelements(const ObjectAddresses *addresses);
+
+extern bool object_addresses_identical(const ObjectAddresses *a,
+ ObjectAddresses *b);
+
+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 d9f50d2..372e50a 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,23}" "{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..200d649 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -13,9 +13,29 @@
#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"
+/*
+ * EventTriggerDDLDropList is set to the objects to delete when a DDLDrop
+ * event happens (basically any command that drops objects). Note that all
+ * object types are here, not only those supported as
+ * EventTriggerSupportsObjectType(). FIXME we need to filter this somehow.
+ *
+ * EventTriggerDDLDropInProgress will be set in a DDLDrop event. That's used
+ * to allow access to the DDLDropList.
+ *
+ * isIgnoredDDLDropEvent is set when running DROP commands that affect
+ * unsupported object types. ddl_drop triggers must not be called when it
+ * is set.
+ */
+extern bool EventTriggerDDLDropInProgress;
+extern ObjectAddresses *EventTriggerDDLDropList;
+extern bool isIgnoredDDLDropEvent;
+
+
typedef struct EventTriggerData
{
NodeTag type;
@@ -42,5 +62,7 @@ extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
+extern bool AreThereDDLDropEventTriggers(void);
+extern void EventTriggerDDLDrop(ObjectAddresses *objects);
#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/include/utils/evtcache.h b/src/include/utils/evtcache.h
index c230995..d8ce1ec 100644
--- a/src/include/utils/evtcache.h
+++ b/src/include/utils/evtcache.h
@@ -19,7 +19,8 @@
typedef enum
{
EVT_DDLCommandStart,
- EVT_DDLCommandEnd
+ EVT_DDLCommandEnd,
+ EVT_DDLDrop
} EventTriggerEvent;
typedef struct
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 70e67d9..772c830 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -803,9 +803,18 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
var->freeval = true;
var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]);
- var->value = CStringGetTextDatum(trigdata->tag);
- var->isnull = false;
- var->freeval = true;
+ if (trigdata->tag)
+ {
+ var->value = CStringGetTextDatum(trigdata->tag);
+ var->isnull = false;
+ var->freeval = true;
+ }
+ else
+ {
+ var->value = (Datum) 0;
+ var->isnull = true;
+ var->freeval = false;
+ }
/*
* Let the instrumentation plugin peek at this function
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index bf020de..38e2e42 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -99,5 +99,205 @@ 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;
+-- now test the ddl_drop event trigger
+CREATE FUNCTION test_evtrig_dropped_objs() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+DECLARE
+obj record;
+col record;
+schema oid;
+catalog text;
+colname text;
+BEGIN
+ RAISE NOTICE 'function test_evtrig_dropped_objs starting: event %', tg_event;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ORDER BY 1, 2
+ LOOP
+ -- skip reporting for objects in pg_toast; they contain OID references
+ -- in their names
+ IF obj.classid::regclass = 'pg_catalog.pg_class'::regclass THEN
+ catalog = 'pg_class';
+ colname = 'relnamespace';
+ END IF;
+ IF obj.classid::regclass = 'pg_catalog.pg_index'::regclass THEN
+ catalog = 'pg_index';
+ colname = 'indnamespace';
+ END IF;
+ IF obj.classid::regclass = 'pg_catalog.pg_type'::regclass THEN
+ catalog = 'pg_type';
+ colname = 'typnamespace';
+ END IF;
+ IF catalog IS NOT NULL THEN
+ EXECUTE format('SELECT %s FROM %s WHERE oid = %s',
+ colname, catalog, obj.objid)
+ INTO schema;
+ IF schema = 99 OR schema = pg_my_temp_schema() THEN CONTINUE; END IF;
+ END IF;
+
+ RAISE NOTICE 'object: %', pg_describe_object(obj.classid, obj.objid, obj.objsubid);
+
+ END LOOP;
+ RAISE NOTICE 'function test_evtrig_dropped_objs done';
+ END
+$$;
+-- OK
+create event trigger regress_event_trigger_drop_objects on ddl_drop
+ execute procedure test_evtrig_dropped_objs();
+-- a simple enough test: cascade
+create table evt_a(id serial primary key, value text);
+create view evt_a_v as select id from evt_a;
+drop table evt_a cascade;
+NOTICE: drop cascades to view evt_a_v
+NOTICE: function test_evtrig_dropped_objs starting: event ddl_drop
+NOTICE: object: type evt_a_id_seq
+NOTICE: object: type evt_a[]
+NOTICE: object: type evt_a
+NOTICE: object: type evt_a_v[]
+NOTICE: object: type evt_a_v
+NOTICE: object: sequence evt_a_id_seq
+NOTICE: object: table evt_a
+NOTICE: object: index evt_a_pkey
+NOTICE: object: view evt_a_v
+NOTICE: object: default for table evt_a column id
+NOTICE: object: constraint evt_a_pkey on table evt_a
+NOTICE: object: rule _RETURN on view evt_a_v
+NOTICE: function test_evtrig_dropped_objs done
+-- another test with multiple targets
+create table evt_a(id serial);
+create table evt_b(id serial);
+drop table evt_a, evt_b;
+NOTICE: function test_evtrig_dropped_objs starting: event ddl_drop
+NOTICE: object: type evt_a_id_seq
+NOTICE: object: type evt_a[]
+NOTICE: object: type evt_a
+NOTICE: object: type evt_b_id_seq
+NOTICE: object: type evt_b[]
+NOTICE: object: type evt_b
+NOTICE: object: sequence evt_a_id_seq
+NOTICE: object: table evt_a
+NOTICE: object: sequence evt_b_id_seq
+NOTICE: object: table evt_b
+NOTICE: object: default for table evt_a column id
+NOTICE: object: default for table evt_b column id
+NOTICE: function test_evtrig_dropped_objs done
+-- sneaky: modify the table being dropped in the event trigger.
+create function test_evtrig_dropped_objs_sneaky() returns event_trigger
+language plpgsql as $$
+DECLARE
+obj record;
+BEGIN
+ RAISE NOTICE 'test_evtrig_dropped_objs_sneaky: %', tg_event;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ORDER BY 1, 2
+ LOOP
+ IF obj.objid::regclass = 'evt_b'::regclass THEN
+ ALTER TABLE evt_a ADD COLUMN bar text;
+ END IF;
+ END LOOP;
+ END
+$$;
+create event trigger regress_event_trigger_drop_objects_2 on ddl_drop
+ execute procedure test_evtrig_dropped_objs_sneaky();
+create table evt_a (a serial primary key, b int, c text);
+create table evt_b (a serial primary key, b int, c text);
+-- drop column also runs a trigger.
+alter table evt_a drop column b;
+NOTICE: function test_evtrig_dropped_objs starting: event ddl_drop
+NOTICE: object: table evt_a column b
+NOTICE: function test_evtrig_dropped_objs done
+NOTICE: test_evtrig_dropped_objs_sneaky: ddl_drop
+-- this should work.
+drop table evt_a, evt_b;
+NOTICE: function test_evtrig_dropped_objs starting: event ddl_drop
+NOTICE: object: type evt_a_a_seq
+NOTICE: object: type evt_a[]
+NOTICE: object: type evt_a
+NOTICE: object: type evt_b_a_seq
+NOTICE: object: type evt_b[]
+NOTICE: object: type evt_b
+NOTICE: object: sequence evt_a_a_seq
+NOTICE: object: table evt_a
+NOTICE: object: index evt_a_pkey
+NOTICE: object: sequence evt_b_a_seq
+NOTICE: object: table evt_b
+NOTICE: object: index evt_b_pkey
+NOTICE: object: default for table evt_a column a
+NOTICE: object: default for table evt_b column a
+NOTICE: object: constraint evt_a_pkey on table evt_a
+NOTICE: object: constraint evt_b_pkey on table evt_b
+NOTICE: function test_evtrig_dropped_objs done
+NOTICE: test_evtrig_dropped_objs_sneaky: ddl_drop
+drop event trigger regress_event_trigger_drop_objects_2;
+-- sneakier: add a default value to the new column, too
+create function test_evtrig_dropped_objs_sneakier() returns event_trigger
+language plpgsql as $$
+DECLARE
+obj record;
+col record;
+BEGIN
+ RAISE NOTICE 'test_evtrig_dropped_objs_sneakier: %', tg_event;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ORDER BY 1, 2
+ LOOP
+ IF obj.objid::regclass = 'evt_a'::regclass THEN
+ ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya';
+ END IF;
+ END LOOP;
+ END
+$$;
+create event trigger regress_event_trigger_drop_objects_3 on ddl_drop
+ execute procedure test_evtrig_dropped_objs_sneakier();
+create table evt_a (a serial primary key, b int, c text);
+-- this should fail: refuse to operate when catalogs were changed by the
+-- trigger ... but the actual error we get is that we cannot run the ALTER
+-- TABLE because the table "is being accessed by other queries in this
+-- session", which is correct.
+alter table evt_a drop column b;
+NOTICE: function test_evtrig_dropped_objs starting: event ddl_drop
+NOTICE: object: table evt_a column b
+NOTICE: function test_evtrig_dropped_objs done
+NOTICE: test_evtrig_dropped_objs_sneakier: ddl_drop
+ERROR: cannot ALTER TABLE "evt_a" because it is being used by active queries in this session
+CONTEXT: SQL statement "ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya'"
+PL/pgSQL function test_evtrig_dropped_objs_sneakier() line 11 at SQL statement
+-- this should really fail because of our refusal to operate when catalogs were
+-- changed by the trigger
+drop table evt_a;
+NOTICE: function test_evtrig_dropped_objs starting: event ddl_drop
+NOTICE: object: type evt_a_a_seq
+NOTICE: object: type evt_a[]
+NOTICE: object: type evt_a
+NOTICE: object: sequence evt_a_a_seq
+NOTICE: object: table evt_a
+NOTICE: object: index evt_a_pkey
+NOTICE: object: default for table evt_a column a
+NOTICE: object: constraint evt_a_pkey on table evt_a
+NOTICE: function test_evtrig_dropped_objs done
+NOTICE: test_evtrig_dropped_objs_sneakier: ddl_drop
+NOTICE: function test_evtrig_dropped_objs starting: event ddl_drop
+CONTEXT: SQL statement "ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya'"
+PL/pgSQL function test_evtrig_dropped_objs_sneakier() line 11 at SQL statement
+NOTICE: object: type pg_temp_206879[]
+CONTEXT: SQL statement "ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya'"
+PL/pgSQL function test_evtrig_dropped_objs_sneakier() line 11 at SQL statement
+NOTICE: object: type pg_temp_206879
+CONTEXT: SQL statement "ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya'"
+PL/pgSQL function test_evtrig_dropped_objs_sneakier() line 11 at SQL statement
+NOTICE: object: table pg_temp_206879
+CONTEXT: SQL statement "ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya'"
+PL/pgSQL function test_evtrig_dropped_objs_sneakier() line 11 at SQL statement
+NOTICE: function test_evtrig_dropped_objs done
+CONTEXT: SQL statement "ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya'"
+PL/pgSQL function test_evtrig_dropped_objs_sneakier() line 11 at SQL statement
+NOTICE: test_evtrig_dropped_objs_sneakier: ddl_drop
+CONTEXT: SQL statement "ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya'"
+PL/pgSQL function test_evtrig_dropped_objs_sneakier() line 11 at SQL statement
+ERROR: event trigger function on ddl_drop event must not change list of objects to drop
+-- cleanup the ddl_drop tests
+drop event trigger if exists regress_event_trigger_drop_objects;
+drop event trigger if exists regress_event_trigger_drop_objects_2;
+NOTICE: event trigger "regress_event_trigger_drop_objects_2" does not exist, skipping
+drop event trigger if exists regress_event_trigger_drop_objects_3;
drop function test_event_trigger();
+drop function test_evtrig_dropped_objs();
drop role regression_bob;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index a07dcd7..01ee307 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -102,5 +102,125 @@ 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;
+
+-- now test the ddl_drop event trigger
+CREATE FUNCTION test_evtrig_dropped_objs() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+DECLARE
+obj record;
+col record;
+schema oid;
+catalog text;
+colname text;
+BEGIN
+ RAISE NOTICE 'function test_evtrig_dropped_objs starting: event %', tg_event;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ORDER BY 1, 2
+ LOOP
+ -- skip reporting for objects in pg_toast; they contain OID references
+ -- in their names
+ IF obj.classid::regclass = 'pg_catalog.pg_class'::regclass THEN
+ catalog = 'pg_class';
+ colname = 'relnamespace';
+ END IF;
+ IF obj.classid::regclass = 'pg_catalog.pg_index'::regclass THEN
+ catalog = 'pg_index';
+ colname = 'indnamespace';
+ END IF;
+ IF obj.classid::regclass = 'pg_catalog.pg_type'::regclass THEN
+ catalog = 'pg_type';
+ colname = 'typnamespace';
+ END IF;
+ IF catalog IS NOT NULL THEN
+ EXECUTE format('SELECT %s FROM %s WHERE oid = %s',
+ colname, catalog, obj.objid)
+ INTO schema;
+ IF schema = 99 OR schema = pg_my_temp_schema() THEN CONTINUE; END IF;
+ END IF;
+
+ RAISE NOTICE 'object: %', pg_describe_object(obj.classid, obj.objid, obj.objsubid);
+
+ END LOOP;
+ RAISE NOTICE 'function test_evtrig_dropped_objs done';
+ END
+$$;
+
+-- OK
+create event trigger regress_event_trigger_drop_objects on ddl_drop
+ execute procedure test_evtrig_dropped_objs();
+
+-- a simple enough test: cascade
+create table evt_a(id serial primary key, value text);
+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;
+
+-- sneaky: modify the table being dropped in the event trigger.
+create function test_evtrig_dropped_objs_sneaky() returns event_trigger
+language plpgsql as $$
+DECLARE
+obj record;
+BEGIN
+ RAISE NOTICE 'test_evtrig_dropped_objs_sneaky: %', tg_event;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ORDER BY 1, 2
+ LOOP
+ IF obj.objid::regclass = 'evt_b'::regclass THEN
+ ALTER TABLE evt_a ADD COLUMN bar text;
+ END IF;
+ END LOOP;
+ END
+$$;
+create event trigger regress_event_trigger_drop_objects_2 on ddl_drop
+ execute procedure test_evtrig_dropped_objs_sneaky();
+
+create table evt_a (a serial primary key, b int, c text);
+create table evt_b (a serial primary key, b int, c text);
+-- drop column also runs a trigger.
+alter table evt_a drop column b;
+-- this should work.
+drop table evt_a, evt_b;
+drop event trigger regress_event_trigger_drop_objects_2;
+
+-- sneakier: add a default value to the new column, too
+create function test_evtrig_dropped_objs_sneakier() returns event_trigger
+language plpgsql as $$
+DECLARE
+obj record;
+col record;
+BEGIN
+ RAISE NOTICE 'test_evtrig_dropped_objs_sneakier: %', tg_event;
+
+ FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ORDER BY 1, 2
+ LOOP
+ IF obj.objid::regclass = 'evt_a'::regclass THEN
+ ALTER TABLE evt_a ADD COLUMN bar text DEFAULT 'broke ya';
+ END IF;
+ END LOOP;
+ END
+$$;
+create event trigger regress_event_trigger_drop_objects_3 on ddl_drop
+ execute procedure test_evtrig_dropped_objs_sneakier();
+
+create table evt_a (a serial primary key, b int, c text);
+-- this should fail: refuse to operate when catalogs were changed by the
+-- trigger ... but the actual error we get is that we cannot run the ALTER
+-- TABLE because the table "is being accessed by other queries in this
+-- session", which is correct.
+alter table evt_a drop column b;
+-- this should really fail because of our refusal to operate when catalogs were
+-- changed by the trigger
+drop table evt_a;
+
+-- cleanup the ddl_drop tests
+drop event trigger if exists regress_event_trigger_drop_objects;
+drop event trigger if exists regress_event_trigger_drop_objects_2;
+drop event trigger if exists regress_event_trigger_drop_objects_3;
drop function test_event_trigger();
+drop function test_evtrig_dropped_objs();
+
drop role regression_bob;
On Wed, Feb 20, 2013 at 10:48 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
I have added some protections so that these do not fire on undesirable
events (such as dropping an event trigger); also event triggers and
other object types are filtered out of pg_dropped_objects, in case
something like DROP OWNED happens. (So for instance you could get an
empty pg_dropped_objects if you did DROP OWNED and the mentioned user
only owns an event trigger). Maybe this needs to be reconsidered.
This is tricky. The whole point of skipping the
ddl_command_start/ddl_command_end triggers when the command is one
that operates on event triggers is that we don't want a user to
install an event trigger that prevents said user from dropping that
event trigger afterwards. We're currently safe from that, but with
the behavior you describe, we won't be: an event trigger that
unconditionally thows an error will block all drops, even of event
triggers.
I am inclined to suggest that we go back to an earlier suggestion I
made, which Dimitri didn't like, but which I still think might be the
best way forward: add a SUSET GUC that disables event triggers. If we
do that, then our answer to that problem, for the current event
triggers and all we might add in the future, can be: well, if that
happens, set the disable_event_triggers GUC and retry the drop. OTOH,
if we DON'T do that, then this problem is going to come back up every
time we add a new firing point, and it may be really tough to make
sure we're secure against this in all cases.
If you don't like that suggestion I'm open to other options, but I
think we should try hard to preserve the invariant that a superuser
can always drop an event trigger without interference from the event
trigger system itself.
There's also code to re-obtain the list of objects to drop after the
event trigger functions have run; the second list is compared to the
first one, and if they differ, an error is raised.
This is definitely an improvement. I am not 100% clear on whether
this is sufficient, but it sure seems a lot better than punting.
I think there might be a possible failure mode if somebody creates a
new object that depends on one of the objects to be dropped while the
event trigger is running. Since system catalogs are normally read
with SnapshotNow, I think it might be possible to create a situation
where the first and second searches return different results even
though the trigger hasn't done anything naughty. I believe that I
added some locking in 9.2 that will prevent this in the case where you
create a table that depends on a concurrently-dropped schema, but
there are other cases that have this problem, such as a function being
added to a concurrently-dropped schema.
Now, IMHO, that isn't necessarily a show-stopper for this patch,
because that same phenomenon can cause catalog corruption as things
stand. So in the scenario where an event trigger causes a transaction
to fail because of this issue, it will actually be *preventing*
catalog corruption. However, if this goes in, it might bump up the
priority of fixing some of those locking issues, since it may make
them more visible.
It's rather annoying that performMultipleDeletions and performDeletion
contain almost exactly the same code. I think maybe it's time to morph
the latter into a thin layer on top of the former.
Yeah, or at least factor out the code you need for this into its own function.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Feb 21, 2013 at 12:25 PM, Robert Haas <robertmhaas@gmail.com> wrote:
There's also code to re-obtain the list of objects to drop after the
event trigger functions have run; the second list is compared to the
first one, and if they differ, an error is raised.This is definitely an improvement. I am not 100% clear on whether
this is sufficient, but it sure seems a lot better than punting.
What if the object that gets whacked around is one of the named
objects rather than some dependency thereof? Suppose for example that
the event trigger drops the same object that the user tried to drop.
We need to error out cleanly in that case, not blindly proceed with
the drop.
(In the worst case, somebody could create an unrelated object with the
same OID and we could latch onto and drop that one. Seems remote
unless the user has a way to fiddle with the OID counter, but if it
happened it would be bad.)
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Wed, Feb 20, 2013 at 10:48 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:I have added some protections so that these do not fire on undesirable
events (such as dropping an event trigger); also event triggers and
other object types are filtered out of pg_dropped_objects, in case
something like DROP OWNED happens. (So for instance you could get an
empty pg_dropped_objects if you did DROP OWNED and the mentioned user
only owns an event trigger). Maybe this needs to be reconsidered.This is tricky. The whole point of skipping the
ddl_command_start/ddl_command_end triggers when the command is one
that operates on event triggers is that we don't want a user to
install an event trigger that prevents said user from dropping that
event trigger afterwards. We're currently safe from that, but with
the behavior you describe, we won't be: an event trigger that
unconditionally thows an error will block all drops, even of event
triggers.
You're misunderstanding. If you do DROP EVENT TRIGGER, the DDL_DROP
event won't fire at all. So no matter how messed up your system is, you
can always fix it by simply dropping the event trigger.
What I was saying is that if you have some command other than DROP EVENT
TRIGGER, which happens to drop an event trigger, said event trigger will
not be present in the pg_dropped_objects results.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Thu, Feb 21, 2013 at 12:25 PM, Robert Haas <robertmhaas@gmail.com> wrote:
There's also code to re-obtain the list of objects to drop after the
event trigger functions have run; the second list is compared to the
first one, and if they differ, an error is raised.This is definitely an improvement. I am not 100% clear on whether
this is sufficient, but it sure seems a lot better than punting.What if the object that gets whacked around is one of the named
objects rather than some dependency thereof? Suppose for example that
the event trigger drops the same object that the user tried to drop.
We need to error out cleanly in that case, not blindly proceed with
the drop.
An error is raised, which I think is sane. I think this peculiar
situation warrants its own few lines in the new regression test.
One funny thing I noticed is that if I add a column in a table being
dropped, the targetObjects list does not change after the trigger has
run. The reason for this is that the table's attributes are not present
in the targetObjects list; instead they are dropped manually by calling
DeleteAttributeTuples(). I saw that you can end up with lingering
pg_attribute entries that way.
(In the worst case, somebody could create an unrelated object with the
same OID and we could latch onto and drop that one. Seems remote
unless the user has a way to fiddle with the OID counter, but if it
happened it would be bad.)
Hmm.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Feb 21, 2013 at 12:47 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
You're misunderstanding. If you do DROP EVENT TRIGGER, the DDL_DROP
event won't fire at all. So no matter how messed up your system is, you
can always fix it by simply dropping the event trigger.What I was saying is that if you have some command other than DROP EVENT
TRIGGER, which happens to drop an event trigger, said event trigger will
not be present in the pg_dropped_objects results.
Hmm. But, that means that if some other object manages to depend on
an event trigger, and you drop the event trigger with CASCADE taking
the other object with it, then some other event trigger being used
for, say, replication might fail to see the drop. Right now that's
not possible but it seems potentially fragile. Not that I have a
great idea.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Feb 21, 2013 at 12:52 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
What if the object that gets whacked around is one of the named
objects rather than some dependency thereof? Suppose for example that
the event trigger drops the same object that the user tried to drop.
We need to error out cleanly in that case, not blindly proceed with
the drop.An error is raised, which I think is sane. I think this peculiar
situation warrants its own few lines in the new regression test.
Definitely.
One funny thing I noticed is that if I add a column in a table being
dropped, the targetObjects list does not change after the trigger has
run. The reason for this is that the table's attributes are not present
in the targetObjects list; instead they are dropped manually by calling
DeleteAttributeTuples(). I saw that you can end up with lingering
pg_attribute entries that way.
I venture to guess that this is exactly the sort of thing that made
Tom argue upthread that we shouldn't be putting a firing point in the
middle of the drop operation. Any slip-ups here will result in
corrupt catalogs, and it's not exactly future-proof either.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Thu, Feb 21, 2013 at 12:52 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
One funny thing I noticed is that if I add a column in a table being
dropped, the targetObjects list does not change after the trigger has
run. The reason for this is that the table's attributes are not present
in the targetObjects list; instead they are dropped manually by calling
DeleteAttributeTuples(). I saw that you can end up with lingering
pg_attribute entries that way.I venture to guess that this is exactly the sort of thing that made
Tom argue upthread that we shouldn't be putting a firing point in the
middle of the drop operation. Any slip-ups here will result in
corrupt catalogs, and it's not exactly future-proof either.
Well, is this kind of thing enough to punt the whole patch, or can we
chalk it off as the user's problem? We can word the docs to state that
we try to detect actions that are known to cause corruption but that
some might slip past, and that it's the user's responsibility to ensure
that the event trigger functions behave sanely.
Another idea I just had was to scan the catalogs after the event trigger
and see if the Xmin for each tuple IsCurrentTransaction(), and if so
throw an error. I think that would be more accurate than the current
implementation, but rather a lot of trouble. I'm open to implementing
that if we consider that idea watertight enough that this patch is
considered commitable.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Robert Haas escribi�:
I venture to guess that this is exactly the sort of thing that made
Tom argue upthread that we shouldn't be putting a firing point in the
middle of the drop operation. Any slip-ups here will result in
corrupt catalogs, and it's not exactly future-proof either.
Well, is this kind of thing enough to punt the whole patch, or can we
chalk it off as the user's problem?
I don't really think that we want any events in the first release that
are defined so that a bogus trigger can cause catalog corruption.
That will, for example, guarantee that we can *never* open up the
feature to non-superusers. I think we'd be painting ourselves into a
corner that we could not get out of.
Maybe down the road we'll conclude that there's no other way and we're
willing to put up with an unsafe feature, but I don't want to take that
step under time pressure to ship something in 9.3.
Another idea I just had was to scan the catalogs after the event trigger
and see if the Xmin for each tuple IsCurrentTransaction(), and if so
throw an error.
You mean examine every row in every catalog? Doesn't sound like a great
plan.
I thought the proposal was to recompute the set of drop target objects,
and complain if that had changed.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Feb 28, 2013 at 2:14 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Robert Haas escribió:
I venture to guess that this is exactly the sort of thing that made
Tom argue upthread that we shouldn't be putting a firing point in the
middle of the drop operation. Any slip-ups here will result in
corrupt catalogs, and it's not exactly future-proof either.Well, is this kind of thing enough to punt the whole patch, or can we
chalk it off as the user's problem?I don't really think that we want any events in the first release that
are defined so that a bogus trigger can cause catalog corruption.
That will, for example, guarantee that we can *never* open up the
feature to non-superusers. I think we'd be painting ourselves into a
corner that we could not get out of.
I agree, although my feelings on this point are actually somewhat
stronger than that.
Maybe down the road we'll conclude that there's no other way and we're
willing to put up with an unsafe feature, but I don't want to take that
step under time pressure to ship something in 9.3.
I'm opposed to doing it under any circumstances, ever. Right now,
there's a very limited number of things which can result in foreign
key constraints between system catalogs being violated. So, when it
happens, from a support point of view, we don't have many things to
investigate. If we add "badly written event triggers" to the list,
it's going to open up a can of worms so large it'll be gravitationally
rounded.
I thought the proposal was to recompute the set of drop target objects,
and complain if that had changed.
Alvaro did that, but it isn't sufficient to prevent catalog corruption.
It seems to me that a better way to do this might be to look up the
names of all the objects being dropped, as we get rid of them, and
pass that information off to the ddl_command_end trigger via something
like the pg_dropped_objects() function Dimitri proposed. Then we
don't have to deal with the massive grottiness of running a command
right in the middle of the deletions, which I continue to maintain
will have many as-yet-unforeseen failure modes.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane escribió:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Robert Haas escribi�:
I venture to guess that this is exactly the sort of thing that made
Tom argue upthread that we shouldn't be putting a firing point in the
middle of the drop operation. Any slip-ups here will result in
corrupt catalogs, and it's not exactly future-proof either.Well, is this kind of thing enough to punt the whole patch, or can we
chalk it off as the user's problem?I don't really think that we want any events in the first release that
are defined so that a bogus trigger can cause catalog corruption.
That will, for example, guarantee that we can *never* open up the
feature to non-superusers. I think we'd be painting ourselves into a
corner that we could not get out of.
Roger.
Another idea I just had was to scan the catalogs after the event trigger
and see if the Xmin for each tuple IsCurrentTransaction(), and if so
throw an error.You mean examine every row in every catalog? Doesn't sound like a great
plan.
No, I mean the rows that are part of the set of objects to be deleted.
I thought the proposal was to recompute the set of drop target objects,
and complain if that had changed.
Yeah, that's what the patch I submitted upthread does. The problem is
that pg_attribute rows are not in that set; they are deleted manually by
heap_drop_with_catalog by calling DeleteAttributeTuples. So if you add
a column to a table in the trigger function, the sets are identical
and that logic doesn't detect that things are amiss.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Thu, Feb 28, 2013 at 2:14 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Maybe down the road we'll conclude that there's no other way and we're
willing to put up with an unsafe feature, but I don't want to take that
step under time pressure to ship something in 9.3.I'm opposed to doing it under any circumstances, ever. Right now,
there's a very limited number of things which can result in foreign
key constraints between system catalogs being violated. So, when it
happens, from a support point of view, we don't have many things to
investigate. If we add "badly written event triggers" to the list,
it's going to open up a can of worms so large it'll be gravitationally
rounded.
Well, that's a good point, but I think that was a foreseeable
consequence of event triggers themselves. I agree that we need to get
this as robust and bulletproof as possible.
I thought the proposal was to recompute the set of drop target objects,
and complain if that had changed.Alvaro did that, but it isn't sufficient to prevent catalog corruption.
It seems to me that a better way to do this might be to look up the
names of all the objects being dropped, as we get rid of them, and
pass that information off to the ddl_command_end trigger via something
like the pg_dropped_objects() function Dimitri proposed. Then we
don't have to deal with the massive grottiness of running a command
right in the middle of the deletions, which I continue to maintain
will have many as-yet-unforeseen failure modes.
There are two points you're making here: one is about what data do we
provide about objects being dropped (you argue OIDs and names are
sufficient); and you're also saying calling these triggers at
ddl_command_end is the right timing. I disagree about the first one,
because I don't think object names are sufficient; Tom argued upthread
about being able to look up the objects from the catalogs in the event
trigger function. This in turn means that ddl_command_end is not good
enough; these functions need to be called earlier than that. Which is
what the new DDL_DROP event provides: a point in the execution sequence
that's after we've looked up the objects, but before they are gone from
the catalogs.
Can we get agreement on those points? Otherwise ISTM we're going to
continue to argue in circles.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Robert Haas escribi�:
It seems to me that a better way to do this might be to look up the
names of all the objects being dropped, as we get rid of them, and
pass that information off to the ddl_command_end trigger via something
like the pg_dropped_objects() function Dimitri proposed. Then we
don't have to deal with the massive grottiness of running a command
right in the middle of the deletions, which I continue to maintain
will have many as-yet-unforeseen failure modes.
I share Robert's fear of this.
There are two points you're making here: one is about what data do we
provide about objects being dropped (you argue OIDs and names are
sufficient); and you're also saying calling these triggers at
ddl_command_end is the right timing. I disagree about the first one,
because I don't think object names are sufficient; Tom argued upthread
about being able to look up the objects from the catalogs in the event
trigger function. This in turn means that ddl_command_end is not good
enough; these functions need to be called earlier than that. Which is
what the new DDL_DROP event provides: a point in the execution sequence
that's after we've looked up the objects, but before they are gone from
the catalogs.
Can we get agreement on those points? Otherwise ISTM we're going to
continue to argue in circles.
I think it's fairly obvious that
(1) dealing with a DROP only after it's happened is pretty limiting;
(2) allowing user-defined code to run mid-command is dangerous.
What's at issue is the tradeoff we make between these inescapable
facts, and I'm not sure if we can get consensus on that.
On the whole, though, I'm thinking that the approach Robert suggests
is the way to go, because it doesn't foreclose our doing something
else later. Whereas if we ship 9.3 with a mid-DROP event, and we then
find out that it's even more dangerous than we currently realize,
we're going to be between a rock and a hard place.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> writes:
I think it's fairly obvious that
(1) dealing with a DROP only after it's happened is pretty limiting;
(2) allowing user-defined code to run mid-command is dangerous.
What's at issue is the tradeoff we make between these inescapable
facts, and I'm not sure if we can get consensus on that.On the whole, though, I'm thinking that the approach Robert suggests
is the way to go, because it doesn't foreclose our doing something
else later. Whereas if we ship 9.3 with a mid-DROP event, and we then
find out that it's even more dangerous than we currently realize,
we're going to be between a rock and a hard place.
The good news is that the patch to do that has already been sent on this
list, and got reviewed in details by Álvaro who did offer incremental
changes. Version 3 of that patch is to be found in:
/messages/by-id/m2fw19n1hr.fsf@2ndQuadrant.fr
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Tom Lane <tgl@sss.pgh.pa.us> writes:
I think it's fairly obvious that
(1) dealing with a DROP only after it's happened is pretty limiting;
(2) allowing user-defined code to run mid-command is dangerous.
What's at issue is the tradeoff we make between these inescapable
facts, and I'm not sure if we can get consensus on that.
Mmm.
On the whole, though, I'm thinking that the approach Robert suggests
is the way to go, because it doesn't foreclose our doing something
else later. Whereas if we ship 9.3 with a mid-DROP event, and we then
find out that it's even more dangerous than we currently realize,
we're going to be between a rock and a hard place.
Makes sense.
The good news is that the patch to do that has already been sent on this
list, and got reviewed in details by Álvaro who did offer incremental
changes. Version 3 of that patch is to be found in:
As I recall there were a few more fixes I wanted to do on top of that
patch. I will see about that. Thanks for the pointer.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Feb 28, 2013 at 3:20 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think it's fairly obvious that
(1) dealing with a DROP only after it's happened is pretty limiting;
(2) allowing user-defined code to run mid-command is dangerous.
What's at issue is the tradeoff we make between these inescapable
facts, and I'm not sure if we can get consensus on that.
I'll note that Slony could do something useful with an ON EVENT trigger
even if there's NO data provided as to what tables got dropped.
We could get a "prevent dropping by accident" test that amounts to:
if exists (select 1 from _slony_schema.sl_table where
not exists (select 1 from pg_catalog.pg_class where oid =
tab_reloid)) then
raise exception 'You attempted to drop a replicated table. Shame on you!";
end if;
That could be extended to precede the exception by raising a warning
for each such table that was found. That's a nice "save the admin from
accidentally breaking replication" check.
If we're agonizing over "what more do we need to ensure it's useful?", and
it's looking pretty open-ended, well, for the above test, I don't need *any*
attributes to be passed to me by the event trigger in order to do something
that's useful enough. I wouldn't feel horribly badly if things stopped short
of having ON DROP do anything extra.
If I *really* wanted to do some sophisticated tracking of things, the apropos
route might be to set up both a BEFORE and an AFTER trigger, have the
BEFORE part capture relevant data in memory (with some question of
"how much relevant data") and then, in the AFTER trigger, refer back to
the captured data in order to do something. That again doesn't require
adding much of anything to the trigger attributes.
--
When confronted by a difficult problem, solve it by reducing it to the
question, "How would the Lone Ranger handle this?"
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
The good news is that the patch to do that has already been sent on this
list, and got reviewed in details by Álvaro who did offer incremental
changes. Version 3 of that patch is to be found in:
Here's a v4 of that patch. I added support for DROP OWNED, and added
object name and schema name available to the pg_dropped_objects
function.
Since we're now in agreement that this is the way to go, I think this
only needs a few more tweaks to get to a committable state, as well as
some useful tests and doc changes. (v3 has docs which I didn't include
here but are probably useful almost verbatim.)
Do we want some more stuff provided by pg_dropped_objects? We now have
classId, objectId, objectSubId, object name, schema name. One further
thing I think we need is the object's type, i.e. a simple untranslated
string "table", "view", "operator" and so on. AFAICT this requires a
nearly-duplicate of getObjectDescription.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.4.patchtext/x-diff; charset=us-asciiDownload
[1mdiff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c[m
[1mindex d203725..2233158 100644[m
[1m--- a/src/backend/catalog/dependency.c[m
[1m+++ b/src/backend/catalog/dependency.c[m
[36m@@ -267,6 +267,12 @@[m [mperformDeletion(const ObjectAddress *object,[m
{[m
ObjectAddress *thisobj = targetObjects->refs + i;[m
[m
[32m+[m [32mif ((!(flags & PERFORM_DELETION_INTERNAL)) &&[m
[32m+[m [32mEventTriggerSupportsObjectType(getObjectClass(thisobj)))[m
[32m+[m [32m{[m
[32m+[m [32mevtrig_sqldrop_add_object(thisobj);[m
[32m+[m [32m}[m
[32m+[m
deleteOneObject(thisobj, &depRel, flags);[m
}[m
[m
[36m@@ -349,6 +355,12 @@[m [mperformMultipleDeletions(const ObjectAddresses *objects,[m
{[m
ObjectAddress *thisobj = targetObjects->refs + i;[m
[m
[32m+[m [32mif ((!(flags & PERFORM_DELETION_INTERNAL)) &&[m
[32m+[m [32mEventTriggerSupportsObjectType(getObjectClass(thisobj)))[m
[32m+[m [32m{[m
[32m+[m [32mevtrig_sqldrop_add_object(thisobj);[m
[32m+[m [32m}[m
[32m+[m
deleteOneObject(thisobj, &depRel, flags);[m
}[m
[m
[36m@@ -366,6 +378,10 @@[m [mperformMultipleDeletions(const ObjectAddresses *objects,[m
* This is currently used only to clean out the contents of a schema[m
* (namespace): the passed object is a namespace. We normally want this[m
* to be done silently, so there's an option to suppress NOTICE messages.[m
[32m+[m[32m *[m
[32m+[m[32m * Note we don't fire object drop event triggers here; it would be wrong to do[m
[32m+[m[32m * so for the current only use of this function, but if more callers are added[m
[32m+[m[32m * this might need to be reconsidered.[m
*/[m
void[m
deleteWhatDependsOn(const ObjectAddress *object,[m
[1mdiff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c[m
[1mindex 269d19c..347c405 100644[m
[1m--- a/src/backend/commands/alter.c[m
[1m+++ b/src/backend/commands/alter.c[m
[36m@@ -746,58 +746,6 @@[m [mExecAlterOwnerStmt(AlterOwnerStmt *stmt)[m
}[m
[m
/*[m
[31m- * Return a copy of the tuple for the object with the given object OID, from[m
[31m- * the given catalog (which must have been opened by the caller and suitably[m
[31m- * locked). NULL is returned if the OID is not found.[m
[31m- *[m
[31m- * We try a syscache first, if available.[m
[31m- *[m
[31m- * XXX this function seems general in possible usage. Given sufficient callers[m
[31m- * elsewhere, we should consider moving it to a more appropriate place.[m
[31m- */[m
[31m-static HeapTuple[m
[31m-get_catalog_object_by_oid(Relation catalog, Oid objectId)[m
[31m-{[m
[31m- HeapTuple tuple;[m
[31m- Oid classId = RelationGetRelid(catalog);[m
[31m- int oidCacheId = get_object_catcache_oid(classId);[m
[31m-[m
[31m- if (oidCacheId > 0)[m
[31m- {[m
[31m- tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));[m
[31m- if (!HeapTupleIsValid(tuple)) /* should not happen */[m
[31m- return NULL;[m
[31m- }[m
[31m- else[m
[31m- {[m
[31m- Oid oidIndexId = get_object_oid_index(classId);[m
[31m- SysScanDesc scan;[m
[31m- ScanKeyData skey;[m
[31m-[m
[31m- Assert(OidIsValid(oidIndexId));[m
[31m-[m
[31m- ScanKeyInit(&skey,[m
[31m- ObjectIdAttributeNumber,[m
[31m- BTEqualStrategyNumber, F_OIDEQ,[m
[31m- ObjectIdGetDatum(objectId));[m
[31m-[m
[31m- scan = systable_beginscan(catalog, oidIndexId, true,[m
[31m- SnapshotNow, 1, &skey);[m
[31m- tuple = systable_getnext(scan);[m
[31m- if (!HeapTupleIsValid(tuple))[m
[31m- {[m
[31m- systable_endscan(scan);[m
[31m- return NULL;[m
[31m- }[m
[31m- tuple = heap_copytuple(tuple);[m
[31m-[m
[31m- systable_endscan(scan);[m
[31m- }[m
[31m-[m
[31m- return tuple;[m
[31m-}[m
[31m-[m
[31m-/*[m
* Generic function to change the ownership of a given object, for simple[m
* cases (won't work for tables, nor other cases where we need to do more than[m
* change the ownership column of a single catalog entry).[m
[1mdiff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c[m
[1mindex 18b3753..66f5005 100644[m
[1m--- a/src/backend/commands/event_trigger.c[m
[1m+++ b/src/backend/commands/event_trigger.c[m
[36m@@ -25,6 +25,7 @@[m
#include "commands/dbcommands.h"[m
#include "commands/event_trigger.h"[m
#include "commands/trigger.h"[m
[32m+[m[32m#include "funcapi.h"[m
#include "parser/parse_func.h"[m
#include "pgstat.h"[m
#include "miscadmin.h"[m
[36m@@ -39,6 +40,10 @@[m
#include "utils/syscache.h"[m
#include "tcop/utility.h"[m
[m
[32m+[m[32m/* Globally visible state variables */[m
[32m+[m[32mbool evtrig_sqldrop_inprogress = false;[m
[32m+[m[32mslist_head SQLDropList = SLIST_STATIC_INIT(SQLDropList);[m
[32m+[m
typedef struct[m
{[m
const char *obtypename;[m
[36m@@ -88,6 +93,15 @@[m [mstatic event_trigger_support_data event_trigger_support[] = {[m
{ NULL, false }[m
};[m
[m
[32m+[m[32m/* Support for dropped objects */[m
[32m+[m[32mtypedef struct SQLDropObject[m
[32m+[m[32m{[m
[32m+[m [32mObjectAddress address;[m
[32m+[m [32mchar *objname;[m
[32m+[m [32mchar *schemaname;[m
[32m+[m [32mslist_node next;[m
[32m+[m[32m} SQLDropObject;[m
[32m+[m
static void AlterEventTriggerOwner_internal(Relation rel,[m
HeapTuple tup,[m
Oid newOwnerId);[m
[36m@@ -150,8 +164,12 @@[m [mCreateEventTrigger(CreateEventTrigStmt *stmt)[m
}[m
[m
/* Validate tag list, if any. */[m
[31m- if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL)[m
[32m+[m [32mif ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||[m
[32m+[m [32m strcmp(stmt->eventname, "ddl_command_end") == 0)[m
[32m+[m [32m&& tags != NULL)[m
[32m+[m [32m{[m
validate_ddl_tags("tag", tags);[m
[32m+[m [32m}[m
[m
/*[m
* Give user a nice error message if an event trigger of the same name[m
[36m@@ -218,7 +236,8 @@[m [mcheck_ddl_tag(const char *tag)[m
if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 ||[m
pg_strcasecmp(tag, "SELECT INTO") == 0 ||[m
pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||[m
[31m- pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)[m
[32m+[m [32mpg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 ||[m
[32m+[m [32mpg_strcasecmp(tag, "DROP OWNED") == 0)[m
return EVENT_TRIGGER_COMMAND_TAG_OK;[m
[m
/*[m
[36m@@ -825,3 +844,202 @@[m [mEventTriggerSupportsObjectType(ObjectType obtype)[m
}[m
return true;[m
}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Support for dropped objects information on event trigger functions.[m
[32m+[m[32m *[m
[32m+[m[32m * We keep the list of objects dropped by the current command in a list of[m
[32m+[m[32m * these structs. Each command that might drop objects saves the current[m
[32m+[m[32m * list in a local variable, initialize a new empty list and do the dependency.c[m
[32m+[m[32m * dance to drop objects, which populates the list; when the event triggers are[m
[32m+[m[32m * invoked they can consume the list via pg_event_trigger_dropped_objects().[m
[32m+[m[32m * When the command finishes, the list is cleared and the original list is[m
[32m+[m[32m * restored. This is to support the case that an event trigger function drops[m
[32m+[m[32m * objects "reentrantly".[m
[32m+[m[32m *[m
[32m+[m[32m * For each object dropped, we save the below info, which can be obtained as a[m
[32m+[m[32m * set via the pg_event_trigger_dropped_objects() SQL-callable function.[m
[32m+[m[32m */[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Initialize state of objects dropped[m
[32m+[m[32m */[m
[32m+[m[32mvoid[m
[32m+[m[32mEventTriggerInitializeDrop(bool *save_inprogress, slist_head *save_objlist)[m
[32m+[m[32m{[m
[32m+[m [32m/* save previous state in local vars of caller, for later restore */[m
[32m+[m [32m*save_inprogress = evtrig_sqldrop_inprogress;[m
[32m+[m [32m*save_objlist = SQLDropList;[m
[32m+[m
[32m+[m [32mevtrig_sqldrop_inprogress = true;[m
[32m+[m [32mslist_init(&SQLDropList);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Restore state after running a command that drops objects; free memory from a[m
[32m+[m[32m * list we may have created.[m
[32m+[m[32m */[m
[32m+[m[32mvoid[m
[32m+[m[32mEventTriggerFinalizeDrop(bool save_inprogress, slist_head save_objlist)[m
[32m+[m[32m{[m
[32m+[m [32mslist_mutable_iter iter;[m
[32m+[m
[32m+[m [32mslist_foreach_modify(iter, &SQLDropList)[m
[32m+[m [32m{[m
[32m+[m [32mSQLDropObject *obj = slist_container(SQLDropObject, next, iter.cur);[m
[32m+[m
[32m+[m [32mif (obj->objname)[m
[32m+[m [32mpfree(obj->objname);[m
[32m+[m [32mif (obj->schemaname)[m
[32m+[m [32mpfree(obj->schemaname);[m
[32m+[m [32mpfree(obj);[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mevtrig_sqldrop_inprogress = save_inprogress;[m
[32m+[m [32mSQLDropList = save_objlist;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Register one object as being dropped by the current command.[m
[32m+[m[32m *[m
[32m+[m[32m * XXX do we need to think about memory context these things are stored in?[m
[32m+[m[32m */[m
[32m+[m[32mvoid[m
[32m+[m[32mevtrig_sqldrop_add_object(ObjectAddress *object)[m
[32m+[m[32m{[m
[32m+[m [32mSQLDropObject *obj;[m
[32m+[m [32mHeapTuple tuple;[m
[32m+[m [32mRelation catalog;[m
[32m+[m
[32m+[m [32mAssert(EventTriggerSupportsObjectType(getObjectClass(object)));[m
[32m+[m
[32m+[m [32mobj = palloc0(sizeof(SQLDropObject));[m
[32m+[m [32mobj->address = *object;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * Obtain object and schema names from the object's catalog tuple, if one[m
[32m+[m [32m * exists.[m
[32m+[m [32m */[m
[32m+[m [32mcatalog = heap_open(obj->address.classId, AccessShareLock);[m
[32m+[m [32mtuple = get_catalog_object_by_oid(catalog, obj->address.objectId);[m
[32m+[m [32mif (tuple)[m
[32m+[m [32m{[m
[32m+[m [32mAttrNumber attnum;[m
[32m+[m [32mDatum datum;[m
[32m+[m [32mbool isnull;[m
[32m+[m
[32m+[m [32mattnum = get_object_attnum_name(obj->address.classId);[m
[32m+[m [32mif (attnum != InvalidAttrNumber)[m
[32m+[m [32m{[m
[32m+[m [32mdatum = heap_getattr(tuple, attnum,[m
[32m+[m [32m RelationGetDescr(catalog), &isnull);[m
[32m+[m [32mif (!isnull)[m
[32m+[m [32mobj->objname = pstrdup(NameStr(*DatumGetName(datum)));[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mattnum = get_object_attnum_namespace(obj->address.classId);[m
[32m+[m [32mif (attnum != InvalidAttrNumber)[m
[32m+[m [32m{[m
[32m+[m [32mdatum = heap_getattr(tuple, attnum,[m
[32m+[m [32m RelationGetDescr(catalog), &isnull);[m
[32m+[m [32mif (!isnull)[m
[32m+[m [32mobj->schemaname = get_namespace_name(DatumGetObjectId(datum));[m
[32m+[m [32m}[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mheap_close(catalog, AccessShareLock);[m
[32m+[m
[32m+[m [32mslist_push_head(&SQLDropList, &obj->next);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * pg_event_trigger_dropped_objects[m
[32m+[m[32m *[m
[32m+[m[32m * Make the list of dropped objects available to the user function run by the[m
[32m+[m[32m * Event Trigger.[m
[32m+[m[32m */[m
[32m+[m[32mDatum[m
[32m+[m[32mpg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)[m
[32m+[m[32m{[m
[32m+[m [32mReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;[m
[32m+[m [32mTupleDesc tupdesc;[m
[32m+[m [32mTuplestorestate *tupstore;[m
[32m+[m [32mMemoryContext per_query_ctx;[m
[32m+[m [32mMemoryContext oldcontext;[m
[32m+[m [32mslist_iter iter;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * This function is meant to be called from within an event trigger in[m
[32m+[m [32m * order to get the list of objects dropped, if any.[m
[32m+[m [32m */[m
[32m+[m [32mif (!evtrig_sqldrop_inprogress)[m
[32m+[m [32mereport(ERROR,[m
[32m+[m [32m(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),[m
[32m+[m [32m errmsg("%s can only be called from an event trigger function",[m
[32m+[m [32m"pg_event_trigger_dropped_objects()")));[m
[32m+[m
[32m+[m [32m/* check to see if caller supports us returning a tuplestore */[m
[32m+[m [32mif (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))[m
[32m+[m [32mereport(ERROR,[m
[32m+[m [32m(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),[m
[32m+[m [32m errmsg("set-valued function called in context that cannot accept a set")));[m
[32m+[m [32mif (!(rsinfo->allowedModes & SFRM_Materialize))[m
[32m+[m [32mereport(ERROR,[m
[32m+[m [32m(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),[m
[32m+[m [32m errmsg("materialize mode required, but it is not allowed in this context")));[m
[32m+[m
[32m+[m [32m/* Build a tuple descriptor for our result type */[m
[32m+[m [32mif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)[m
[32m+[m [32melog(ERROR, "return type must be a row type");[m
[32m+[m
[32m+[m [32m/* Build tuplestore to hold the result rows */[m
[32m+[m [32mper_query_ctx = rsinfo->econtext->ecxt_per_query_memory;[m
[32m+[m [32moldcontext = MemoryContextSwitchTo(per_query_ctx);[m
[32m+[m
[32m+[m [32mtupstore = tuplestore_begin_heap(true, false, work_mem);[m
[32m+[m [32mrsinfo->returnMode = SFRM_Materialize;[m
[32m+[m [32mrsinfo->setResult = tupstore;[m
[32m+[m [32mrsinfo->setDesc = tupdesc;[m
[32m+[m
[32m+[m [32mMemoryContextSwitchTo(oldcontext);[m
[32m+[m
[32m+[m [32mslist_foreach(iter, &SQLDropList)[m
[32m+[m [32m{[m
[32m+[m [32mSQLDropObject *obj;[m
[32m+[m [32mDatum values[5];[m
[32m+[m [32mbool nulls[5];[m
[32m+[m
[32m+[m [32mobj = slist_container(SQLDropObject, next, iter.cur);[m
[32m+[m
[32m+[m [32mMemSet(values, 0, sizeof(values));[m
[32m+[m [32mMemSet(nulls, 0, sizeof(nulls));[m
[32m+[m
[32m+[m [32m/* classid */[m
[32m+[m [32mvalues[0] = ObjectIdGetDatum(obj->address.classId);[m
[32m+[m
[32m+[m [32m/* objid */[m
[32m+[m [32mvalues[1] = ObjectIdGetDatum(obj->address.objectId);[m
[32m+[m
[32m+[m [32m/* objsubid */[m
[32m+[m [32mvalues[2] = Int32GetDatum(obj->address.objectSubId);[m
[32m+[m
[32m+[m [32m/* objname */[m
[32m+[m [32mif (obj->objname)[m
[32m+[m [32mvalues[3] = CStringGetTextDatum(obj->objname);[m
[32m+[m [32melse[m
[32m+[m [32mnulls[3] = true;[m
[32m+[m
[32m+[m [32m/* schemaname */[m
[32m+[m [32mif (obj->schemaname)[m
[32m+[m [32mvalues[4] = CStringGetTextDatum(obj->objname);[m
[32m+[m [32melse[m
[32m+[m [32mnulls[4] = true;[m
[32m+[m
[32m+[m [32mtuplestore_putvalues(tupstore, tupdesc, values, nulls);[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32m/* clean up and return the tuplestore */[m
[32m+[m [32mtuplestore_donestoring(tupstore);[m
[32m+[m
[32m+[m [32mreturn (Datum) 0;[m
[32m+[m[32m}[m
[1mdiff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c[m
[1mindex 8904c6f..e30fe42 100644[m
[1m--- a/src/backend/tcop/utility.c[m
[1m+++ b/src/backend/tcop/utility.c[m
[36m@@ -697,33 +697,60 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
case T_DropStmt:[m
{[m
DropStmt *stmt = (DropStmt *) parsetree;[m
[32m+[m [32mbool save_inprogress;[m
[32m+[m [32mslist_head save_objlist;[m
[m
[31m- if (isCompleteQuery[m
[31m- && EventTriggerSupportsObjectType(stmt->removeType))[m
[32m+[m [32m/*[m
[32m+[m [32m * don't run any event trigger when we require not to have open[m
[32m+[m [32m * a transaction[m
[32m+[m [32m */[m
[32m+[m [32mif (stmt->removeType == OBJECT_INDEX && stmt->concurrent)[m
[32m+[m [32mPreventTransactionChain(isTopLevel,[m
[32m+[m [32m"DROP INDEX CONCURRENTLY");[m
[32m+[m
[32m+[m [32mif (isCompleteQuery &&[m
[32m+[m [32mEventTriggerSupportsObjectType(stmt->removeType))[m
[32m+[m [32m{[m
EventTriggerDDLCommandStart(parsetree);[m
[m
[31m- switch (stmt->removeType)[m
[32m+[m [32mEventTriggerInitializeDrop(&save_inprogress, &save_objlist);[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mPG_TRY();[m
{[m
[31m- case OBJECT_INDEX:[m
[31m- if (stmt->concurrent)[m
[31m- PreventTransactionChain(isTopLevel,[m
[31m- "DROP INDEX CONCURRENTLY");[m
[31m- /* fall through */[m
[32m+[m [32mswitch (stmt->removeType)[m
[32m+[m [32m{[m
[32m+[m [32mcase OBJECT_INDEX:[m
[32m+[m [32mcase OBJECT_TABLE:[m
[32m+[m [32mcase OBJECT_SEQUENCE:[m
[32m+[m [32mcase OBJECT_VIEW:[m
[32m+[m [32mcase OBJECT_FOREIGN_TABLE:[m
[32m+[m [32mRemoveRelations((DropStmt *) parsetree);[m
[32m+[m [32mbreak;[m
[32m+[m [32mdefault:[m
[32m+[m [32mRemoveObjects((DropStmt *) parsetree);[m
[32m+[m [32mbreak;[m
[32m+[m [32m}[m
[m
[31m- case OBJECT_TABLE:[m
[31m- case OBJECT_SEQUENCE:[m
[31m- case OBJECT_VIEW:[m
[31m- case OBJECT_FOREIGN_TABLE:[m
[31m- RemoveRelations((DropStmt *) parsetree);[m
[31m- break;[m
[31m- default:[m
[31m- RemoveObjects((DropStmt *) parsetree);[m
[31m- break;[m
[32m+[m [32mif (isCompleteQuery[m
[32m+[m [32m&& EventTriggerSupportsObjectType(stmt->removeType))[m
[32m+[m [32m{[m
[32m+[m [32mEventTriggerDDLCommandEnd(parsetree);[m
[32m+[m [32m}[m
[32m+[m [32m}[m
[32m+[m [32mPG_CATCH();[m
[32m+[m [32m{[m
[32m+[m [32mif (isCompleteQuery[m
[32m+[m [32m&& EventTriggerSupportsObjectType(stmt->removeType))[m
[32m+[m [32mEventTriggerFinalizeDrop(save_inprogress, save_objlist);[m
[32m+[m
[32m+[m [32mPG_RE_THROW();[m
}[m
[32m+[m [32mPG_END_TRY();[m
[m
if (isCompleteQuery[m
&& EventTriggerSupportsObjectType(stmt->removeType))[m
[31m- EventTriggerDDLCommandEnd(parsetree);[m
[32m+[m [32mEventTriggerFinalizeDrop(save_inprogress, save_objlist);[m
[m
break;[m
}[m
[36m@@ -1238,9 +1265,37 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
break;[m
[m
case T_DropOwnedStmt:[m
[31m- /* no event triggers for global objects */[m
[31m- DropOwnedObjects((DropOwnedStmt *) parsetree);[m
[31m- break;[m
[32m+[m [32m{[m
[32m+[m [32mbool save_inprogress;[m
[32m+[m [32mslist_head save_objlist;[m
[32m+[m
[32m+[m [32mif (isCompleteQuery)[m
[32m+[m [32m{[m
[32m+[m [32mEventTriggerDDLCommandStart(parsetree);[m
[32m+[m
[32m+[m [32mEventTriggerInitializeDrop(&save_inprogress, &save_objlist);[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mPG_TRY();[m
[32m+[m [32m{[m
[32m+[m [32mDropOwnedObjects((DropOwnedStmt *) parsetree);[m
[32m+[m
[32m+[m [32mif (isCompleteQuery)[m
[32m+[m [32mEventTriggerDDLCommandEnd(parsetree);[m
[32m+[m [32m}[m
[32m+[m [32mPG_CATCH();[m
[32m+[m [32m{[m
[32m+[m [32mif (isCompleteQuery)[m
[32m+[m [32mEventTriggerFinalizeDrop(save_inprogress, save_objlist);[m
[32m+[m [32mPG_RE_THROW();[m
[32m+[m [32m}[m
[32m+[m [32mPG_END_TRY();[m
[32m+[m
[32m+[m [32mif (isCompleteQuery)[m
[32m+[m [32mEventTriggerFinalizeDrop(save_inprogress, save_objlist);[m
[32m+[m
[32m+[m [32mbreak;[m
[32m+[m [32m}[m
[m
case T_ReassignOwnedStmt:[m
/* no event triggers for global objects */[m
[1mdiff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c[m
[1mindex 5865962..7f25de4 100644[m
[1m--- a/src/backend/utils/cache/lsyscache.c[m
[1m+++ b/src/backend/utils/cache/lsyscache.c[m
[36m@@ -18,6 +18,7 @@[m
#include "access/hash.h"[m
#include "access/htup_details.h"[m
#include "access/nbtree.h"[m
[32m+[m[32m#include "access/sysattr.h"[m
#include "bootstrap/bootstrap.h"[m
#include "catalog/pg_amop.h"[m
#include "catalog/pg_amproc.h"[m
[36m@@ -40,6 +41,7 @@[m
#include "utils/lsyscache.h"[m
#include "utils/rel.h"[m
#include "utils/syscache.h"[m
[32m+[m[32m#include "utils/tqual.h"[m
#include "utils/typcache.h"[m
[m
/* Hook for plugins to get control in get_attavgwidth() */[m
[36m@@ -2926,3 +2928,54 @@[m [mget_range_subtype(Oid rangeOid)[m
else[m
return InvalidOid;[m
}[m
[32m+[m
[32m+[m[32m/* ------------- GENERIC -------------- */[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Return a copy of the tuple for the object with the given object OID, from[m
[32m+[m[32m * the given catalog (which must have been opened by the caller and suitably[m
[32m+[m[32m * locked). NULL is returned if the OID is not found.[m
[32m+[m[32m *[m
[32m+[m[32m * We try a syscache first, if available.[m
[32m+[m[32m */[m
[32m+[m[32mHeapTuple[m
[32m+[m[32mget_catalog_object_by_oid(Relation catalog, Oid objectId)[m
[32m+[m[32m{[m
[32m+[m [32mHeapTuple tuple;[m
[32m+[m [32mOid classId = RelationGetRelid(catalog);[m
[32m+[m [32mint oidCacheId = get_object_catcache_oid(classId);[m
[32m+[m
[32m+[m [32mif (oidCacheId > 0)[m
[32m+[m [32m{[m
[32m+[m [32mtuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));[m
[32m+[m [32mif (!HeapTupleIsValid(tuple)) /* should not happen */[m
[32m+[m [32mreturn NULL;[m
[32m+[m [32m}[m
[32m+[m [32melse[m
[32m+[m [32m{[m
[32m+[m [32mOid oidIndexId = get_object_oid_index(classId);[m
[32m+[m [32mSysScanDesc scan;[m
[32m+[m [32mScanKeyData skey;[m
[32m+[m
[32m+[m [32mAssert(OidIsValid(oidIndexId));[m
[32m+[m
[32m+[m [32mScanKeyInit(&skey,[m
[32m+[m [32mObjectIdAttributeNumber,[m
[32m+[m [32mBTEqualStrategyNumber, F_OIDEQ,[m
[32m+[m [32mObjectIdGetDatum(objectId));[m
[32m+[m
[32m+[m [32mscan = systable_beginscan(catalog, oidIndexId, true,[m
[32m+[m [32m SnapshotNow, 1, &skey);[m
[32m+[m [32mtuple = systable_getnext(scan);[m
[32m+[m [32mif (!HeapTupleIsValid(tuple))[m
[32m+[m [32m{[m
[32m+[m [32msystable_endscan(scan);[m
[32m+[m [32mreturn NULL;[m
[32m+[m [32m}[m
[32m+[m [32mtuple = heap_copytuple(tuple);[m
[32m+[m
[32m+[m [32msystable_endscan(scan);[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mreturn tuple;[m
[32m+[m[32m}[m
[1mdiff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h[m
[1mindex d9f50d2..6b7d4a6 100644[m
[1m--- a/src/include/catalog/pg_proc.h[m
[1m+++ b/src/include/catalog/pg_proc.h[m
[36m@@ -4679,7 +4679,9 @@[m [mDESCR("SP-GiST support for quad tree over range");[m
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_ ));[m
DESCR("SP-GiST support for quad tree over range");[m
[m
[31m-[m
[32m+[m[32m/* event triggers */[m
[32m+[m[32mDATA(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}" "{o,o,o,o,0}" "{classid, objid, objsubid, objname, schemaname}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));[m
[32m+[m[32mDESCR("list an extension's version update paths");[m
/*[m
* Symbolic values for provolatile column: these indicate whether the result[m
* of a function is dependent *only* on the values of its explicit arguments,[m
[1mdiff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h[m
[1mindex 74c150b..2e7d815 100644[m
[1m--- a/src/include/commands/event_trigger.h[m
[1m+++ b/src/include/commands/event_trigger.h[m
[36m@@ -13,7 +13,10 @@[m
#ifndef EVENT_TRIGGER_H[m
#define EVENT_TRIGGER_H[m
[m
[32m+[m[32m#include "catalog/dependency.h"[m
[32m+[m[32m#include "catalog/objectaddress.h"[m
#include "catalog/pg_event_trigger.h"[m
[32m+[m[32m#include "lib/ilist.h"[m
#include "nodes/parsenodes.h"[m
[m
typedef struct EventTriggerData[m
[36m@@ -43,4 +46,10 @@[m [mextern bool EventTriggerSupportsObjectType(ObjectType obtype);[m
extern void EventTriggerDDLCommandStart(Node *parsetree);[m
extern void EventTriggerDDLCommandEnd(Node *parsetree);[m
[m
[32m+[m[32mextern void EventTriggerInitializeDrop(bool *save_inprogress,[m
[32m+[m [32m slist_head *save_objlist);[m
[32m+[m[32mextern void EventTriggerFinalizeDrop(bool save_inprogress,[m
[32m+[m [32m slist_head save_objlist);[m
[32m+[m[32mextern void evtrig_sqldrop_add_object(ObjectAddress *object);[m
[32m+[m
#endif /* EVENT_TRIGGER_H */[m
[1mdiff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h[m
[1mindex 533539c..d51b829 100644[m
[1m--- a/src/include/utils/builtins.h[m
[1m+++ b/src/include/utils/builtins.h[m
[36m@@ -1146,6 +1146,9 @@[m [mextern Datum pg_describe_object(PG_FUNCTION_ARGS);[m
/* commands/constraint.c */[m
extern Datum unique_key_recheck(PG_FUNCTION_ARGS);[m
[m
[32m+[m[32m/* commands/event_trigger.c */[m
[32m+[m[32mextern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);[m
[32m+[m
/* commands/extension.c */[m
extern Datum pg_available_extensions(PG_FUNCTION_ARGS);[m
extern Datum pg_available_extension_versions(PG_FUNCTION_ARGS);[m
[1mdiff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h[m
[1mindex 49f459a..dfa6eb7 100644[m
[1m--- a/src/include/utils/lsyscache.h[m
[1m+++ b/src/include/utils/lsyscache.h[m
[36m@@ -16,6 +16,7 @@[m
#include "access/attnum.h"[m
#include "access/htup.h"[m
#include "nodes/pg_list.h"[m
[32m+[m[32m#include "utils/relcache.h"[m
[m
/* Result list element for get_op_btree_interpretation */[m
typedef struct OpBtreeInterpretation[m
[36m@@ -152,6 +153,7 @@[m [mextern void free_attstatsslot(Oid atttype,[m
float4 *numbers, int nnumbers);[m
extern char *get_namespace_name(Oid nspid);[m
extern Oid get_range_subtype(Oid rangeOid);[m
[32m+[m[32mextern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);[m
[m
#define type_is_array(typid) (get_element_type(typid) != InvalidOid)[m
/* type_is_array_domain accepts both plain arrays and domains over arrays */[m
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Dimitri Fontaine escribió:
The good news is that the patch to do that has already been sent on this
list, and got reviewed in details by Álvaro who did offer incremental
changes. Version 3 of that patch is to be found in:Here's a v4 of that patch. I added support for DROP OWNED, and added
object name and schema name available to the pg_dropped_objects
function.
Thanks!
Do we want some more stuff provided by pg_dropped_objects? We now have
classId, objectId, objectSubId, object name, schema name. One further
thing I think we need is the object's type, i.e. a simple untranslated
string "table", "view", "operator" and so on. AFAICT this requires a
nearly-duplicate of getObjectDescription.
About what missing information to add, please review:
http://wiki.postgresql.org/wiki/Event_Triggers#How_to_expose_Information_to_Event_Triggers_Functions
I'd like us to provide the same set of extra information from within
classic Event Triggers and in the records returned by the function.
Maybe a good idea would be to create a datatype for that, and publish a
single TG_DETAILS variable from within Event Triggers, of that type, and
have the pg_dropped_objects() function return a setof that type?
About the implementation and the getObjectDescription() remark, please
have a look at your latest revision of the other patch in the series,
/messages/by-id/20130109165829.GB4490@alvh.no-ip.org
I think it provides exactly what you need here, and you already did
cleanup my work for getting at that…
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Do we want some more stuff provided by pg_dropped_objects? We now have
classId, objectId, objectSubId, object name, schema name. One further
thing I think we need is the object's type, i.e. a simple untranslated
string "table", "view", "operator" and so on. AFAICT this requires a
nearly-duplicate of getObjectDescription.About what missing information to add, please review:
http://wiki.postgresql.org/wiki/Event_Triggers#How_to_expose_Information_to_Event_Triggers_Functions
I'd like us to provide the same set of extra information from within
classic Event Triggers and in the records returned by the function.Maybe a good idea would be to create a datatype for that, and publish a
single TG_DETAILS variable from within Event Triggers, of that type, and
have the pg_dropped_objects() function return a setof that type?
I'm very unsure about that idea. In any case the proposed name seems
way too general. Maybe we could have a separate datatype for objects
being dropped, which would be specific to
pg_event_trigger_dropped_objects(), and not try to mix it with other
events' info; but really I don't see much point in a tailored datatype
when we can simply declare the right set of columns to the function in
the first place.
About the implementation and the getObjectDescription() remark, please
have a look at your latest revision of the other patch in the series,/messages/by-id/20130109165829.GB4490@alvh.no-ip.org
I think it provides exactly what you need here, and you already did
cleanup my work for getting at that…
Hmm, it includes a completely different implementation to get at the
object name and schema (I didn't know I had written that previous one --
how ironic), but it doesn't include the "obtypename" (your term) which
is what I want here. (BTW I don't like "obtypename" at all; how about
"object_type"?) So we would have "object_type", "object_name",
"object_schema".
Another question. If I do ALTER TABLE foo DROP COLUMN bar, do we need
to fire an event trigger for the dropped column? Right now we don't,
ISTM we should. And if we want that, then the above set of three
properties doesn't cut it.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
I'm very unsure about that idea. In any case the proposed name seems
way too general. Maybe we could have a separate datatype for objects
being dropped, which would be specific to
pg_event_trigger_dropped_objects(), and not try to mix it with other
events' info; but really I don't see much point in a tailored datatype
when we can simply declare the right set of columns to the function in
the first place.
I'm not too sure about it either, it's just an excuse to keep the two
places in sync (TG_* and the pg_event_trigger_dropped_objects() return
type).
Hmm, it includes a completely different implementation to get at the
object name and schema (I didn't know I had written that previous one --
how ironic), but it doesn't include the "obtypename" (your term) which
is what I want here. (BTW I don't like "obtypename" at all; how about
"object_type"?) So we would have "object_type", "object_name",
"object_schema".
IIRC obtypename is not my choice, it already is named that way in the
code, maybe not in user visible places, though.
Another question. If I do ALTER TABLE foo DROP COLUMN bar, do we need
to fire an event trigger for the dropped column? Right now we don't,
ISTM we should. And if we want that, then the above set of three
properties doesn't cut it.
Do we paint ourselves in a corner by not supporting columns in the first
release?
Table columns are proper SQL level objects in that they have their own
catalog entry and OID and a set of commands to manage them, but that set
of command is in fact a *subset* of ALTER TABLE. It feels like drifting
a little more into the land of exposing PostgreSQL internals in a way,
so I'm not too sure about the proper answer here.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Another question. If I do ALTER TABLE foo DROP COLUMN bar, do we need
to fire an event trigger for the dropped column? Right now we don't,
ISTM we should. And if we want that, then the above set of three
properties doesn't cut it.Do we paint ourselves in a corner by not supporting columns in the first
release?
Well, the main distinction is that there's no space to refer to them in
the current implementation, for lack of objectSubId or (more likely)
column name.
Table columns are proper SQL level objects in that they have their own
catalog entry and OID and a set of commands to manage them, but that set
of command is in fact a *subset* of ALTER TABLE.
Table columns do not have OIDs; you refer to them by
(objectId, objectSubId). The latter is an integer that references
pg_attribute.attnum.
I am only wondering about ways to drop things at present, without
concern for whether it's a straight DROP FOO command or ALTER, etc.
It feels like drifting
a little more into the land of exposing PostgreSQL internals in a way,
so I'm not too sure about the proper answer here.
Mumble.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Do we want some more stuff provided by pg_dropped_objects? We now have
classId, objectId, objectSubId, object name, schema name. One further
thing I think we need is the object's type, i.e. a simple untranslated
string "table", "view", "operator" and so on. AFAICT this requires a
nearly-duplicate of getObjectDescription.About what missing information to add, please review:
http://wiki.postgresql.org/wiki/Event_Triggers#How_to_expose_Information_to_Event_Triggers_Functions
That list contains the following items:
TG_OBJECTID
TG_OBJECTNAME
TG_SCHEMANAME
TG_OPERATION
TG_OBTYPENAME
TG_CONTEXT
Of the above, TG_OPERATION is redundant with the fact that we're
building pg_dropped_objects (i.e. the user code knows it's dealing with
a drop) and TG_CONTEXT is not relevant; with the attached patch, we
provide all the other bits.
I think this is mostly ready to go in. I'll look at your docs, and
unless there are more objections will commit later or early tomorrow.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.5.patchtext/x-diff; charset=us-asciiDownload
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 198,203 **** static bool stack_address_present_add_flags(const ObjectAddress *object,
--- 198,204 ----
ObjectAddressStack *stack);
static void getRelationDescription(StringInfo buffer, Oid relid);
static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+ static void getRelationTypeDescription(StringInfo buffer, Oid relid);
/*
***************
*** 267,272 **** performDeletion(const ObjectAddress *object,
--- 268,279 ----
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
***************
*** 349,354 **** performMultipleDeletions(const ObjectAddresses *objects,
--- 356,367 ----
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
***************
*** 366,371 **** performMultipleDeletions(const ObjectAddresses *objects,
--- 379,388 ----
* 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,
***************
*** 3107,3109 **** pg_describe_object(PG_FUNCTION_ARGS)
--- 3124,3318 ----
description = getObjectDescription(&address);
PG_RETURN_TEXT_P(cstring_to_text(description));
}
+
+ char *
+ getObjectTypeDescription(const ObjectAddress *object)
+ {
+ StringInfoData buffer;
+
+ initStringInfo(&buffer);
+
+ switch (getObjectClass(object))
+ {
+ case OCLASS_CLASS:
+ getRelationTypeDescription(&buffer, object->objectId);
+ break;
+
+ case OCLASS_PROC:
+ appendStringInfo(&buffer, "function");
+ break;
+
+ case OCLASS_TYPE:
+ appendStringInfo(&buffer, "type");
+ break;
+
+ case OCLASS_CAST:
+ appendStringInfo(&buffer, "cast");
+ break;
+
+ case OCLASS_COLLATION:
+ appendStringInfo(&buffer, "collation");
+ break;
+
+ case OCLASS_CONSTRAINT:
+ appendStringInfo(&buffer, "constraint");
+ break;
+
+ case OCLASS_CONVERSION:
+ appendStringInfo(&buffer, "conversion");
+ break;
+
+ case OCLASS_DEFAULT:
+ appendStringInfo(&buffer, "default value");
+ break;
+
+ case OCLASS_LANGUAGE:
+ appendStringInfo(&buffer, "language");
+ break;
+
+ case OCLASS_LARGEOBJECT:
+ appendStringInfo(&buffer, "large object");
+ break;
+
+ case OCLASS_OPERATOR:
+ appendStringInfo(&buffer, "operator");
+ break;
+
+ case OCLASS_OPCLASS:
+ appendStringInfo(&buffer, "operator class");
+ break;
+
+ case OCLASS_OPFAMILY:
+ appendStringInfo(&buffer, "operator family");
+ break;
+
+ case OCLASS_AMOP:
+ appendStringInfo(&buffer, "operator of access method");
+ break;
+
+ case OCLASS_AMPROC:
+ appendStringInfo(&buffer, "function of access method");
+ break;
+
+ case OCLASS_REWRITE:
+ appendStringInfo(&buffer, "rule");
+ break;
+
+ case OCLASS_TRIGGER:
+ appendStringInfo(&buffer, "trigger");
+ break;
+
+ case OCLASS_SCHEMA:
+ appendStringInfo(&buffer, "schema");
+ break;
+
+ case OCLASS_TSPARSER:
+ appendStringInfo(&buffer, "text search parser");
+ break;
+
+ case OCLASS_TSDICT:
+ appendStringInfo(&buffer, "text search dictionary");
+ break;
+
+ case OCLASS_TSTEMPLATE:
+ appendStringInfo(&buffer, "text search template");
+ break;
+
+ case OCLASS_TSCONFIG:
+ appendStringInfo(&buffer, "text search configuration");
+ break;
+
+ case OCLASS_ROLE:
+ appendStringInfo(&buffer, "role");
+ break;
+
+ case OCLASS_DATABASE:
+ appendStringInfo(&buffer, "database");
+ break;
+
+ case OCLASS_TBLSPACE:
+ appendStringInfo(&buffer, "tablespace");
+ break;
+
+ case OCLASS_FDW:
+ appendStringInfo(&buffer, "foreign-data wrapper");
+ break;
+
+ case OCLASS_FOREIGN_SERVER:
+ appendStringInfo(&buffer, "server");
+ break;
+
+ case OCLASS_USER_MAPPING:
+ appendStringInfo(&buffer, "user mapping");
+ break;
+
+ case OCLASS_DEFACL:
+ /* XXX do we need more detail here? */
+ appendStringInfo(&buffer, "default ACL");
+ break;
+
+ case OCLASS_EXTENSION:
+ appendStringInfo(&buffer, "extension");
+ break;
+
+ case OCLASS_EVENT_TRIGGER:
+ appendStringInfo(&buffer, "event trigger");
+ break;
+
+ default:
+ appendStringInfo(&buffer, "unrecognized object type");
+ break;
+ }
+
+ return buffer.data;
+ }
+
+ /*
+ * subroutine for getObjectTypeDescription: describe a relation type
+ */
+ static void
+ getRelationTypeDescription(StringInfo buffer, Oid relid)
+ {
+ HeapTuple relTup;
+ Form_pg_class relForm;
+
+ relTup = SearchSysCache1(RELOID,
+ ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+ switch (relForm->relkind)
+ {
+ case RELKIND_RELATION:
+ appendStringInfo(buffer, "table");
+ break;
+ case RELKIND_INDEX:
+ appendStringInfo(buffer, "index");
+ break;
+ case RELKIND_SEQUENCE:
+ appendStringInfo(buffer, "sequence");
+ break;
+ case RELKIND_TOASTVALUE:
+ appendStringInfo(buffer, "toast table");
+ break;
+ case RELKIND_VIEW:
+ appendStringInfo(buffer, "view");
+ break;
+ case RELKIND_MATVIEW:
+ appendStringInfo(buffer, "materialized view");
+ break;
+ case RELKIND_COMPOSITE_TYPE:
+ appendStringInfo(buffer, "composite type");
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ appendStringInfo(buffer, "foreign table");
+ break;
+ default:
+ /* shouldn't get here */
+ appendStringInfo(buffer, "relation");
+ break;
+ }
+
+ ReleaseSysCache(relTup);
+ }
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 748,805 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
}
/*
- * Return a copy of the tuple for the object with the given object OID, from
- * the given catalog (which must have been opened by the caller and suitably
- * locked). NULL is returned if the OID is not found.
- *
- * We try a syscache first, if available.
- *
- * XXX this function seems general in possible usage. Given sufficient callers
- * elsewhere, we should consider moving it to a more appropriate place.
- */
- static HeapTuple
- get_catalog_object_by_oid(Relation catalog, Oid objectId)
- {
- HeapTuple tuple;
- Oid classId = RelationGetRelid(catalog);
- int oidCacheId = get_object_catcache_oid(classId);
-
- if (oidCacheId > 0)
- {
- tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
- if (!HeapTupleIsValid(tuple)) /* should not happen */
- return NULL;
- }
- else
- {
- Oid oidIndexId = get_object_oid_index(classId);
- SysScanDesc scan;
- ScanKeyData skey;
-
- Assert(OidIsValid(oidIndexId));
-
- ScanKeyInit(&skey,
- ObjectIdAttributeNumber,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(objectId));
-
- scan = systable_beginscan(catalog, oidIndexId, true,
- SnapshotNow, 1, &skey);
- tuple = systable_getnext(scan);
- if (!HeapTupleIsValid(tuple))
- {
- systable_endscan(scan);
- return NULL;
- }
- tuple = heap_copytuple(tuple);
-
- systable_endscan(scan);
- }
-
- return tuple;
- }
-
- /*
* Generic function to change the ownership of a given object, for simple
* cases (won't work for tables, nor other cases where we need to do more than
* change the ownership column of a single catalog entry).
--- 748,753 ----
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 25,30 ****
--- 25,31 ----
#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 ****
--- 40,49 ----
#include "utils/syscache.h"
#include "tcop/utility.h"
+ /* Globally visible state variables */
+ bool evtrig_sqldrop_inprogress = false;
+ slist_head SQLDropList = SLIST_STATIC_INIT(SQLDropList);
+
typedef struct
{
const char *obtypename;
***************
*** 89,94 **** static event_trigger_support_data event_trigger_support[] = {
--- 94,109 ----
{ NULL, false }
};
+ /* Support for dropped objects */
+ typedef struct SQLDropObject
+ {
+ ObjectAddress address;
+ char *objname;
+ 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
--- 166,177 ----
}
/* 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;
/*
--- 239,246 ----
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;
/*
***************
*** 827,829 **** EventTriggerSupportsObjectType(ObjectType obtype)
--- 847,1054 ----
}
return true;
}
+
+ /*
+ * Support for dropped objects information on event trigger functions.
+ *
+ * We keep the list of objects dropped by the current command in a list of
+ * these structs. Each command that might drop objects saves the current
+ * list in a local variable, initialize a new empty list and do the dependency.c
+ * dance to drop objects, which populates the list; when the event triggers are
+ * invoked they can consume the list via pg_event_trigger_dropped_objects().
+ * When the command finishes, the list is cleared and the original list is
+ * restored. This is to support the case that an event trigger function drops
+ * objects "reentrantly".
+ *
+ * For each object dropped, we save the below info, which can be obtained as a
+ * set via the pg_event_trigger_dropped_objects() SQL-callable function.
+ */
+
+ /*
+ * Initialize state of objects dropped
+ */
+ void
+ EventTriggerInitializeDrop(bool *save_inprogress, slist_head *save_objlist)
+ {
+ /* save previous state in local vars of caller, for later restore */
+ *save_inprogress = evtrig_sqldrop_inprogress;
+ *save_objlist = SQLDropList;
+
+ evtrig_sqldrop_inprogress = true;
+ slist_init(&SQLDropList);
+ }
+
+ /*
+ * Restore state after running a command that drops objects; free memory from a
+ * list we may have created.
+ */
+ void
+ EventTriggerFinalizeDrop(bool save_inprogress, slist_head save_objlist)
+ {
+ slist_mutable_iter iter;
+
+ slist_foreach_modify(iter, &SQLDropList)
+ {
+ SQLDropObject *obj = slist_container(SQLDropObject, next, iter.cur);
+
+ if (obj->objname)
+ pfree(obj->objname);
+ if (obj->schemaname)
+ pfree(obj->schemaname);
+ pfree(obj);
+ }
+
+ evtrig_sqldrop_inprogress = save_inprogress;
+ SQLDropList = save_objlist;
+ }
+
+ /*
+ * Register one object as being dropped by the current command.
+ *
+ * XXX do we need to think about memory context these things are stored in?
+ */
+ void
+ evtrig_sqldrop_add_object(ObjectAddress *object)
+ {
+ SQLDropObject *obj;
+ HeapTuple tuple;
+ Relation catalog;
+
+ Assert(EventTriggerSupportsObjectType(getObjectClass(object)));
+
+ obj = palloc0(sizeof(SQLDropObject));
+ obj->address = *object;
+
+ /*
+ * Obtain object and schema names from the object's catalog tuple, if one
+ * exists.
+ */
+ 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_name(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ obj->objname = pstrdup(NameStr(*DatumGetName(datum)));
+ }
+
+ attnum = get_object_attnum_namespace(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ obj->schemaname = get_namespace_name(DatumGetObjectId(datum));
+ }
+ }
+
+ /* and object type, too */
+ obj->objecttype = getObjectTypeDescription(&obj->address);
+
+ heap_close(catalog, AccessShareLock);
+
+ slist_push_head(&SQLDropList, &obj->next);
+ }
+
+ /*
+ * 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;
+
+ /*
+ * This function is meant to be called from within an event trigger in
+ * order to get the list of objects dropped, if any.
+ */
+ if (!evtrig_sqldrop_inprogress)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s can only be called from an 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, &SQLDropList)
+ {
+ SQLDropObject *obj;
+ 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[0] = ObjectIdGetDatum(obj->address.classId);
+
+ /* objid */
+ values[1] = ObjectIdGetDatum(obj->address.objectId);
+
+ /* objsubid */
+ values[2] = Int32GetDatum(obj->address.objectSubId);
+
+ /* object type */
+ values[3] = CStringGetTextDatum(obj->objecttype);
+
+ /* objname */
+ if (obj->objname)
+ values[4] = CStringGetTextDatum(obj->objname);
+ else
+ nulls[4] = true;
+
+ /* schemaname */
+ if (obj->schemaname)
+ values[5] = CStringGetTextDatum(obj->schemaname);
+ else
+ nulls[5] = 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
***************
*** 699,732 **** standard_ProcessUtility(Node *parsetree,
case T_DropStmt:
{
DropStmt *stmt = (DropStmt *) parsetree;
! if (isCompleteQuery
! && EventTriggerSupportsObjectType(stmt->removeType))
EventTriggerDDLCommandStart(parsetree);
! 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:
! case OBJECT_MATVIEW:
! case OBJECT_FOREIGN_TABLE:
! RemoveRelations((DropStmt *) parsetree);
! break;
! default:
! RemoveObjects((DropStmt *) parsetree);
! break;
}
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
! EventTriggerDDLCommandEnd(parsetree);
break;
}
--- 699,759 ----
case T_DropStmt:
{
DropStmt *stmt = (DropStmt *) parsetree;
+ bool save_inprogress;
+ slist_head save_objlist;
! /*
! * 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);
! EventTriggerInitializeDrop(&save_inprogress, &save_objlist);
! }
!
! PG_TRY();
{
! switch (stmt->removeType)
! {
! case OBJECT_INDEX:
! case OBJECT_TABLE:
! case OBJECT_SEQUENCE:
! case OBJECT_VIEW:
! case OBJECT_MATVIEW:
! case OBJECT_FOREIGN_TABLE:
! RemoveRelations((DropStmt *) parsetree);
! break;
! default:
! RemoveObjects((DropStmt *) parsetree);
! break;
! }
! if (isCompleteQuery
! && EventTriggerSupportsObjectType(stmt->removeType))
! {
! EventTriggerDDLCommandEnd(parsetree);
! }
! }
! PG_CATCH();
! {
! if (isCompleteQuery
! && EventTriggerSupportsObjectType(stmt->removeType))
! EventTriggerFinalizeDrop(save_inprogress, save_objlist);
!
! PG_RE_THROW();
}
+ PG_END_TRY();
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
! EventTriggerFinalizeDrop(save_inprogress, save_objlist);
break;
}
***************
*** 1248,1256 **** standard_ProcessUtility(Node *parsetree,
break;
case T_DropOwnedStmt:
! /* no event triggers for global objects */
! DropOwnedObjects((DropOwnedStmt *) parsetree);
! break;
case T_ReassignOwnedStmt:
/* no event triggers for global objects */
--- 1275,1311 ----
break;
case T_DropOwnedStmt:
! {
! bool save_inprogress;
! slist_head save_objlist;
!
! if (isCompleteQuery)
! {
! EventTriggerDDLCommandStart(parsetree);
!
! EventTriggerInitializeDrop(&save_inprogress, &save_objlist);
! }
!
! PG_TRY();
! {
! DropOwnedObjects((DropOwnedStmt *) parsetree);
!
! if (isCompleteQuery)
! EventTriggerDDLCommandEnd(parsetree);
! }
! PG_CATCH();
! {
! if (isCompleteQuery)
! EventTriggerFinalizeDrop(save_inprogress, save_objlist);
! PG_RE_THROW();
! }
! PG_END_TRY();
!
! if (isCompleteQuery)
! EventTriggerFinalizeDrop(save_inprogress, save_objlist);
!
! break;
! }
case T_ReassignOwnedStmt:
/* no event triggers for global objects */
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
***************
*** 18,23 ****
--- 18,24 ----
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+ #include "access/sysattr.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
***************
*** 40,45 ****
--- 41,47 ----
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
+ #include "utils/tqual.h"
#include "utils/typcache.h"
/* Hook for plugins to get control in get_attavgwidth() */
***************
*** 2926,2928 **** get_range_subtype(Oid rangeOid)
--- 2928,2981 ----
else
return InvalidOid;
}
+
+ /* ------------- GENERIC -------------- */
+
+ /*
+ * Return a copy of the tuple for the object with the given object OID, from
+ * the given catalog (which must have been opened by the caller and suitably
+ * locked). NULL is returned if the OID is not found.
+ *
+ * We try a syscache first, if available.
+ */
+ HeapTuple
+ get_catalog_object_by_oid(Relation catalog, Oid objectId)
+ {
+ HeapTuple tuple;
+ Oid classId = RelationGetRelid(catalog);
+ int oidCacheId = get_object_catcache_oid(classId);
+
+ if (oidCacheId > 0)
+ {
+ tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ return NULL;
+ }
+ else
+ {
+ Oid oidIndexId = get_object_oid_index(classId);
+ SysScanDesc scan;
+ ScanKeyData skey;
+
+ Assert(OidIsValid(oidIndexId));
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(catalog, oidIndexId, true,
+ SnapshotNow, 1, &skey);
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ {
+ systable_endscan(scan);
+ return NULL;
+ }
+ tuple = heap_copytuple(tuple);
+
+ systable_endscan(scan);
+ }
+
+ return tuple;
+ }
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 179,184 **** extern ObjectClass getObjectClass(const ObjectAddress *object);
--- 179,186 ----
extern char *getObjectDescription(const ObjectAddress *object);
extern char *getObjectDescriptionOids(Oid classid, Oid objid);
+ extern char *getObjectTypeDescription(const ObjectAddress *object);
+
extern ObjectAddresses *new_object_addresses(void);
extern void add_exact_object_address(const ObjectAddress *object,
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4682,4687 **** DATA(insert OID = 3473 ( spg_range_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0
--- 4682,4690 ----
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, object_name, schema_name}" _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,19 ****
--- 13,22 ----
#ifndef EVENT_TRIGGER_H
#define EVENT_TRIGGER_H
+ #include "catalog/dependency.h"
+ #include "catalog/objectaddress.h"
#include "catalog/pg_event_trigger.h"
+ #include "lib/ilist.h"
#include "nodes/parsenodes.h"
typedef struct EventTriggerData
***************
*** 43,46 **** extern bool EventTriggerSupportsObjectType(ObjectType obtype);
--- 46,55 ----
extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
+ extern void EventTriggerInitializeDrop(bool *save_inprogress,
+ slist_head *save_objlist);
+ extern void EventTriggerFinalizeDrop(bool save_inprogress,
+ slist_head save_objlist);
+ extern void evtrig_sqldrop_add_object(ObjectAddress *object);
+
#endif /* EVENT_TRIGGER_H */
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 1147,1152 **** extern Datum pg_describe_object(PG_FUNCTION_ARGS);
--- 1147,1155 ----
/* 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/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
***************
*** 16,21 ****
--- 16,22 ----
#include "access/attnum.h"
#include "access/htup.h"
#include "nodes/pg_list.h"
+ #include "utils/relcache.h"
/* Result list element for get_op_btree_interpretation */
typedef struct OpBtreeInterpretation
***************
*** 152,157 **** extern void free_attstatsslot(Oid atttype,
--- 153,159 ----
float4 *numbers, int nnumbers);
extern char *get_namespace_name(Oid nspid);
extern Oid get_range_subtype(Oid rangeOid);
+ extern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);
#define type_is_array(typid) (get_element_type(typid) != InvalidOid)
/* type_is_array_domain accepts both plain arrays and domains over arrays */
Alvaro Herrera escribió:
I think this is mostly ready to go in. I'll look at your docs, and
unless there are more objections will commit later or early tomorrow.
Actually it still needs a bit more work: the error messages in
pg_event_trigger_dropped_object need to be reworked. It's a bit
annoying that the function throws an error if the function is called in
a CREATE command, rather than returning an empty set; or is it just me?
Here's v6 with docs and regression tests too. Note the new function in
objectaddress.c; without that, I was getting regression failures because
catalogs such as pg_amop and pg_default_acl are not present in its
supporting table.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.6.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 71241c8..188f6ee 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -46,6 +46,15 @@
</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. 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,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9b7e967..3802a1a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15702,4 +15702,46 @@ FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
<xref linkend="SQL-CREATETRIGGER">.
</para>
</sect1>
+
+ <sect1 id="functions-event-trigger">
+ <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 a <literal>DROP</> command.
+ </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_sql_drop()
+ 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;
+ END LOOP;
+END
+$$;
+</programlisting>
+ </para>
+
+ <para>
+ For more information about event triggers,
+ see <xref linkend="event-triggers">.
+ </para>
+ </sect1>
+
</chapter>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 32f05bb..35eaa0f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -198,6 +198,7 @@ static bool stack_address_present_add_flags(const ObjectAddress *object,
ObjectAddressStack *stack);
static void getRelationDescription(StringInfo buffer, Oid relid);
static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationTypeDescription(StringInfo buffer, Oid relid);
/*
@@ -267,6 +268,12 @@ performDeletion(const ObjectAddress *object,
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
@@ -349,6 +356,12 @@ performMultipleDeletions(const ObjectAddresses *objects,
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
@@ -366,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,
@@ -3107,3 +3124,195 @@ pg_describe_object(PG_FUNCTION_ARGS)
description = getObjectDescription(&address);
PG_RETURN_TEXT_P(cstring_to_text(description));
}
+
+char *
+getObjectTypeDescription(const ObjectAddress *object)
+{
+ StringInfoData buffer;
+
+ initStringInfo(&buffer);
+
+ switch (getObjectClass(object))
+ {
+ case OCLASS_CLASS:
+ getRelationTypeDescription(&buffer, object->objectId);
+ break;
+
+ case OCLASS_PROC:
+ appendStringInfo(&buffer, "function");
+ break;
+
+ case OCLASS_TYPE:
+ appendStringInfo(&buffer, "type");
+ break;
+
+ case OCLASS_CAST:
+ appendStringInfo(&buffer, "cast");
+ break;
+
+ case OCLASS_COLLATION:
+ appendStringInfo(&buffer, "collation");
+ break;
+
+ case OCLASS_CONSTRAINT:
+ appendStringInfo(&buffer, "constraint");
+ break;
+
+ case OCLASS_CONVERSION:
+ appendStringInfo(&buffer, "conversion");
+ break;
+
+ case OCLASS_DEFAULT:
+ appendStringInfo(&buffer, "default value");
+ break;
+
+ case OCLASS_LANGUAGE:
+ appendStringInfo(&buffer, "language");
+ break;
+
+ case OCLASS_LARGEOBJECT:
+ appendStringInfo(&buffer, "large object");
+ break;
+
+ case OCLASS_OPERATOR:
+ appendStringInfo(&buffer, "operator");
+ break;
+
+ case OCLASS_OPCLASS:
+ appendStringInfo(&buffer, "operator class");
+ break;
+
+ case OCLASS_OPFAMILY:
+ appendStringInfo(&buffer, "operator family");
+ break;
+
+ case OCLASS_AMOP:
+ appendStringInfo(&buffer, "operator of access method");
+ break;
+
+ case OCLASS_AMPROC:
+ appendStringInfo(&buffer, "function of access method");
+ break;
+
+ case OCLASS_REWRITE:
+ appendStringInfo(&buffer, "rule");
+ break;
+
+ case OCLASS_TRIGGER:
+ appendStringInfo(&buffer, "trigger");
+ break;
+
+ case OCLASS_SCHEMA:
+ appendStringInfo(&buffer, "schema");
+ break;
+
+ case OCLASS_TSPARSER:
+ appendStringInfo(&buffer, "text search parser");
+ break;
+
+ case OCLASS_TSDICT:
+ appendStringInfo(&buffer, "text search dictionary");
+ break;
+
+ case OCLASS_TSTEMPLATE:
+ appendStringInfo(&buffer, "text search template");
+ break;
+
+ case OCLASS_TSCONFIG:
+ appendStringInfo(&buffer, "text search configuration");
+ break;
+
+ case OCLASS_ROLE:
+ appendStringInfo(&buffer, "role");
+ break;
+
+ case OCLASS_DATABASE:
+ appendStringInfo(&buffer, "database");
+ break;
+
+ case OCLASS_TBLSPACE:
+ appendStringInfo(&buffer, "tablespace");
+ break;
+
+ case OCLASS_FDW:
+ appendStringInfo(&buffer, "foreign-data wrapper");
+ break;
+
+ case OCLASS_FOREIGN_SERVER:
+ appendStringInfo(&buffer, "server");
+ break;
+
+ case OCLASS_USER_MAPPING:
+ appendStringInfo(&buffer, "user mapping");
+ break;
+
+ case OCLASS_DEFACL:
+ /* XXX do we need more detail here? */
+ appendStringInfo(&buffer, "default ACL");
+ break;
+
+ case OCLASS_EXTENSION:
+ appendStringInfo(&buffer, "extension");
+ break;
+
+ case OCLASS_EVENT_TRIGGER:
+ appendStringInfo(&buffer, "event trigger");
+ break;
+
+ default:
+ appendStringInfo(&buffer, "unrecognized object type");
+ break;
+ }
+
+ return buffer.data;
+}
+
+/*
+ * subroutine for getObjectTypeDescription: describe a relation type
+ */
+static void
+getRelationTypeDescription(StringInfo buffer, Oid relid)
+{
+ HeapTuple relTup;
+ Form_pg_class relForm;
+
+ relTup = SearchSysCache1(RELOID,
+ ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+ switch (relForm->relkind)
+ {
+ case RELKIND_RELATION:
+ appendStringInfo(buffer, "table");
+ break;
+ case RELKIND_INDEX:
+ appendStringInfo(buffer, "index");
+ break;
+ case RELKIND_SEQUENCE:
+ appendStringInfo(buffer, "sequence");
+ break;
+ case RELKIND_TOASTVALUE:
+ appendStringInfo(buffer, "toast table");
+ break;
+ case RELKIND_VIEW:
+ appendStringInfo(buffer, "view");
+ break;
+ case RELKIND_MATVIEW:
+ appendStringInfo(buffer, "materialized view");
+ break;
+ case RELKIND_COMPOSITE_TYPE:
+ appendStringInfo(buffer, "composite type");
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ appendStringInfo(buffer, "foreign table");
+ break;
+ default:
+ /* shouldn't get here */
+ appendStringInfo(buffer, "relation");
+ break;
+ }
+
+ ReleaseSysCache(relTup);
+}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6f60d7c..04f6893 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1345,6 +1345,24 @@ get_object_aclkind(Oid class_id)
}
/*
+ * Return whether we have useful data for the given object class in the
+ * ObjectProperty table.
+ */
+bool
+is_objectclass_supported(Oid class_id)
+{
+ int index;
+
+ for (index = 0; index < lengthof(ObjectProperty); index++)
+ {
+ if (ObjectProperty[index].class_oid == class_id)
+ return true;
+ }
+
+ return false;
+}
+
+/*
* Find ObjectProperty structure by class_id.
*/
static ObjectPropertyType *
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 416a068..8a88c91 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -748,58 +748,6 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
}
/*
- * Return a copy of the tuple for the object with the given object OID, from
- * the given catalog (which must have been opened by the caller and suitably
- * locked). NULL is returned if the OID is not found.
- *
- * We try a syscache first, if available.
- *
- * XXX this function seems general in possible usage. Given sufficient callers
- * elsewhere, we should consider moving it to a more appropriate place.
- */
-static HeapTuple
-get_catalog_object_by_oid(Relation catalog, Oid objectId)
-{
- HeapTuple tuple;
- Oid classId = RelationGetRelid(catalog);
- int oidCacheId = get_object_catcache_oid(classId);
-
- if (oidCacheId > 0)
- {
- tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
- if (!HeapTupleIsValid(tuple)) /* should not happen */
- return NULL;
- }
- else
- {
- Oid oidIndexId = get_object_oid_index(classId);
- SysScanDesc scan;
- ScanKeyData skey;
-
- Assert(OidIsValid(oidIndexId));
-
- ScanKeyInit(&skey,
- ObjectIdAttributeNumber,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(objectId));
-
- scan = systable_beginscan(catalog, oidIndexId, true,
- SnapshotNow, 1, &skey);
- tuple = systable_getnext(scan);
- if (!HeapTupleIsValid(tuple))
- {
- systable_endscan(scan);
- return NULL;
- }
- tuple = heap_copytuple(tuple);
-
- systable_endscan(scan);
- }
-
- return tuple;
-}
-
-/*
* Generic function to change the ownership of a given object, for simple
* cases (won't work for tables, nor other cases where we need to do more than
* change the ownership column of a single catalog entry).
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 596178f..ed63f67 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 evtrig_sqldrop_inprogress = false;
+slist_head SQLDropList = SLIST_STATIC_INIT(SQLDropList);
+
typedef struct
{
const char *obtypename;
@@ -89,6 +94,16 @@ static event_trigger_support_data event_trigger_support[] = {
{ NULL, false }
};
+/* Support for dropped objects */
+typedef struct SQLDropObject
+{
+ ObjectAddress address;
+ char *objname;
+ char *schemaname;
+ char *objecttype;
+ slist_node next;
+} SQLDropObject;
+
static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup,
Oid newOwnerId);
@@ -151,8 +166,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
@@ -220,7 +239,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;
/*
@@ -827,3 +847,212 @@ EventTriggerSupportsObjectType(ObjectType obtype)
}
return true;
}
+
+/*
+ * Support for dropped objects information on event trigger functions.
+ *
+ * We keep the list of objects dropped by the current command in a list of
+ * these structs. Each command that might drop objects saves the current
+ * list in a local variable, initialize a new empty list and do the dependency.c
+ * dance to drop objects, which populates the list; when the event triggers are
+ * invoked they can consume the list via pg_event_trigger_dropped_objects().
+ * When the command finishes, the list is cleared and the original list is
+ * restored. This is to support the case that an event trigger function drops
+ * objects "reentrantly".
+ *
+ * For each object dropped, we save the below info, which can be obtained as a
+ * set via the pg_event_trigger_dropped_objects() SQL-callable function.
+ */
+
+/*
+ * Initialize state of objects dropped
+ */
+void
+EventTriggerInitializeDrop(bool *save_inprogress, slist_head *save_objlist)
+{
+ /* save previous state in local vars of caller, for later restore */
+ *save_inprogress = evtrig_sqldrop_inprogress;
+ *save_objlist = SQLDropList;
+
+ evtrig_sqldrop_inprogress = true;
+ slist_init(&SQLDropList);
+}
+
+/*
+ * Restore state after running a command that drops objects; free memory from a
+ * list we may have created.
+ */
+void
+EventTriggerFinalizeDrop(bool save_inprogress, slist_head save_objlist)
+{
+ slist_mutable_iter iter;
+
+ slist_foreach_modify(iter, &SQLDropList)
+ {
+ SQLDropObject *obj = slist_container(SQLDropObject, next, iter.cur);
+
+ if (obj->objname)
+ pfree(obj->objname);
+ if (obj->schemaname)
+ pfree(obj->schemaname);
+ pfree(obj);
+ }
+
+ evtrig_sqldrop_inprogress = save_inprogress;
+ SQLDropList = save_objlist;
+}
+
+/*
+ * Register one object as being dropped by the current command.
+ *
+ * XXX do we need to think about memory context these things are stored in?
+ */
+void
+evtrig_sqldrop_add_object(ObjectAddress *object)
+{
+ SQLDropObject *obj;
+
+ Assert(EventTriggerSupportsObjectType(getObjectClass(object)));
+
+ obj = palloc0(sizeof(SQLDropObject));
+ obj->address = *object;
+
+ /*
+ * Obtain object and schema names from the object's catalog tuple, if one
+ * exists.
+ */
+ if (is_objectclass_supported(obj->address.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_name(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ obj->objname = pstrdup(NameStr(*DatumGetName(datum)));
+ }
+
+ attnum = get_object_attnum_namespace(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ obj->schemaname = get_namespace_name(DatumGetObjectId(datum));
+ }
+ }
+
+ heap_close(catalog, AccessShareLock);
+ }
+
+ /* and object type, too */
+ obj->objecttype = getObjectTypeDescription(&obj->address);
+
+ slist_push_head(&SQLDropList, &obj->next);
+}
+
+/*
+ * 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;
+
+ /*
+ * This function is meant to be called from within an event trigger in
+ * order to get the list of objects dropped, if any.
+ */
+ if (!evtrig_sqldrop_inprogress)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s can only be called from an 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, &SQLDropList)
+ {
+ SQLDropObject *obj;
+ 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[0] = ObjectIdGetDatum(obj->address.classId);
+
+ /* objid */
+ values[1] = ObjectIdGetDatum(obj->address.objectId);
+
+ /* objsubid */
+ values[2] = Int32GetDatum(obj->address.objectSubId);
+
+ /* object type */
+ values[3] = CStringGetTextDatum(obj->objecttype);
+
+ /* objname */
+ if (obj->objname)
+ values[4] = CStringGetTextDatum(obj->objname);
+ else
+ nulls[4] = true;
+
+ /* schemaname */
+ if (obj->schemaname)
+ values[5] = CStringGetTextDatum(obj->schemaname);
+ else
+ nulls[5] = 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 a1c03f1..537dc5c 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -699,34 +699,61 @@ standard_ProcessUtility(Node *parsetree,
case T_DropStmt:
{
DropStmt *stmt = (DropStmt *) parsetree;
+ bool save_inprogress;
+ slist_head save_objlist;
- 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);
- switch (stmt->removeType)
+ EventTriggerInitializeDrop(&save_inprogress, &save_objlist);
+ }
+
+ PG_TRY();
{
- case OBJECT_INDEX:
- if (stmt->concurrent)
- PreventTransactionChain(isTopLevel,
- "DROP INDEX CONCURRENTLY");
- /* fall through */
+ switch (stmt->removeType)
+ {
+ case OBJECT_INDEX:
+ case OBJECT_TABLE:
+ case OBJECT_SEQUENCE:
+ case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
+ case OBJECT_FOREIGN_TABLE:
+ RemoveRelations((DropStmt *) parsetree);
+ break;
+ default:
+ RemoveObjects((DropStmt *) parsetree);
+ break;
+ }
- case OBJECT_TABLE:
- case OBJECT_SEQUENCE:
- case OBJECT_VIEW:
- case OBJECT_MATVIEW:
- case OBJECT_FOREIGN_TABLE:
- RemoveRelations((DropStmt *) parsetree);
- break;
- default:
- RemoveObjects((DropStmt *) parsetree);
- break;
+ if (isCompleteQuery
+ && EventTriggerSupportsObjectType(stmt->removeType))
+ {
+ EventTriggerDDLCommandEnd(parsetree);
+ }
+ }
+ PG_CATCH();
+ {
+ if (isCompleteQuery
+ && EventTriggerSupportsObjectType(stmt->removeType))
+ EventTriggerFinalizeDrop(save_inprogress, save_objlist);
+
+ PG_RE_THROW();
}
+ PG_END_TRY();
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
- EventTriggerDDLCommandEnd(parsetree);
+ EventTriggerFinalizeDrop(save_inprogress, save_objlist);
break;
}
@@ -1248,9 +1275,37 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_DropOwnedStmt:
- /* no event triggers for global objects */
- DropOwnedObjects((DropOwnedStmt *) parsetree);
- break;
+ {
+ bool save_inprogress;
+ slist_head save_objlist;
+
+ if (isCompleteQuery)
+ {
+ EventTriggerDDLCommandStart(parsetree);
+
+ EventTriggerInitializeDrop(&save_inprogress, &save_objlist);
+ }
+
+ PG_TRY();
+ {
+ DropOwnedObjects((DropOwnedStmt *) parsetree);
+
+ if (isCompleteQuery)
+ EventTriggerDDLCommandEnd(parsetree);
+ }
+ PG_CATCH();
+ {
+ if (isCompleteQuery)
+ EventTriggerFinalizeDrop(save_inprogress, save_objlist);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ if (isCompleteQuery)
+ EventTriggerFinalizeDrop(save_inprogress, save_objlist);
+
+ break;
+ }
case T_ReassignOwnedStmt:
/* no event triggers for global objects */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 5865962..7f25de4 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -18,6 +18,7 @@
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
@@ -40,6 +41,7 @@
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
+#include "utils/tqual.h"
#include "utils/typcache.h"
/* Hook for plugins to get control in get_attavgwidth() */
@@ -2926,3 +2928,54 @@ get_range_subtype(Oid rangeOid)
else
return InvalidOid;
}
+
+/* ------------- GENERIC -------------- */
+
+/*
+ * Return a copy of the tuple for the object with the given object OID, from
+ * the given catalog (which must have been opened by the caller and suitably
+ * locked). NULL is returned if the OID is not found.
+ *
+ * We try a syscache first, if available.
+ */
+HeapTuple
+get_catalog_object_by_oid(Relation catalog, Oid objectId)
+{
+ HeapTuple tuple;
+ Oid classId = RelationGetRelid(catalog);
+ int oidCacheId = get_object_catcache_oid(classId);
+
+ if (oidCacheId > 0)
+ {
+ tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ return NULL;
+ }
+ else
+ {
+ Oid oidIndexId = get_object_oid_index(classId);
+ SysScanDesc scan;
+ ScanKeyData skey;
+
+ Assert(OidIsValid(oidIndexId));
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(catalog, oidIndexId, true,
+ SnapshotNow, 1, &skey);
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ {
+ systable_endscan(scan);
+ return NULL;
+ }
+ tuple = heap_copytuple(tuple);
+
+ systable_endscan(scan);
+ }
+
+ return tuple;
+}
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8e0837f..106d8fb 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -179,6 +179,8 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
extern char *getObjectDescription(const ObjectAddress *object);
extern char *getObjectDescriptionOids(Oid classid, Oid objid);
+extern char *getObjectTypeDescription(const ObjectAddress *object);
+
extern ObjectAddresses *new_object_addresses(void);
extern void add_exact_object_address(const ObjectAddress *object,
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index ffaf4ea..8a34b50 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -38,6 +38,7 @@ extern void check_object_ownership(Oid roleid,
extern Oid get_object_namespace(const ObjectAddress *address);
+extern bool is_objectclass_supported(Oid class_id);
extern Oid get_object_oid_index(Oid class_id);
extern int get_object_catcache_oid(Oid class_id);
extern int get_object_catcache_name(Oid class_id);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 0e26ebf..6e07102 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4682,6 +4682,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,26,25,25,25}" "{o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, object_name, schema_name}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
+DESCR("list objects dropped by the current command");
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 74c150b..2e7d815 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -13,7 +13,10 @@
#ifndef EVENT_TRIGGER_H
#define EVENT_TRIGGER_H
+#include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_event_trigger.h"
+#include "lib/ilist.h"
#include "nodes/parsenodes.h"
typedef struct EventTriggerData
@@ -43,4 +46,10 @@ extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
+extern void EventTriggerInitializeDrop(bool *save_inprogress,
+ slist_head *save_objlist);
+extern void EventTriggerFinalizeDrop(bool save_inprogress,
+ slist_head save_objlist);
+extern void evtrig_sqldrop_add_object(ObjectAddress *object);
+
#endif /* EVENT_TRIGGER_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index c0debe4..785d1de 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1147,6 +1147,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/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 49f459a..dfa6eb7 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -16,6 +16,7 @@
#include "access/attnum.h"
#include "access/htup.h"
#include "nodes/pg_list.h"
+#include "utils/relcache.h"
/* Result list element for get_op_btree_interpretation */
typedef struct OpBtreeInterpretation
@@ -152,6 +153,7 @@ extern void free_attstatsslot(Oid atttype,
float4 *numbers, int nnumbers);
extern char *get_namespace_name(Oid nspid);
extern Oid get_range_subtype(Oid rangeOid);
+extern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);
#define type_is_array(typid) (get_element_type(typid) != InvalidOid)
/* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index bf020de..0bf0791 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -93,11 +93,134 @@ 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
+-- cleanup before next test
+-- these are all OK; the third one should emit a NOTICE
+drop event trigger if exists regress_event_trigger_noperms;
+NOTICE: event trigger "regress_event_trigger_noperms" does not exist, skipping
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();
+\dy
+ List of event triggers
+ Name | Event | Owner | Enabled | Procedure | Tags
+------+-------+-------+---------+-----------+------
+(0 rows)
+
+-- 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);
+RESET SESSION AUTHORIZATION;
+CREATE TABLE dropped_objects (type text,
+ schema text,
+ object 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_name);
+ END IF;
+
+ INSERT INTO dropped_objects
+ (type, schema, object, curr_user, sess_user) VALUES
+ (obj.object_type, obj.schema_name, obj.object_name,
+ 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')
+ EXECUTE PROCEDURE test_evtrig_dropped_objects();
+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 table audit_tbls.table_three column the_value
+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_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
+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_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 | curr_user | sess_user
+--------+------------+--------------+-----------+-----------
+ schema | | schema_two | alvherre | alvherre
+ table | audit_tbls | table_two | alvherre | alvherre
+ type | audit_tbls | table_two | alvherre | alvherre
+ type | audit_tbls | _table_two | alvherre | alvherre
+ table | schema_two | table_two | alvherre | alvherre
+ type | schema_two | table_two | alvherre | alvherre
+ type | schema_two | _table_two | alvherre | alvherre
+ table | audit_tbls | table_three | alvherre | alvherre
+ type | audit_tbls | table_three | alvherre | alvherre
+ type | audit_tbls | _table_three | alvherre | alvherre
+ table | schema_two | table_three | alvherre | alvherre
+ type | schema_two | table_three | alvherre | alvherre
+ type | schema_two | _table_three | alvherre | alvherre
+ table | audit_tbls | table_three | alvherre | alvherre
+ schema | | schema_one | alvherre | alvherre
+ table | schema_one | table_one | alvherre | alvherre
+ type | schema_one | table_one | alvherre | alvherre
+ type | schema_one | _table_one | alvherre | alvherre
+ table | schema_one | table_two | alvherre | alvherre
+ type | schema_one | table_two | alvherre | alvherre
+ type | schema_one | _table_two | alvherre | alvherre
+ table | audit_tbls | table_two | alvherre | alvherre
+ table | schema_one | table_three | alvherre | alvherre
+ type | schema_one | table_three | alvherre | alvherre
+ type | schema_one | _table_three | alvherre | alvherre
+(25 rows)
+
+drop owned by regression_bob;
+SELECT * FROM dropped_objects WHERE type = 'schema';
+ type | schema | object | 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;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index a07dcd7..faad1b4 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -97,10 +97,70 @@ 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
+-- cleanup before next test
+-- these are all OK; the third one should emit a NOTICE
+drop event trigger if exists regress_event_trigger_noperms;
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();
+\dy
+
+-- 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);
+
+RESET SESSION AUTHORIZATION;
+
+CREATE TABLE dropped_objects (type text,
+ schema text,
+ object 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_name);
+ END IF;
+
+ INSERT INTO dropped_objects
+ (type, schema, object, curr_user, sess_user) VALUES
+ (obj.object_type, obj.schema_name, obj.object_name,
+ 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')
+ EXECUTE PROCEDURE test_evtrig_dropped_objects();
+
+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;
On Mon, Mar 4, 2013 at 11:13 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Another question. If I do ALTER TABLE foo DROP COLUMN bar, do we need
to fire an event trigger for the dropped column? Right now we don't,
ISTM we should. And if we want that, then the above set of three
properties doesn't cut it.
+1. Similar questions arise for object-access-hooks, among other places.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Mar 4, 2013 at 4:59 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Alvaro Herrera escribió:
I think this is mostly ready to go in. I'll look at your docs, and
unless there are more objections will commit later or early tomorrow.Actually it still needs a bit more work: the error messages in
pg_event_trigger_dropped_object need to be reworked. It's a bit
annoying that the function throws an error if the function is called in
a CREATE command, rather than returning an empty set; or is it just me?
That seems like a reasonable thing to change.
Here's v6 with docs and regression tests too. Note the new function in
objectaddress.c; without that, I was getting regression failures because
catalogs such as pg_amop and pg_default_acl are not present in its
supporting table.
I agree that this is reasonably close to being committable. I do have
a bit of concern about the save-and-restore logic for the
dropped-object list -- it necessitates that the processing of every
DDL command that can potentially drop objects be bracketed with a
PG_TRY/PG_CATCH block. While that's relatively easy to guarantee
today (but doesn't ALTER TABLE need similar handling?), it seems to me
that it might get broken in the future. How hard would it be to pass
this information up and down the call stack instead of doing it this
way? Or at least move the save/restore logic into something inside
the deletion machinery itself so that new callers don't have to worry
about it?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Mon, Mar 4, 2013 at 4:59 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Alvaro Herrera escribió:
I think this is mostly ready to go in. I'll look at your docs, and
unless there are more objections will commit later or early tomorrow.Actually it still needs a bit more work: the error messages in
pg_event_trigger_dropped_object need to be reworked. It's a bit
annoying that the function throws an error if the function is called in
a CREATE command, rather than returning an empty set; or is it just me?That seems like a reasonable thing to change.
I'm thinking just removing the check altogether, and if the list of
objects dropped is empty, then the empty set is returned. That is, if
you call the function directly in user-invoked SQL, you get empty; if
you call the function in a CREATE event trigger function, you get empty.
Since this makes the boolean "drop_in_progress" useless, it'd be removed
as well.
I do have
a bit of concern about the save-and-restore logic for the
dropped-object list -- it necessitates that the processing of every
DDL command that can potentially drop objects be bracketed with a
PG_TRY/PG_CATCH block. While that's relatively easy to guarantee
today (but doesn't ALTER TABLE need similar handling?), it seems to me
that it might get broken in the future. How hard would it be to pass
this information up and down the call stack instead of doing it this
way?
This would be rather messy, I think; there are too many layers, and too
many different functions.
Or at least move the save/restore logic into something inside the
deletion machinery itself so that new callers don't have to worry
about it?
Hmm, maybe this can be made to work, I'll see about it.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
Or at least move the save/restore logic into something inside the
deletion machinery itself so that new callers don't have to worry
about it?
I don't think that works well; precisely the point of the
initialize/finalize pair of functions is to bracket the drop so that the
objects reported by the deletion machinery are stored in the right list.
I tried this macro:
/*
* Wrap a code fragment that executes a command that may drop database objects,
* so that the event trigger environment is appropriately setup.
*
* Note this macro will call EventTriggerDDLCommandEnd if the object type is
* supported; caller must make sure to call EventTriggerDDLCommandStart by
* itself.
*/
#define ExecuteDropCommand(isCompleteQuery, codeFragment, has_objtype, objtype) \
do { \
slist_head _save_objlist; \
bool _supported; \
\
_supported = has_objtype ? EventTriggerSupportsObjectType(objtype) : true; \
\
if (isCompleteQuery) \
{ \
EventTriggerInitializeDrop(&_save_objlist); \
} \
PG_TRY(); \
{ \
codeFragment; \
if (isCompleteQuery && _supported) \
{ \
EventTriggerDDLCommandEnd(parsetree); \
} \
} \
PG_CATCH(); \
{ \
if (isCompleteQuery && _supported) \
{ \
EventTriggerFinalizeDrop(_save_objlist); \
} \
PG_RE_THROW(); \
} \
PG_END_TRY(); \
EventTriggerFinalizeDrop(_save_objlist); \
} while (0)
This looks nice in DropOwned:
case T_DropOwnedStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecuteDropCommand(isCompleteQuery,
DropOwnedObjects((DropOwnedStmt *) parsetree),
false, 0);
break;
And it works for DropStmt too:
ExecuteDropCommand(isCompleteQuery,
switch (stmt->removeType)
{
case OBJECT_INDEX:
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_MATVIEW:
case OBJECT_FOREIGN_TABLE:
RemoveRelations((DropStmt *) parsetree);
break;
default:
RemoveObjects((DropStmt *) parsetree);
break;
}, true, stmt->removeType);
but a rather serious problem is that pgindent refuses to work completely
on this (which is understandable, IMV). My editor doesn't like the
braces inside something that looks like a function call, either. We use
this pattern (a codeFragment being called by a macro) as
ProcessMessageList in inval.c, but the code fragments there are much
simpler.
And in AlterTable, using the macro would be much uglier because the code
fragment is more convoluted.
I'm not really sure if it's worth having the above macro if the only
caller is DropOwned.
Hmm, maybe I should be considering a pair of macros instead --
UTILITY_START_DROP and UTILITY_END_DROP. I'll give this a try. Other
ideas are welcome.
FWIW, I noticed that the AlterTable case lacks a call to DDLCommandEnd;
reporting that to Dimitri led to him noticing that DefineStmt also lacks
one. This is a simple bug in the already-committed implementation which
should probably be fixed separately from this patch.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera escribió:
Hmm, maybe I should be considering a pair of macros instead --
UTILITY_START_DROP and UTILITY_END_DROP. I'll give this a try. Other
ideas are welcome.
This seems to work. See attached; I like the result because there's no
clutter and it supports all three cases without a problem.
I also added a new output column to pg_event_trigger_dropped_objects,
"subobject_name", which is NULL except when a column is being dropped,
in which case it contains the column name. Also, the "object type"
column now says "table column" instead of "table" when dropping a
column.
Another question arose in testing: this reports dropping of temp
objects, too, but of course not always: particularly not when temp
objects are dropped at the end of a session (or the beginning of a
session that reuses a previously used temp schema). I find this rather
inconsistent and I wonder if we should instead suppress reporting of
temp objects.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.7.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 71241c8..1e67d86 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -36,8 +36,8 @@
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 — 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,6 +46,16 @@
</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,6 +443,11 @@
<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>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9b7e967..5721d1b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15702,4 +15702,54 @@ 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</>, 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>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 32f05bb..a91111b 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -198,6 +198,7 @@ static bool stack_address_present_add_flags(const ObjectAddress *object,
ObjectAddressStack *stack);
static void getRelationDescription(StringInfo buffer, Oid relid);
static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId);
/*
@@ -267,6 +268,12 @@ performDeletion(const ObjectAddress *object,
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
@@ -349,6 +356,12 @@ performMultipleDeletions(const ObjectAddresses *objects,
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
@@ -366,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,
@@ -3107,3 +3124,199 @@ pg_describe_object(PG_FUNCTION_ARGS)
description = getObjectDescription(&address);
PG_RETURN_TEXT_P(cstring_to_text(description));
}
+
+char *
+getObjectTypeDescription(const ObjectAddress *object)
+{
+ StringInfoData buffer;
+
+ initStringInfo(&buffer);
+
+ switch (getObjectClass(object))
+ {
+ case OCLASS_CLASS:
+ getRelationTypeDescription(&buffer, object->objectId,
+ object->objectSubId);
+ break;
+
+ case OCLASS_PROC:
+ appendStringInfo(&buffer, "function");
+ break;
+
+ case OCLASS_TYPE:
+ appendStringInfo(&buffer, "type");
+ break;
+
+ case OCLASS_CAST:
+ appendStringInfo(&buffer, "cast");
+ break;
+
+ case OCLASS_COLLATION:
+ appendStringInfo(&buffer, "collation");
+ break;
+
+ case OCLASS_CONSTRAINT:
+ appendStringInfo(&buffer, "constraint");
+ break;
+
+ case OCLASS_CONVERSION:
+ appendStringInfo(&buffer, "conversion");
+ break;
+
+ case OCLASS_DEFAULT:
+ appendStringInfo(&buffer, "default value");
+ break;
+
+ case OCLASS_LANGUAGE:
+ appendStringInfo(&buffer, "language");
+ break;
+
+ case OCLASS_LARGEOBJECT:
+ appendStringInfo(&buffer, "large object");
+ break;
+
+ case OCLASS_OPERATOR:
+ appendStringInfo(&buffer, "operator");
+ break;
+
+ case OCLASS_OPCLASS:
+ appendStringInfo(&buffer, "operator class");
+ break;
+
+ case OCLASS_OPFAMILY:
+ appendStringInfo(&buffer, "operator family");
+ break;
+
+ case OCLASS_AMOP:
+ appendStringInfo(&buffer, "operator of access method");
+ break;
+
+ case OCLASS_AMPROC:
+ appendStringInfo(&buffer, "function of access method");
+ break;
+
+ case OCLASS_REWRITE:
+ appendStringInfo(&buffer, "rule");
+ break;
+
+ case OCLASS_TRIGGER:
+ appendStringInfo(&buffer, "trigger");
+ break;
+
+ case OCLASS_SCHEMA:
+ appendStringInfo(&buffer, "schema");
+ break;
+
+ case OCLASS_TSPARSER:
+ appendStringInfo(&buffer, "text search parser");
+ break;
+
+ case OCLASS_TSDICT:
+ appendStringInfo(&buffer, "text search dictionary");
+ break;
+
+ case OCLASS_TSTEMPLATE:
+ appendStringInfo(&buffer, "text search template");
+ break;
+
+ case OCLASS_TSCONFIG:
+ appendStringInfo(&buffer, "text search configuration");
+ break;
+
+ case OCLASS_ROLE:
+ appendStringInfo(&buffer, "role");
+ break;
+
+ case OCLASS_DATABASE:
+ appendStringInfo(&buffer, "database");
+ break;
+
+ case OCLASS_TBLSPACE:
+ appendStringInfo(&buffer, "tablespace");
+ break;
+
+ case OCLASS_FDW:
+ appendStringInfo(&buffer, "foreign-data wrapper");
+ break;
+
+ case OCLASS_FOREIGN_SERVER:
+ appendStringInfo(&buffer, "server");
+ break;
+
+ case OCLASS_USER_MAPPING:
+ appendStringInfo(&buffer, "user mapping");
+ break;
+
+ case OCLASS_DEFACL:
+ /* XXX do we need more detail here? */
+ appendStringInfo(&buffer, "default ACL");
+ break;
+
+ case OCLASS_EXTENSION:
+ appendStringInfo(&buffer, "extension");
+ break;
+
+ case OCLASS_EVENT_TRIGGER:
+ appendStringInfo(&buffer, "event trigger");
+ break;
+
+ default:
+ appendStringInfo(&buffer, "unrecognized object type");
+ break;
+ }
+
+ return buffer.data;
+}
+
+/*
+ * subroutine for getObjectTypeDescription: describe a relation type
+ */
+static void
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+{
+ HeapTuple relTup;
+ Form_pg_class relForm;
+
+ relTup = SearchSysCache1(RELOID,
+ ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+ switch (relForm->relkind)
+ {
+ case RELKIND_RELATION:
+ appendStringInfo(buffer, "table");
+ break;
+ case RELKIND_INDEX:
+ appendStringInfo(buffer, "index");
+ break;
+ case RELKIND_SEQUENCE:
+ appendStringInfo(buffer, "sequence");
+ break;
+ case RELKIND_TOASTVALUE:
+ appendStringInfo(buffer, "toast table");
+ break;
+ case RELKIND_VIEW:
+ appendStringInfo(buffer, "view");
+ break;
+ case RELKIND_MATVIEW:
+ appendStringInfo(buffer, "materialized view");
+ break;
+ case RELKIND_COMPOSITE_TYPE:
+ appendStringInfo(buffer, "composite type");
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ appendStringInfo(buffer, "foreign table");
+ break;
+ default:
+ /* shouldn't get here */
+ appendStringInfo(buffer, "relation");
+ break;
+ }
+
+ if (objectSubId != 0)
+ appendStringInfo(buffer, " column");
+
+ ReleaseSysCache(relTup);
+}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6f60d7c..04f6893 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1345,6 +1345,24 @@ get_object_aclkind(Oid class_id)
}
/*
+ * Return whether we have useful data for the given object class in the
+ * ObjectProperty table.
+ */
+bool
+is_objectclass_supported(Oid class_id)
+{
+ int index;
+
+ for (index = 0; index < lengthof(ObjectProperty); index++)
+ {
+ if (ObjectProperty[index].class_oid == class_id)
+ return true;
+ }
+
+ return false;
+}
+
+/*
* Find ObjectProperty structure by class_id.
*/
static ObjectPropertyType *
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 416a068..8a88c91 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -748,58 +748,6 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
}
/*
- * Return a copy of the tuple for the object with the given object OID, from
- * the given catalog (which must have been opened by the caller and suitably
- * locked). NULL is returned if the OID is not found.
- *
- * We try a syscache first, if available.
- *
- * XXX this function seems general in possible usage. Given sufficient callers
- * elsewhere, we should consider moving it to a more appropriate place.
- */
-static HeapTuple
-get_catalog_object_by_oid(Relation catalog, Oid objectId)
-{
- HeapTuple tuple;
- Oid classId = RelationGetRelid(catalog);
- int oidCacheId = get_object_catcache_oid(classId);
-
- if (oidCacheId > 0)
- {
- tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
- if (!HeapTupleIsValid(tuple)) /* should not happen */
- return NULL;
- }
- else
- {
- Oid oidIndexId = get_object_oid_index(classId);
- SysScanDesc scan;
- ScanKeyData skey;
-
- Assert(OidIsValid(oidIndexId));
-
- ScanKeyInit(&skey,
- ObjectIdAttributeNumber,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(objectId));
-
- scan = systable_beginscan(catalog, oidIndexId, true,
- SnapshotNow, 1, &skey);
- tuple = systable_getnext(scan);
- if (!HeapTupleIsValid(tuple))
- {
- systable_endscan(scan);
- return NULL;
- }
- tuple = heap_copytuple(tuple);
-
- systable_endscan(scan);
- }
-
- return tuple;
-}
-
-/*
* Generic function to change the ownership of a given object, for simple
* cases (won't work for tables, nor other cases where we need to do more than
* change the ownership column of a single catalog entry).
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 596178f..d467fb8 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 -- */
+/* list of objects dropped by current command */
+slist_head SQLDropList = SLIST_STATIC_INIT(SQLDropList);
+
typedef struct
{
const char *obtypename;
@@ -89,6 +94,17 @@ static event_trigger_support_data event_trigger_support[] = {
{ NULL, false }
};
+/* Support for dropped objects */
+typedef struct SQLDropObject
+{
+ ObjectAddress address;
+ char *objname;
+ char *subobjname;
+ char *schemaname;
+ char *objecttype;
+ slist_node next;
+} SQLDropObject;
+
static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup,
Oid newOwnerId);
@@ -151,8 +167,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
@@ -220,7 +240,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;
/*
@@ -827,3 +848,215 @@ EventTriggerSupportsObjectType(ObjectType obtype)
}
return true;
}
+
+/*
+ * Support for dropped objects information on event trigger functions.
+ *
+ * We keep the list of objects dropped by the current command in SQLDropList
+ * (comprising SQLDropObject items). Each command that might drop objects
+ * saves the current list in a local variable, initializes a new empty list and
+ * does the dependency.c dance to drop objects, which populates the list; when
+ * the event triggers are invoked they can consume the list via
+ * pg_event_trigger_dropped_objects(). When the command finishes, the list is
+ * cleared and the original list is restored. This is to support the case that
+ * an event trigger function drops objects "reentrantly".
+ */
+
+/*
+ * Initialize state of objects dropped
+ */
+void
+EventTriggerInitializeDrop(slist_head *save_objlist)
+{
+ /* save previous state in local vars of caller, for later restore */
+ *save_objlist = SQLDropList;
+
+ slist_init(&SQLDropList);
+}
+
+/*
+ * Restore state after running a command that drops objects; free memory from a
+ * list we may have created.
+ */
+void
+EventTriggerFinalizeDrop(slist_head save_objlist)
+{
+ slist_mutable_iter iter;
+
+ slist_foreach_modify(iter, &SQLDropList)
+ {
+ SQLDropObject *obj = slist_container(SQLDropObject, next, iter.cur);
+
+ if (obj->objname)
+ pfree(obj->objname);
+ if (obj->subobjname)
+ pfree(obj->subobjname);
+ if (obj->schemaname)
+ pfree(obj->schemaname);
+ pfree(obj);
+ }
+
+ SQLDropList = save_objlist;
+}
+
+/*
+ * Register one object as being dropped by the current command.
+ *
+ * XXX do we need to think about memory context these things are stored in?
+ */
+void
+evtrig_sqldrop_add_object(ObjectAddress *object)
+{
+ SQLDropObject *obj;
+
+ Assert(EventTriggerSupportsObjectType(getObjectClass(object)));
+
+ obj = palloc0(sizeof(SQLDropObject));
+ obj->address = *object;
+
+ /*
+ * Obtain object and schema names from the object's catalog tuple, if one
+ * exists.
+ */
+ if (is_objectclass_supported(obj->address.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_name(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ obj->objname = pstrdup(NameStr(*DatumGetName(datum)));
+ }
+
+ attnum = get_object_attnum_namespace(obj->address.classId);
+ if (attnum != InvalidAttrNumber)
+ {
+ datum = heap_getattr(tuple, attnum,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ obj->schemaname = get_namespace_name(DatumGetObjectId(datum));
+ }
+ }
+
+ /* add a column name, if present */
+ if (obj->address.objectSubId != 0)
+ {
+ /* must be a relation of some kind */
+ Assert(RelationGetRelid(catalog) == RelationRelationId);
+
+ obj->subobjname = get_attname(obj->address.objectId,
+ obj->address.objectSubId);
+ }
+
+ heap_close(catalog, AccessShareLock);
+ }
+
+ /* and object type, too */
+ obj->objecttype = getObjectTypeDescription(&obj->address);
+
+ slist_push_head(&SQLDropList, &obj->next);
+}
+
+/*
+ * 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;
+
+ /* 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, &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);
+
+ /* objname */
+ if (obj->objname)
+ values[i++] = CStringGetTextDatum(obj->objname);
+ else
+ nulls[i++] = true;
+
+ /* subobjname */
+ if (obj->subobjname)
+ values[i++] = CStringGetTextDatum(obj->subobjname);
+ else
+ nulls[i++] = true;
+
+ /* schemaname */
+ if (obj->schemaname)
+ values[i++] = CStringGetTextDatum(obj->schemaname);
+ else
+ nulls[i++] = true;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /* clean up and return the tuplestore */
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a1c03f1..66839d8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -370,6 +370,52 @@ ProcessUtility(Node *parsetree,
} \
} while (0)
+/*
+ * UTILITY_BEGIN_DROP and UTILITY_END_DROP are a pair of macros to enclose a
+ * code fragment that executes a command that may drop database objects, so
+ * that the event trigger environment is appropriately setup.
+ *
+ * Note these macros will call EventTriggerDDLCommandEnd if the object type is
+ * supported; caller must make sure to call EventTriggerDDLCommandStart by
+ * itself.
+ */
+#define UTILITY_BEGIN_DROP(isCompleteQuery, has_objtype, objtype) \
+ do { \
+ slist_head _save_objlist; \
+ bool _supported; \
+ bool _isComplete = isCompleteQuery; \
+ \
+ _supported = has_objtype ? EventTriggerSupportsObjectType(objtype) : true; \
+ \
+ if (_isComplete) \
+ { \
+ EventTriggerInitializeDrop(&_save_objlist); \
+ } \
+ PG_TRY(); \
+ {
+
+#define UTILITY_END_DROP(parsetree) \
+ } \
+ if (_isComplete && _supported) \
+ { \
+ EventTriggerDDLCommandEnd(parsetree); \
+ } \
+ \
+ PG_CATCH(); \
+ { \
+ if (_isComplete && _supported) \
+ { \
+ EventTriggerFinalizeDrop(_save_objlist); \
+ } \
+ PG_RE_THROW(); \
+ } \
+ PG_END_TRY(); \
+ if (_isComplete && _supported) \
+ { \
+ EventTriggerFinalizeDrop(_save_objlist); \
+ } \
+ } while (0)
+
void
standard_ProcessUtility(Node *parsetree,
const char *queryString,
@@ -704,6 +750,8 @@ standard_ProcessUtility(Node *parsetree,
&& EventTriggerSupportsObjectType(stmt->removeType))
EventTriggerDDLCommandStart(parsetree);
+ UTILITY_BEGIN_DROP(isCompleteQuery, true, stmt->removeType);
+
switch (stmt->removeType)
{
case OBJECT_INDEX:
@@ -724,9 +772,7 @@ standard_ProcessUtility(Node *parsetree,
break;
}
- if (isCompleteQuery
- && EventTriggerSupportsObjectType(stmt->removeType))
- EventTriggerDDLCommandEnd(parsetree);
+ UTILITY_END_DROP(parsetree);
break;
}
@@ -826,6 +872,8 @@ standard_ProcessUtility(Node *parsetree,
/* Run parse analysis ... */
stmts = transformAlterTableStmt(atstmt, queryString);
+ UTILITY_BEGIN_DROP(isCompleteQuery, true, OBJECT_TABLE);
+
/* ... and do it */
foreach(l, stmts)
{
@@ -851,6 +899,8 @@ standard_ProcessUtility(Node *parsetree,
if (lnext(l) != NULL)
CommandCounterIncrement();
}
+
+ UTILITY_END_DROP(parsetree);
}
else
ereport(NOTICE,
@@ -1248,8 +1298,12 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_DropOwnedStmt:
- /* no event triggers for global objects */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+
+ UTILITY_BEGIN_DROP(isCompleteQuery, false, 0);
DropOwnedObjects((DropOwnedStmt *) parsetree);
+ UTILITY_END_DROP(parsetree);
break;
case T_ReassignOwnedStmt:
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 5865962..7f25de4 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -18,6 +18,7 @@
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
@@ -40,6 +41,7 @@
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
+#include "utils/tqual.h"
#include "utils/typcache.h"
/* Hook for plugins to get control in get_attavgwidth() */
@@ -2926,3 +2928,54 @@ get_range_subtype(Oid rangeOid)
else
return InvalidOid;
}
+
+/* ------------- GENERIC -------------- */
+
+/*
+ * Return a copy of the tuple for the object with the given object OID, from
+ * the given catalog (which must have been opened by the caller and suitably
+ * locked). NULL is returned if the OID is not found.
+ *
+ * We try a syscache first, if available.
+ */
+HeapTuple
+get_catalog_object_by_oid(Relation catalog, Oid objectId)
+{
+ HeapTuple tuple;
+ Oid classId = RelationGetRelid(catalog);
+ int oidCacheId = get_object_catcache_oid(classId);
+
+ if (oidCacheId > 0)
+ {
+ tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ return NULL;
+ }
+ else
+ {
+ Oid oidIndexId = get_object_oid_index(classId);
+ SysScanDesc scan;
+ ScanKeyData skey;
+
+ Assert(OidIsValid(oidIndexId));
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(catalog, oidIndexId, true,
+ SnapshotNow, 1, &skey);
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ {
+ systable_endscan(scan);
+ return NULL;
+ }
+ tuple = heap_copytuple(tuple);
+
+ systable_endscan(scan);
+ }
+
+ return tuple;
+}
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8e0837f..106d8fb 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -179,6 +179,8 @@ extern ObjectClass getObjectClass(const ObjectAddress *object);
extern char *getObjectDescription(const ObjectAddress *object);
extern char *getObjectDescriptionOids(Oid classid, Oid objid);
+extern char *getObjectTypeDescription(const ObjectAddress *object);
+
extern ObjectAddresses *new_object_addresses(void);
extern void add_exact_object_address(const ObjectAddress *object,
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index ffaf4ea..8a34b50 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -38,6 +38,7 @@ extern void check_object_ownership(Oid roleid,
extern Oid get_object_namespace(const ObjectAddress *address);
+extern bool is_objectclass_supported(Oid class_id);
extern Oid get_object_oid_index(Oid class_id);
extern int get_object_catcache_oid(Oid class_id);
extern int get_object_catcache_name(Oid class_id);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 0e26ebf..09b5e61 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4682,6 +4682,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,26,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, object_name, subobject_name, schema_name}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
+DESCR("list objects dropped by the current command");
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 74c150b..2730e7a 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -13,7 +13,10 @@
#ifndef EVENT_TRIGGER_H
#define EVENT_TRIGGER_H
+#include "catalog/dependency.h"
+#include "catalog/objectaddress.h"
#include "catalog/pg_event_trigger.h"
+#include "lib/ilist.h"
#include "nodes/parsenodes.h"
typedef struct EventTriggerData
@@ -43,4 +46,8 @@ extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
+extern void EventTriggerInitializeDrop(slist_head *save_objlist);
+extern void EventTriggerFinalizeDrop(slist_head save_objlist);
+extern void evtrig_sqldrop_add_object(ObjectAddress *object);
+
#endif /* EVENT_TRIGGER_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index c0debe4..785d1de 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1147,6 +1147,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/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 49f459a..dfa6eb7 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -16,6 +16,7 @@
#include "access/attnum.h"
#include "access/htup.h"
#include "nodes/pg_list.h"
+#include "utils/relcache.h"
/* Result list element for get_op_btree_interpretation */
typedef struct OpBtreeInterpretation
@@ -152,6 +153,7 @@ extern void free_attstatsslot(Oid atttype,
float4 *numbers, int nnumbers);
extern char *get_namespace_name(Oid nspid);
extern Oid get_range_subtype(Oid rangeOid);
+extern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);
#define type_is_array(typid) (get_element_type(typid) != InvalidOid)
/* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index bf020de..264bbb3 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -93,11 +93,129 @@ 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();
+\dy
+ List of event triggers
+ Name | Event | Owner | Enabled | Procedure | Tags
+------+-------+-------+---------+-----------+------
+(0 rows)
+
+-- 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);
+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_name);
+ END IF;
+
+ INSERT INTO dropped_objects
+ (type, schema, object, subobject, curr_user, sess_user) VALUES
+ (obj.object_type, obj.schema_name, obj.object_name,
+ obj.subobject_name, 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 7 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 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 | table_two | | alvherre | alvherre
+ type | audit_tbls | _table_two | | alvherre | alvherre
+ table | schema_two | table_two | | alvherre | alvherre
+ type | schema_two | table_two | | alvherre | alvherre
+ type | schema_two | _table_two | | alvherre | alvherre
+ table | audit_tbls | table_three | | alvherre | alvherre
+ type | audit_tbls | table_three | | alvherre | alvherre
+ type | audit_tbls | _table_three | | alvherre | alvherre
+ table | schema_two | table_three | | alvherre | alvherre
+ type | schema_two | table_three | | alvherre | alvherre
+ type | schema_two | _table_three | | alvherre | alvherre
+ table column | audit_tbls | table_three | the_value | alvherre | alvherre
+ schema | | schema_one | | alvherre | alvherre
+ table | schema_one | table_one | | alvherre | alvherre
+ type | schema_one | table_one | | alvherre | alvherre
+ type | schema_one | _table_one | | alvherre | alvherre
+ table | schema_one | table_two | | alvherre | alvherre
+ type | schema_one | table_two | | alvherre | alvherre
+ type | 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 | table_three | | alvherre | alvherre
+ type | schema_one | _table_three | | alvherre | alvherre
+(26 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;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index a07dcd7..0c5e7db 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -97,10 +97,71 @@ 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();
+\dy
+
+-- 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);
+
+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_name);
+ END IF;
+
+ INSERT INTO dropped_objects
+ (type, schema, object, subobject, curr_user, sess_user) VALUES
+ (obj.object_type, obj.schema_name, obj.object_name,
+ obj.subobject_name, 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;
Okay, I added a couple of lines to skip reporting dropped temp schemas,
and to skip any objects belonging to any temp schema (not just my own,
note). Not posting a new version because the change is pretty trivial.
Now, one last thing that comes up is what about objects that don't have
straight names (such as pg_amop, pg_amproc, pg_default_acl etc etc), the
only thing you get is a catalog OID and an object OID ... but they are
pretty useless because by the time you get to the ddl_command_end
trigger, the objects are gone from the catalog. Maybe we should report
*something* about those. Say, perhaps the object description ... but if
we want that, it should be untranslated (i.e. not just what
getObjectDescription gives you, because that may be translated, so we
would need to patch it so that it only translates if the caller requests
it)
Another example is reporting of functions: right now you get the
function name .. but if there are overloaded functions there's no way to
know wihch one was dropped. Example:
alvherre=# create function f(int, int) returns int language sql as $$ select $1 + $2; $$;
CREATE FUNCTION
alvherre=# create function f(int, int, int) returns int language sql as $$ select $1 + $2 + $3; $$;
CREATE FUNCTION
alvherre=# drop function f(int, int);
DROP FUNCTION
alvherre=# select * from dropped_objects ;
type | schema | object | subobj | curr_user | sess_user
----------+--------+-----------+--------+-----------+-----------
function | public | f | | alvherre | alvherre
Maybe we could use the "subobject_name" field (what you see as subobj
above) to store the function signature (perhaps excluding the function
name), for example. So you'd get object="f" subobject="(int,int)".
Or maybe we should stash the whole function signature as name and leave
subobject NULL.
The reason I'm worrying about this is that it might be important for
some use cases. For instance, replication cases probably don't care
about that at all. But if we want to be able to use event triggers for
auditing user activity, we need this info.
Thoughts?
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera escribió:
Now, one last thing that comes up is what about objects that don't have
straight names (such as pg_amop, pg_amproc, pg_default_acl etc etc), the
only thing you get is a catalog OID and an object OID ... but they are
pretty useless because by the time you get to the ddl_command_end
trigger, the objects are gone from the catalog. Maybe we should report
*something* about those.
Here's another idea --- have three columns, "type", "schema" (as in the
current patch and as shown above), and a third one for object identity.
For tables and other objects that have simple names, the identity would
be their names. For columns, it'd be <tablename>.<columnname>. For
functions, it'd be the complete signature. And so on.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Mar 5, 2013 at 5:37 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Okay, I added a couple of lines to skip reporting dropped temp schemas,
and to skip any objects belonging to any temp schema (not just my own,
note). Not posting a new version because the change is pretty trivial.Now, one last thing that comes up is what about objects that don't have
straight names (such as pg_amop, pg_amproc, pg_default_acl etc etc), the
only thing you get is a catalog OID and an object OID ... but they are
pretty useless because by the time you get to the ddl_command_end
trigger, the objects are gone from the catalog. Maybe we should report
*something* about those. Say, perhaps the object description ... but if
we want that, it should be untranslated (i.e. not just what
getObjectDescription gives you, because that may be translated, so we
would need to patch it so that it only translates if the caller requests
it)
Broadly, I suggest making the output format match as exactly as
possible what commands like COMMENT and SECURITY LABEL accept as
input. We've already confronted all of these notational issues there.
Columns are identified as COLUMN table.name; functions as FUNCTION
function_name(argtypes); etc. Of course it's fine to split the object
type off into a separate column, but it should have the same name here
that it does there.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Here's another idea --- have three columns, "type", "schema" (as in the
current patch and as shown above), and a third one for object identity.For tables and other objects that have simple names, the identity would
be their names. For columns, it'd be <tablename>.<columnname>. For
functions, it'd be the complete signature. And so on.
Sounds very good as an extra column yes.
Robert Haas <robertmhaas@gmail.com> writes:
Broadly, I suggest making the output format match as exactly as
possible what commands like COMMENT and SECURITY LABEL accept as
input. We've already confronted all of these notational issues there.
Columns are identified as COLUMN table.name; functions as FUNCTION
function_name(argtypes); etc. Of course it's fine to split the object
type off into a separate column, but it should have the same name here
that it does there.
I would like the format to be easily copy/paste'able to things such as
regclass/regtype/regprocedure casts, and apparently the COMMENT input
format seems to be the same as that one, so +1 from me.
--
Dimitri Fontaine 06 63 07 10 78
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Mar 5, 2013 at 12:45 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Hmm, maybe I should be considering a pair of macros instead --
UTILITY_START_DROP and UTILITY_END_DROP. I'll give this a try. Other
ideas are welcome.
That seems like a possibly promising idea. I do wonder how well any
of this is going to scale. Presumably people are going to want
similar things for CREATE and (hardest) ALTER. Seems like
ProcessUtility() could get pretty messy and confusing. But I don't
have a better idea, either. :-(
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Tue, Mar 5, 2013 at 12:45 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Hmm, maybe I should be considering a pair of macros instead --
UTILITY_START_DROP and UTILITY_END_DROP. I'll give this a try. Other
ideas are welcome.That seems like a possibly promising idea. I do wonder how well any
of this is going to scale.
I did followup with a patch implementing that; did you see it?
Presumably people are going to want
similar things for CREATE and (hardest) ALTER. Seems like
ProcessUtility() could get pretty messy and confusing. But I don't
have a better idea, either. :-(
Well, the first thing that we need to settle is the user interface.
Normalized command string don't seem to cut it; requiring users to write
SQL parsers is rather unfriendly IMHO. The current idea of having a
function that returns objects affected by the command seems relatively
sensible. For drops, it seems pretty straighforward so far. For CREATE
it's probably somewhat more involved, but seems doable in principle (but
yes, we're going to have to sprinkle ProcessUtility() with a lot of
UTILITY_START/END_CREATE calls).
Not sure about ALTER; maybe we will need a completely different idea to
attack that.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Mar 8, 2013 at 9:18 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Robert Haas escribió:
On Tue, Mar 5, 2013 at 12:45 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Hmm, maybe I should be considering a pair of macros instead --
UTILITY_START_DROP and UTILITY_END_DROP. I'll give this a try. Other
ideas are welcome.That seems like a possibly promising idea. I do wonder how well any
of this is going to scale.I did followup with a patch implementing that; did you see it?
No, sorry. Which thread is it on?
Presumably people are going to want
similar things for CREATE and (hardest) ALTER. Seems like
ProcessUtility() could get pretty messy and confusing. But I don't
have a better idea, either. :-(Well, the first thing that we need to settle is the user interface.
Normalized command string don't seem to cut it; requiring users to write
SQL parsers is rather unfriendly IMHO.
I could not agree more. The format we're moving towards for dropped
objects can easily be converted back into SQL if you happen to want to
do that, but it's also much easier to process programatically than a
compared with a normalized command string. So I think we get the best
of both worlds with this design.
The current idea of having a
function that returns objects affected by the command seems relatively
sensible. For drops, it seems pretty straighforward so far. For CREATE
it's probably somewhat more involved, but seems doable in principle (but
yes, we're going to have to sprinkle ProcessUtility() with a lot of
UTILITY_START/END_CREATE calls).Not sure about ALTER; maybe we will need a completely different idea to
attack that.
I am inclined to think that putting this logic in ProcessUtility isn't
scalable, even for CREATE, and even moreso for ALTER, unless we can
put it around everything in that function, rather than each command
individually. Suppose for example that on entry to that function we
simply did this:
if (isCompleteQuery)
++CompleteQueryNestingLevel;
...and at exit, we did the reverse. This could work a bit like the
GUC nesting level. When an object is dropped, we find the array slot
for the current nesting level, and it's, say, a List **, and we push a
new element onto that list. When somebody asks for a list of dropped
objects, we pull from the list for the current nesting level. When
decrementing the nesting level, we flush the list associated with the
old nesting level, if any.
if (isCompleteQuery)
{
list_free(dropped_objects_list[CompleteQueryNestingLevel];
dropped_objects_list[CompleteQueryNestingLevel] = NIL;
--CompleteQueryNestingLevel;
}
Now, if we want to support CREATE, we can just have a
created_objects_list array as well, and only minimal changes are
required here. If we want to support ALTER we can have an
altered_objects_list, and the only decision is what data to stuff into
the list elements.
I think this is a lot better than decorating each individual command,
which is already unweildly and bound to get worse. It is a bit of a
definitional change, because it implies that the list of dropped
objects is the list of objects dropped by the most-nearly-enclosing
DDL command, rather than the list of objects dropped by the
most-nearly-enclosing DDL command *that is capable of dropping
objects*. However, I'm inclined to think that's a better definition
anyway.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Fri, Mar 8, 2013 at 9:18 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Robert Haas escribió:
On Tue, Mar 5, 2013 at 12:45 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Hmm, maybe I should be considering a pair of macros instead --
UTILITY_START_DROP and UTILITY_END_DROP. I'll give this a try. Other
ideas are welcome.That seems like a possibly promising idea. I do wonder how well any
of this is going to scale.I did followup with a patch implementing that; did you see it?
No, sorry. Which thread is it on?
/messages/by-id/20130305214218.GP9507@alvh.no-ip.org
I think Gmail's feature of breaking threads when subject changes is an
annoyance here. I somehow added a "g" at the end and later dropped it.
I didn't remember that behavior of Gmail's.
The current idea of having a
function that returns objects affected by the command seems relatively
sensible. For drops, it seems pretty straighforward so far. For CREATE
it's probably somewhat more involved, but seems doable in principle (but
yes, we're going to have to sprinkle ProcessUtility() with a lot of
UTILITY_START/END_CREATE calls).Not sure about ALTER; maybe we will need a completely different idea to
attack that.I am inclined to think that putting this logic in ProcessUtility isn't
scalable, even for CREATE, and even moreso for ALTER, unless we can
put it around everything in that function, rather than each command
individually. Suppose for example that on entry to that function we
simply did this:if (isCompleteQuery)
++CompleteQueryNestingLevel;...and at exit, we did the reverse. This could work a bit like the
GUC nesting level.
Hmm, this seems an interesting idea to explore.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera escribió:
I am inclined to think that putting this logic in ProcessUtility isn't
scalable, even for CREATE, and even moreso for ALTER, unless we can
put it around everything in that function, rather than each command
individually. Suppose for example that on entry to that function we
simply did this:if (isCompleteQuery)
++CompleteQueryNestingLevel;...and at exit, we did the reverse. This could work a bit like the
GUC nesting level.Hmm, this seems an interesting idea to explore.
Okay, here's a patch implementing the spirit of this idea. We don't
keep track of a nesting level per se; I tried that approach and it
seemed a dead end. Instead I created a pair of macros that are pretty
much the same as UTILITY_BEGIN_DROP, except that they enclose the whole
of ProcessUtility instead of individual commands. These macros set up a
context for event_trigger.c to record affected actions/objects.
Currently this only keeps track of dropped objects (which is what this
particular patch is all about), but it appears reasonably simple to
extend to objects created, and subcommands of ALTER.
I have also modified the return type of
pg_event_trigger_dropped_objects, so that instead of object name it
returns object identity (as well as object type and schema name). For
example, for functions the identity is the signature; for tables, it's
the name. Each object type has its own format, modelled after the
COMMENT syntax (some object types don't have COMMENT support, so I had
to come up with some. This might need further discussion to arrive at
the best possible rendering for each object class). I had to add a
nearly-duplicate of getObjectDescription for this, which needs a bit
more work yet so that it doesn't schema-qualify the object names even
when not in path (most objects are not qualified, but I think operators
and types still are.)
(There's no SQL-callable function to get the object identity, such as
what we have as pg_describe_object. Not sure how important this is.)
Also, we need to ensure that unsupported object types such as
pg_default_acl rows are not reported. I have not tested this yet.
Supposedly this is part of a future patch that will encompass all DCL
commands (grant, revoke, create/drop roles, etc).
This patch also adds event trigger support for DROP OWNED (a two-line
change).
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.8.patchtext/x-diff; charset=us-asciiDownload
*** 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
***************
*** 15750,15753 **** FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
--- 15750,15803 ----
<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
***************
*** 198,203 **** static bool stack_address_present_add_flags(const ObjectAddress *object,
--- 198,207 ----
ObjectAddressStack *stack);
static void getRelationDescription(StringInfo buffer, Oid relid);
static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+ static void getRelationTypeDescription(StringInfo buffer, Oid relid,
+ int32 objectSubId);
+ static void getOpFamilyIdentity(StringInfo buffer, Oid opfid);
+ static void getRelationIdentity(StringInfo buffer, Oid relid);
/*
***************
*** 267,272 **** performDeletion(const ObjectAddress *object,
--- 271,282 ----
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
***************
*** 349,354 **** performMultipleDeletions(const ObjectAddresses *objects,
--- 359,370 ----
{
ObjectAddress *thisobj = targetObjects->refs + i;
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ evtrig_sqldrop_add_object(thisobj);
+ }
+
deleteOneObject(thisobj, &depRel, flags);
}
***************
*** 366,371 **** performMultipleDeletions(const ObjectAddresses *objects,
--- 382,391 ----
* 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,
***************
*** 3101,3103 **** pg_describe_object(PG_FUNCTION_ARGS)
--- 3121,3991 ----
description = getObjectDescription(&address);
PG_RETURN_TEXT_P(cstring_to_text(description));
}
+
+ /*
+ * Return a palloc'ed string that describes the type of object that the
+ * passed address is for.
+ */
+ char *
+ getObjectTypeDescription(const ObjectAddress *object)
+ {
+ StringInfoData buffer;
+
+ initStringInfo(&buffer);
+
+ switch (getObjectClass(object))
+ {
+ case OCLASS_CLASS:
+ getRelationTypeDescription(&buffer, object->objectId,
+ object->objectSubId);
+ break;
+
+ case OCLASS_PROC:
+ appendStringInfo(&buffer, "function");
+ break;
+
+ case OCLASS_TYPE:
+ appendStringInfo(&buffer, "type");
+ break;
+
+ case OCLASS_CAST:
+ appendStringInfo(&buffer, "cast");
+ break;
+
+ case OCLASS_COLLATION:
+ appendStringInfo(&buffer, "collation");
+ break;
+
+ case OCLASS_CONSTRAINT:
+ appendStringInfo(&buffer, "constraint");
+ break;
+
+ case OCLASS_CONVERSION:
+ appendStringInfo(&buffer, "conversion");
+ break;
+
+ case OCLASS_DEFAULT:
+ appendStringInfo(&buffer, "default value");
+ break;
+
+ case OCLASS_LANGUAGE:
+ appendStringInfo(&buffer, "language");
+ break;
+
+ case OCLASS_LARGEOBJECT:
+ appendStringInfo(&buffer, "large object");
+ break;
+
+ case OCLASS_OPERATOR:
+ appendStringInfo(&buffer, "operator");
+ break;
+
+ case OCLASS_OPCLASS:
+ appendStringInfo(&buffer, "operator class");
+ break;
+
+ case OCLASS_OPFAMILY:
+ appendStringInfo(&buffer, "operator family");
+ break;
+
+ case OCLASS_AMOP:
+ appendStringInfo(&buffer, "operator of access method");
+ break;
+
+ case OCLASS_AMPROC:
+ appendStringInfo(&buffer, "function of access method");
+ break;
+
+ case OCLASS_REWRITE:
+ appendStringInfo(&buffer, "rule");
+ break;
+
+ case OCLASS_TRIGGER:
+ appendStringInfo(&buffer, "trigger");
+ break;
+
+ case OCLASS_SCHEMA:
+ appendStringInfo(&buffer, "schema");
+ break;
+
+ case OCLASS_TSPARSER:
+ appendStringInfo(&buffer, "text search parser");
+ break;
+
+ case OCLASS_TSDICT:
+ appendStringInfo(&buffer, "text search dictionary");
+ break;
+
+ case OCLASS_TSTEMPLATE:
+ appendStringInfo(&buffer, "text search template");
+ break;
+
+ case OCLASS_TSCONFIG:
+ appendStringInfo(&buffer, "text search configuration");
+ break;
+
+ case OCLASS_ROLE:
+ appendStringInfo(&buffer, "role");
+ break;
+
+ case OCLASS_DATABASE:
+ appendStringInfo(&buffer, "database");
+ break;
+
+ case OCLASS_TBLSPACE:
+ appendStringInfo(&buffer, "tablespace");
+ break;
+
+ case OCLASS_FDW:
+ appendStringInfo(&buffer, "foreign-data wrapper");
+ break;
+
+ case OCLASS_FOREIGN_SERVER:
+ appendStringInfo(&buffer, "server");
+ break;
+
+ case OCLASS_USER_MAPPING:
+ appendStringInfo(&buffer, "user mapping");
+ break;
+
+ case OCLASS_DEFACL:
+ /* FIXME need more detail here, on defaclobjtype */
+ appendStringInfo(&buffer, "default ACL");
+ break;
+
+ case OCLASS_EXTENSION:
+ appendStringInfo(&buffer, "extension");
+ break;
+
+ case OCLASS_EVENT_TRIGGER:
+ appendStringInfo(&buffer, "event trigger");
+ break;
+
+ default:
+ appendStringInfo(&buffer, "unrecognized");
+ break;
+ }
+
+ return buffer.data;
+ }
+
+ /*
+ * subroutine for getObjectTypeDescription: describe a relation type
+ */
+ static void
+ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+ {
+ HeapTuple relTup;
+ Form_pg_class relForm;
+
+ relTup = SearchSysCache1(RELOID,
+ ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+ switch (relForm->relkind)
+ {
+ case RELKIND_RELATION:
+ appendStringInfo(buffer, "table");
+ break;
+ case RELKIND_INDEX:
+ appendStringInfo(buffer, "index");
+ break;
+ case RELKIND_SEQUENCE:
+ appendStringInfo(buffer, "sequence");
+ break;
+ case RELKIND_TOASTVALUE:
+ appendStringInfo(buffer, "toast table");
+ break;
+ case RELKIND_VIEW:
+ appendStringInfo(buffer, "view");
+ break;
+ case RELKIND_MATVIEW:
+ appendStringInfo(buffer, "materialized view");
+ break;
+ case RELKIND_COMPOSITE_TYPE:
+ appendStringInfo(buffer, "composite type");
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ appendStringInfo(buffer, "foreign table");
+ break;
+ default:
+ /* shouldn't get here */
+ appendStringInfo(buffer, "relation");
+ break;
+ }
+
+ if (objectSubId != 0)
+ appendStringInfo(buffer, " column");
+
+ ReleaseSysCache(relTup);
+ }
+
+ /*
+ * Return a palloc'ed string that identifies an object.
+ *
+ * This is for machine consumption, so it's not translated; and we assume that
+ * the schema name, when pertinent, is going to be obtained by some other
+ * means, so we don't schema-qualify either.
+ */
+ char *
+ getObjectIdentity(const ObjectAddress *object)
+ {
+ StringInfoData buffer;
+
+ initStringInfo(&buffer);
+
+ switch (getObjectClass(object))
+ {
+ case OCLASS_CLASS:
+ getRelationIdentity(&buffer, object->objectId);
+ if (object->objectSubId != 0)
+ appendStringInfo(&buffer, ".%s",
+ get_relid_attribute_name(object->objectId,
+ object->objectSubId));
+ break;
+
+ case OCLASS_PROC:
+ appendStringInfo(&buffer, "%s",
+ format_procedure_internal(object->objectId,
+ false));
+ break;
+
+ case OCLASS_TYPE:
+ /* FIXME -- avoid schema-qualifying this */
+ appendStringInfo(&buffer, "%s",
+ format_type_be(object->objectId));
+ break;
+
+ case OCLASS_CAST:
+ {
+ Relation castDesc;
+ ScanKeyData skey[1];
+ SysScanDesc rcscan;
+ HeapTuple tup;
+ Form_pg_cast castForm;
+
+ castDesc = heap_open(CastRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ rcscan = systable_beginscan(castDesc, CastOidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(rcscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for cast %u",
+ object->objectId);
+
+ castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+ appendStringInfo(&buffer, "(%s AS %s)",
+ format_type_be(castForm->castsource),
+ format_type_be(castForm->casttarget));
+
+ systable_endscan(rcscan);
+ heap_close(castDesc, AccessShareLock);
+ break;
+ }
+
+ case OCLASS_COLLATION:
+ {
+ HeapTuple collTup;
+ Form_pg_collation coll;
+
+ collTup = SearchSysCache1(COLLOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(collTup))
+ elog(ERROR, "cache lookup failed for collation %u",
+ object->objectId);
+ coll = (Form_pg_collation) GETSTRUCT(collTup);
+ appendStringInfo(&buffer, "%s",
+ NameStr(coll->collname));
+ ReleaseSysCache(collTup);
+ break;
+ }
+
+ case OCLASS_CONSTRAINT:
+ {
+ HeapTuple conTup;
+ Form_pg_constraint con;
+
+ conTup = SearchSysCache1(CONSTROID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(conTup))
+ elog(ERROR, "cache lookup failed for constraint %u",
+ object->objectId);
+ con = (Form_pg_constraint) GETSTRUCT(conTup);
+
+ if (OidIsValid(con->conrelid))
+ {
+ StringInfoData rel;
+
+ initStringInfo(&rel);
+ getRelationIdentity(&rel, con->conrelid);
+ appendStringInfo(&buffer, "%s on %s",
+ NameStr(con->conname), rel.data);
+ pfree(rel.data);
+ }
+ else
+ {
+ /* FIXME add domain name */
+ appendStringInfo(&buffer, "%s",
+ NameStr(con->conname));
+ }
+
+ ReleaseSysCache(conTup);
+ break;
+ }
+
+ case OCLASS_CONVERSION:
+ {
+ HeapTuple conTup;
+
+ conTup = SearchSysCache1(CONVOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(conTup))
+ elog(ERROR, "cache lookup failed for conversion %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s",
+ NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
+ ReleaseSysCache(conTup);
+ break;
+ }
+
+ case OCLASS_DEFAULT:
+ {
+ Relation attrdefDesc;
+ ScanKeyData skey[1];
+ SysScanDesc adscan;
+ HeapTuple tup;
+ Form_pg_attrdef attrdef;
+ ObjectAddress colobject;
+
+ attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ adscan = systable_beginscan(attrdefDesc, AttrDefaultOidIndexId,
+ true, SnapshotNow, 1, skey);
+
+ tup = systable_getnext(adscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for attrdef %u",
+ object->objectId);
+
+ attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+
+ colobject.classId = RelationRelationId;
+ colobject.objectId = attrdef->adrelid;
+ colobject.objectSubId = attrdef->adnum;
+
+ appendStringInfo(&buffer, "for %s",
+ getObjectIdentity(&colobject));
+
+ systable_endscan(adscan);
+ heap_close(attrdefDesc, AccessShareLock);
+ break;
+ }
+
+ case OCLASS_LANGUAGE:
+ {
+ HeapTuple langTup;
+
+ langTup = SearchSysCache1(LANGOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(langTup))
+ elog(ERROR, "cache lookup failed for language %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s",
+ NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
+ ReleaseSysCache(langTup);
+ break;
+ }
+ case OCLASS_LARGEOBJECT:
+ appendStringInfo(&buffer, "%u",
+ object->objectId);
+ break;
+
+ case OCLASS_OPERATOR:
+ /* FIXME avoid schema-qualifying this */
+ appendStringInfo(&buffer, "%s",
+ format_operator(object->objectId));
+ break;
+
+ case OCLASS_OPCLASS:
+ {
+ HeapTuple opcTup;
+ Form_pg_opclass opcForm;
+ HeapTuple amTup;
+ Form_pg_am amForm;
+
+ opcTup = SearchSysCache1(CLAOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(opcTup))
+ elog(ERROR, "cache lookup failed for opclass %u",
+ object->objectId);
+ opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
+
+ amTup = SearchSysCache1(AMOID,
+ ObjectIdGetDatum(opcForm->opcmethod));
+ if (!HeapTupleIsValid(amTup))
+ elog(ERROR, "cache lookup failed for access method %u",
+ opcForm->opcmethod);
+ amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+ appendStringInfo(&buffer, "%s for %s",
+ NameStr(opcForm->opcname),
+ NameStr(amForm->amname));
+
+ ReleaseSysCache(amTup);
+ ReleaseSysCache(opcTup);
+ break;
+ }
+
+ case OCLASS_OPFAMILY:
+ getOpFamilyIdentity(&buffer, object->objectId);
+ break;
+
+ case OCLASS_AMOP:
+ {
+ Relation amopDesc;
+ ScanKeyData skey[1];
+ SysScanDesc amscan;
+ HeapTuple tup;
+ Form_pg_amop amopForm;
+ StringInfoData opfam;
+
+ amopDesc = heap_open(AccessMethodOperatorRelationId,
+ AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ amscan = systable_beginscan(amopDesc, AccessMethodOperatorOidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(amscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for amop entry %u",
+ object->objectId);
+
+ amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+ initStringInfo(&opfam);
+ getOpFamilyIdentity(&opfam, amopForm->amopfamily);
+
+ /* XXX is this too verbose? */
+ appendStringInfo(&buffer, "%d (%s, %s) of %s: %s",
+ amopForm->amopstrategy,
+ format_type_be(amopForm->amoplefttype),
+ format_type_be(amopForm->amoprighttype),
+ opfam.data,
+ format_operator(amopForm->amopopr));
+
+ pfree(opfam.data);
+
+ systable_endscan(amscan);
+ heap_close(amopDesc, AccessShareLock);
+ break;
+ }
+
+ case OCLASS_AMPROC:
+ {
+ Relation amprocDesc;
+ ScanKeyData skey[1];
+ SysScanDesc amscan;
+ HeapTuple tup;
+ Form_pg_amproc amprocForm;
+ StringInfoData opfam;
+
+ amprocDesc = heap_open(AccessMethodProcedureRelationId,
+ AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ amscan = systable_beginscan(amprocDesc, AccessMethodProcedureOidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(amscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for amproc entry %u",
+ object->objectId);
+
+ amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+ initStringInfo(&opfam);
+ getOpFamilyIdentity(&opfam, amprocForm->amprocfamily);
+
+ /* XXX is this too verbose? */
+ appendStringInfo(&buffer, "%d (%s, %s) of %s: %s",
+ amprocForm->amprocnum,
+ format_type_be(amprocForm->amproclefttype),
+ format_type_be(amprocForm->amprocrighttype),
+ opfam.data,
+ format_procedure_internal(amprocForm->amproc,
+ false));
+
+ pfree(opfam.data);
+
+ systable_endscan(amscan);
+ heap_close(amprocDesc, AccessShareLock);
+ break;
+ }
+
+ case OCLASS_REWRITE:
+ {
+ Relation ruleDesc;
+ ScanKeyData skey[1];
+ SysScanDesc rcscan;
+ HeapTuple tup;
+ Form_pg_rewrite rule;
+
+ ruleDesc = heap_open(RewriteRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(rcscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for rule %u",
+ object->objectId);
+
+ rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+ appendStringInfo(&buffer, "%s on ",
+ NameStr(rule->rulename));
+ getRelationIdentity(&buffer, rule->ev_class);
+
+ systable_endscan(rcscan);
+ heap_close(ruleDesc, AccessShareLock);
+ break;
+ }
+
+ case OCLASS_TRIGGER:
+ {
+ Relation trigDesc;
+ ScanKeyData skey[1];
+ SysScanDesc tgscan;
+ HeapTuple tup;
+ Form_pg_trigger trig;
+
+ trigDesc = heap_open(TriggerRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ tgscan = systable_beginscan(trigDesc, TriggerOidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(tgscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for trigger %u",
+ object->objectId);
+
+ trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+ appendStringInfo(&buffer, "%s on ",
+ NameStr(trig->tgname));
+ getRelationIdentity(&buffer, trig->tgrelid);
+
+ systable_endscan(tgscan);
+ heap_close(trigDesc, AccessShareLock);
+ break;
+ }
+
+ case OCLASS_SCHEMA:
+ {
+ char *nspname;
+
+ nspname = get_namespace_name(object->objectId);
+ if (!nspname)
+ elog(ERROR, "cache lookup failed for namespace %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s", nspname);
+ break;
+ }
+
+ case OCLASS_TSPARSER:
+ {
+ HeapTuple tup;
+
+ tup = SearchSysCache1(TSPARSEROID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search parser %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s",
+ NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
+ ReleaseSysCache(tup);
+ break;
+ }
+
+ case OCLASS_TSDICT:
+ {
+ HeapTuple tup;
+
+ tup = SearchSysCache1(TSDICTOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s",
+ NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
+ ReleaseSysCache(tup);
+ break;
+ }
+
+ case OCLASS_TSTEMPLATE:
+ {
+ HeapTuple tup;
+
+ tup = SearchSysCache1(TSTEMPLATEOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search template %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s",
+ NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
+ ReleaseSysCache(tup);
+ break;
+ }
+
+ case OCLASS_TSCONFIG:
+ {
+ HeapTuple tup;
+
+ tup = SearchSysCache1(TSCONFIGOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s",
+ NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
+ ReleaseSysCache(tup);
+ break;
+ }
+
+ case OCLASS_ROLE:
+ {
+ appendStringInfo(&buffer, "%s",
+ GetUserNameFromId(object->objectId));
+ break;
+ }
+
+ case OCLASS_DATABASE:
+ {
+ char *datname;
+
+ datname = get_database_name(object->objectId);
+ if (!datname)
+ elog(ERROR, "cache lookup failed for database %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s", datname);
+ break;
+ }
+
+ case OCLASS_TBLSPACE:
+ {
+ char *tblspace;
+
+ tblspace = get_tablespace_name(object->objectId);
+ if (!tblspace)
+ elog(ERROR, "cache lookup failed for tablespace %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s", tblspace);
+ break;
+ }
+
+ case OCLASS_FDW:
+ {
+ ForeignDataWrapper *fdw;
+
+ fdw = GetForeignDataWrapper(object->objectId);
+ appendStringInfo(&buffer, "%s", fdw->fdwname);
+ break;
+ }
+
+ case OCLASS_FOREIGN_SERVER:
+ {
+ ForeignServer *srv;
+
+ srv = GetForeignServer(object->objectId);
+ appendStringInfo(&buffer, "%s", srv->servername);
+ break;
+ }
+
+ case OCLASS_USER_MAPPING:
+ {
+ HeapTuple tup;
+ Oid useid;
+ char *usename;
+
+ tup = SearchSysCache1(USERMAPPINGOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for user mapping %u",
+ object->objectId);
+
+ useid = ((Form_pg_user_mapping) GETSTRUCT(tup))->umuser;
+
+ ReleaseSysCache(tup);
+
+ if (OidIsValid(useid))
+ usename = GetUserNameFromId(useid);
+ else
+ usename = "public";
+
+ appendStringInfo(&buffer, "%s", usename);
+ break;
+ }
+
+ case OCLASS_DEFACL:
+ {
+ Relation defaclrel;
+ ScanKeyData skey[1];
+ SysScanDesc rcscan;
+ HeapTuple tup;
+ Form_pg_default_acl defacl;
+
+ defaclrel = heap_open(DefaultAclRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ rcscan = systable_beginscan(defaclrel, DefaultAclOidIndexId,
+ true, SnapshotNow, 1, skey);
+
+ tup = systable_getnext(rcscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for default ACL %u",
+ object->objectId);
+
+ defacl = (Form_pg_default_acl) GETSTRUCT(tup);
+
+ appendStringInfo(&buffer,
+ "belonging to role %s",
+ GetUserNameFromId(defacl->defaclrole));
+
+ if (OidIsValid(defacl->defaclnamespace))
+ {
+ appendStringInfo(&buffer,
+ " in schema %s",
+ get_namespace_name(defacl->defaclnamespace));
+ }
+
+ systable_endscan(rcscan);
+ heap_close(defaclrel, AccessShareLock);
+ break;
+ }
+
+ case OCLASS_EXTENSION:
+ {
+ char *extname;
+
+ extname = get_extension_name(object->objectId);
+ if (!extname)
+ elog(ERROR, "cache lookup failed for extension %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s", extname);
+ break;
+ }
+
+ case OCLASS_EVENT_TRIGGER:
+ {
+ HeapTuple tup;
+
+ tup = SearchSysCache1(EVENTTRIGGEROID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for event trigger %u",
+ object->objectId);
+ appendStringInfo(&buffer, "%s",
+ NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
+ ReleaseSysCache(tup);
+ break;
+ }
+
+ default:
+ appendStringInfo(&buffer, "unrecognized object %u %u %d",
+ object->classId,
+ object->objectId,
+ object->objectSubId);
+ break;
+ }
+
+ return buffer.data;
+ }
+
+ static void
+ getOpFamilyIdentity(StringInfo buffer, Oid opfid)
+ {
+ HeapTuple opfTup;
+ Form_pg_opfamily opfForm;
+ HeapTuple amTup;
+ Form_pg_am amForm;
+
+ opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
+ if (!HeapTupleIsValid(opfTup))
+ elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+ opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+ amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
+ if (!HeapTupleIsValid(amTup))
+ elog(ERROR, "cache lookup failed for access method %u",
+ opfForm->opfmethod);
+ amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+ appendStringInfo(buffer, "%s for access method %s",
+ NameStr(opfForm->opfname),
+ NameStr(amForm->amname));
+
+ ReleaseSysCache(amTup);
+ ReleaseSysCache(opfTup);
+ }
+
+ static void
+ getRelationIdentity(StringInfo buffer, Oid relid)
+ {
+ HeapTuple relTup;
+ Form_pg_class relForm;
+
+ relTup = SearchSysCache1(RELOID,
+ ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+ appendStringInfo(buffer, "%s",
+ NameStr(relForm->relname));
+
+ ReleaseSysCache(relTup);
+ }
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 1345,1350 **** get_object_aclkind(Oid class_id)
--- 1345,1368 ----
}
/*
+ * Return whether we have useful data for the given object class in the
+ * ObjectProperty table.
+ */
+ bool
+ is_objectclass_supported(Oid class_id)
+ {
+ int index;
+
+ for (index = 0; index < lengthof(ObjectProperty); index++)
+ {
+ if (ObjectProperty[index].class_oid == class_id)
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
* Find ObjectProperty structure by class_id.
*/
static ObjectPropertyType *
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 748,805 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
}
/*
- * Return a copy of the tuple for the object with the given object OID, from
- * the given catalog (which must have been opened by the caller and suitably
- * locked). NULL is returned if the OID is not found.
- *
- * We try a syscache first, if available.
- *
- * XXX this function seems general in possible usage. Given sufficient callers
- * elsewhere, we should consider moving it to a more appropriate place.
- */
- static HeapTuple
- get_catalog_object_by_oid(Relation catalog, Oid objectId)
- {
- HeapTuple tuple;
- Oid classId = RelationGetRelid(catalog);
- int oidCacheId = get_object_catcache_oid(classId);
-
- if (oidCacheId > 0)
- {
- tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
- if (!HeapTupleIsValid(tuple)) /* should not happen */
- return NULL;
- }
- else
- {
- Oid oidIndexId = get_object_oid_index(classId);
- SysScanDesc scan;
- ScanKeyData skey;
-
- Assert(OidIsValid(oidIndexId));
-
- ScanKeyInit(&skey,
- ObjectIdAttributeNumber,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(objectId));
-
- scan = systable_beginscan(catalog, oidIndexId, true,
- SnapshotNow, 1, &skey);
- tuple = systable_getnext(scan);
- if (!HeapTupleIsValid(tuple))
- {
- systable_endscan(scan);
- return NULL;
- }
- tuple = heap_copytuple(tuple);
-
- systable_endscan(scan);
- }
-
- return tuple;
- }
-
- /*
* Generic function to change the ownership of a given object, for simple
* cases (won't work for tables, nor other cases where we need to do more than
* change the ownership column of a single catalog entry).
--- 748,753 ----
*** 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;
/*
***************
*** 826,828 **** EventTriggerSupportsObjectType(ObjectType obtype)
--- 847,1080 ----
}
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/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 5219,5230 **** opt_restart_seqs:
*
* COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW |
* COLLATION | CONVERSION | LANGUAGE | OPERATOR CLASS |
! * LARGE OBJECT | CAST | COLUMN | SCHEMA | TABLESPACE |
* EXTENSION | ROLE | TEXT SEARCH PARSER |
* TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
* TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
* FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER |
* MATERIALIZED VIEW] <objname> |
* AGGREGATE <aggname> (arg1, ...) |
* FUNCTION <funcname> (arg1, arg2, ...) |
* OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
--- 5219,5231 ----
*
* COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW |
* COLLATION | CONVERSION | LANGUAGE | OPERATOR CLASS |
! * LARGE OBJECT | COLUMN | SCHEMA | TABLESPACE |
* EXTENSION | ROLE | TEXT SEARCH PARSER |
* TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
* TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
* FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER |
* MATERIALIZED VIEW] <objname> |
+ * CAST (<typename> AS <typename) |
* AGGREGATE <aggname> (arg1, ...) |
* FUNCTION <funcname> (arg1, arg2, ...) |
* OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
*** 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
***************
*** 304,309 **** regprocedurein(PG_FUNCTION_ARGS)
--- 304,322 ----
char *
format_procedure(Oid procedure_oid)
{
+ return format_procedure_internal(procedure_oid, true);
+ }
+
+ /*
+ * Routine to produce regprocedure names; see format_procedure above.
+ *
+ * opt_qualify says whether to schema-qualify; if false, the name is never
+ * qualified, regardless of search_path visibility. If true it is only
+ * qualified if the function is not in path.
+ */
+ char *
+ format_procedure_internal(Oid procedure_oid, bool opt_qualify)
+ {
char *result;
HeapTuple proctup;
***************
*** 324,332 **** format_procedure(Oid procedure_oid)
/*
* Would this proc be found (given the right args) by regprocedurein?
! * If not, we need to qualify it.
*/
! if (FunctionIsVisible(procedure_oid))
nspname = NULL;
else
nspname = get_namespace_name(procform->pronamespace);
--- 337,345 ----
/*
* Would this proc be found (given the right args) by regprocedurein?
! * If not, we need to qualify it -- unless caller wants it bare.
*/
! if (!opt_qualify || FunctionIsVisible(procedure_oid))
nspname = NULL;
else
nspname = get_namespace_name(procform->pronamespace);
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
***************
*** 18,23 ****
--- 18,24 ----
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+ #include "access/sysattr.h"
#include "bootstrap/bootstrap.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
***************
*** 40,45 ****
--- 41,47 ----
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
+ #include "utils/tqual.h"
#include "utils/typcache.h"
/* Hook for plugins to get control in get_attavgwidth() */
***************
*** 2926,2928 **** get_range_subtype(Oid rangeOid)
--- 2928,2981 ----
else
return InvalidOid;
}
+
+ /* ------------- GENERIC -------------- */
+
+ /*
+ * Return a copy of the tuple for the object with the given object OID, from
+ * the given catalog (which must have been opened by the caller and suitably
+ * locked). NULL is returned if the OID is not found.
+ *
+ * We try a syscache first, if available.
+ */
+ HeapTuple
+ get_catalog_object_by_oid(Relation catalog, Oid objectId)
+ {
+ HeapTuple tuple;
+ Oid classId = RelationGetRelid(catalog);
+ int oidCacheId = get_object_catcache_oid(classId);
+
+ if (oidCacheId > 0)
+ {
+ tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId));
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ return NULL;
+ }
+ else
+ {
+ Oid oidIndexId = get_object_oid_index(classId);
+ SysScanDesc scan;
+ ScanKeyData skey;
+
+ Assert(OidIsValid(oidIndexId));
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(catalog, oidIndexId, true,
+ SnapshotNow, 1, &skey);
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ {
+ systable_endscan(scan);
+ return NULL;
+ }
+ tuple = heap_copytuple(tuple);
+
+ systable_endscan(scan);
+ }
+
+ return tuple;
+ }
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 179,184 **** extern ObjectClass getObjectClass(const ObjectAddress *object);
--- 179,187 ----
extern char *getObjectDescription(const ObjectAddress *object);
extern char *getObjectDescriptionOids(Oid classid, Oid objid);
+ extern char *getObjectTypeDescription(const ObjectAddress *object);
+ extern char *getObjectIdentity(const ObjectAddress *address);
+
extern ObjectAddresses *new_object_addresses(void);
extern void add_exact_object_address(const ObjectAddress *object,
*** a/src/include/catalog/objectaddress.h
--- b/src/include/catalog/objectaddress.h
***************
*** 38,43 **** extern void check_object_ownership(Oid roleid,
--- 38,44 ----
extern Oid get_object_namespace(const ObjectAddress *address);
+ extern bool is_objectclass_supported(Oid class_id);
extern Oid get_object_oid_index(Oid class_id);
extern int get_object_catcache_oid(Oid class_id);
extern int get_object_catcache_name(Oid class_id);
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4690,4695 **** DATA(insert OID = 3473 ( spg_range_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0
--- 4690,4698 ----
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
***************
*** 615,620 **** extern Datum regdictionarysend(PG_FUNCTION_ARGS);
--- 615,621 ----
extern Datum text_regclass(PG_FUNCTION_ARGS);
extern List *stringToQualifiedNameList(const char *string);
extern char *format_procedure(Oid procedure_oid);
+ extern char *format_procedure_internal(Oid procedure_oid, bool opt_qualify);
extern char *format_operator(Oid operator_oid);
/* rowtypes.c */
***************
*** 1147,1152 **** extern Datum pg_describe_object(PG_FUNCTION_ARGS);
--- 1148,1156 ----
/* 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/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
***************
*** 16,21 ****
--- 16,22 ----
#include "access/attnum.h"
#include "access/htup.h"
#include "nodes/pg_list.h"
+ #include "utils/relcache.h"
/* Result list element for get_op_btree_interpretation */
typedef struct OpBtreeInterpretation
***************
*** 152,157 **** extern void free_attstatsslot(Oid atttype,
--- 153,159 ----
float4 *numbers, int nnumbers);
extern char *get_namespace_name(Oid nspid);
extern Oid get_range_subtype(Oid rangeOid);
+ extern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);
#define type_is_array(typid) (get_element_type(typid) != InvalidOid)
/* type_is_array_domain accepts both plain arrays and domains over arrays */
*** 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;
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
Attachments:
dropped_objects.9.patchtext/x-diff; charset=us-asciiDownload
*** 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;
On Wed, Mar 20, 2013 at 5:42 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
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.
I think this is getting there. A few things to think about:
- pg_event_trigger_dropped_objects seems to assume that
currentEventTriggerState will be pointing to the same list on every
call. But is that necessarily true? I'm thinking about a case where
someone opens a cursor in an event trigger and then tries to read from
that cursor later in the transaction. I think you might be able to
crash the server that way.
- I am not wild about the idea of propagating PG_TRY/PG_CATCH blocks
into yet more places. On Linux-x86 they are pretty cheap because
Linux doesn't need a system call to change the signal mask and x86 has
few registers that must be saved-and-restored, but elsewhere this can
be a performance problem. Now maybe ProcessUtility is not a
sufficiently-frequently called function for this to matter... but I'm
not sure. The alternative is to teach the error handling pathways
about this in somewhat greater detail, since the point of TRY/CATCH is
to cleanup things that the regular error handling stuff doesn't now
about.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Wed, Mar 20, 2013 at 5:42 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote: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.I think this is getting there. A few things to think about:
Thanks.
- pg_event_trigger_dropped_objects seems to assume that
currentEventTriggerState will be pointing to the same list on every
call. But is that necessarily true? I'm thinking about a case where
someone opens a cursor in an event trigger and then tries to read from
that cursor later in the transaction. I think you might be able to
crash the server that way.
Well, no, because it uses materialized return mode, so there's no "next
time" --- the list is constructed completely before
pg_event_trigger_dropped_objects returns. So there's no such hole.
- I am not wild about the idea of propagating PG_TRY/PG_CATCH blocks
into yet more places. On Linux-x86 they are pretty cheap because
Linux doesn't need a system call to change the signal mask and x86 has
few registers that must be saved-and-restored, but elsewhere this can
be a performance problem. Now maybe ProcessUtility is not a
sufficiently-frequently called function for this to matter... but I'm
not sure. The alternative is to teach the error handling pathways
about this in somewhat greater detail, since the point of TRY/CATCH is
to cleanup things that the regular error handling stuff doesn't now
about.
I tried this and it doesn't work. The "error pathways" you speak about
would be the xact.c entry points to commit and abort transactions;
however, there's a problem with that because some of the commands that
ProcessUtility() deals with have their own transaction management
calls internally; so those would call CommitTransaction() and the
event trigger state would go away, and then when control gets back to
ProcessUtility there would be nothing to clean up. I think we could
ignore the problem, or install smarts in ProcessUtility to avoid calling
event_trigger.c when one of those commands is involved -- but this seems
to me a solution worse than the problem. So all in all I feel like the
macro on top of PG_TRY is the way to go.
Now there *is* one rather big performance problem in this patch, which
is that it turns on collection of object dropped data regardless of
there being event triggers that use the info at all. That's a serious
drawback and we're going to get complaints about it. So we need to do
something to fix that.
One idea that comes to mind is to add some more things to the grammar,
CREATE EVENT TRIGGER name ... WITH ('DROPPED OBJECTS');
or some such, so that when events happen for which any triggers have
that flag enabled, *then* collecting is activated, otherwise not. This
would be stored in a new column in pg_event_trigger (say "evtsupport", a
char array much like proargmodes).
The sequence of (ahem) events goes like this:
ProcessUtility()
EventTriggerBeginCompleteQuery()
EventTriggerDDLCommandStart()
EventCacheLookup()
EventTriggerInvoke()
.. run whatever command we've been handed ...
EventTriggerDDLCommandEnd()
EventCacheLookup()
EventTriggerInvoke()
EventTriggerEndCompleteQuery()
So EventTriggerBeginCompleteQuery() will have to peek into the event
trigger cache for any ddl_command_end triggers that might apply, and see
if any of them has the flag for "dropped objects". If it's there, then
enable dropped object collection.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Now there *is* one rather big performance problem in this patch, which
is that it turns on collection of object dropped data regardless of
there being event triggers that use the info at all. That's a serious
drawback and we're going to get complaints about it. So we need to do
something to fix that.
One idea that comes to mind is to add some more things to the grammar,
CREATE EVENT TRIGGER name ... WITH ('DROPPED OBJECTS');
Uh ... surely we can just notice whether there's a trigger on the
object-drop event? I don't understand why we need *yet more*
mechanism here.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane escribió:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Now there *is* one rather big performance problem in this patch, which
is that it turns on collection of object dropped data regardless of
there being event triggers that use the info at all. That's a serious
drawback and we're going to get complaints about it. So we need to do
something to fix that.One idea that comes to mind is to add some more things to the grammar,
CREATE EVENT TRIGGER name ... WITH ('DROPPED OBJECTS');Uh ... surely we can just notice whether there's a trigger on the
object-drop event? I don't understand why we need *yet more*
mechanism here.
There's no object-drop event, only ddl_command_end. From previous
discussion I understood we didn't want a separate event, so that's what
we've been running with.
However, I think previous discussions have conflated many different
things, and we've been slowly fixing them one by one; so perhaps at this
point it does make sense to have a new object-drop event. Let's discuss
it -- we would define it as taking place just before ddl_command_end,
and firing any time a command (with matching tag?) has called
performDeletion or performMultipleDeletions. Does that sound okay?
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Mar 26, 2013 at 3:02 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
I tried this and it doesn't work. The "error pathways" you speak about
would be the xact.c entry points to commit and abort transactions;
however, there's a problem with that because some of the commands that
ProcessUtility() deals with have their own transaction management
calls internally; so those would call CommitTransaction() and the
event trigger state would go away, and then when control gets back to
ProcessUtility there would be nothing to clean up. I think we could
ignore the problem, or install smarts in ProcessUtility to avoid calling
event_trigger.c when one of those commands is involved -- but this seems
to me a solution worse than the problem. So all in all I feel like the
macro on top of PG_TRY is the way to go.
I see. :-(
Now there *is* one rather big performance problem in this patch, which
is that it turns on collection of object dropped data regardless of
there being event triggers that use the info at all. That's a serious
drawback and we're going to get complaints about it. So we need to do
something to fix that.
Really? Who is going to care about that? Surely that overhead is
quite trivial.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Tue, Mar 26, 2013 at 3:02 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Now there *is* one rather big performance problem in this patch, which
is that it turns on collection of object dropped data regardless of
there being event triggers that use the info at all. That's a serious
drawback and we're going to get complaints about it. So we need to do
something to fix that.Really? Who is going to care about that? Surely that overhead is
quite trivial.
I don't think it is, because it involves syscache lookups for each
object being dropped, many extra pallocs, etc. Surely that's many times
bigger than the PG_TRY overhead you were worried about.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera escribió:
However, I think previous discussions have conflated many different
things, and we've been slowly fixing them one by one; so perhaps at this
point it does make sense to have a new object-drop event. Let's discuss
it -- we would define it as taking place just before ddl_command_end,
and firing any time a command (with matching tag?) has called
performDeletion or performMultipleDeletions. Does that sound okay?
Here's another version of this patch. I hope this is really final now
... but then, that's what I thought of the previous two versions, too.
I have re-instated event sql_drop, which takes place just before
ddl_command_end, and is called a single time per command (not once per
object dropped, as the old definition would have had it). Within a
function running in that event, you can call
pg_event_trigger_dropped_object(); that will give you a list of all
objects that have been dropped. Since the deletion command has already
been run, the objects are not in catalogs anymore. There are no magic
TG_* variables about objects deleted. I'm a bit unhappy about having to
add calls to EventTriggerSQLDrop() just before each call to
EventTriggerDDLCommandEnd(), but I didn't see any way to make this less
duplicative.
Docs and regression tests have been minimally fixed.
The new event is called sql_drop, note.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.10.patchtext/x-diff; charset=us-asciiDownload
[1mdiff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml[m
[1mindex 71241c8..492a4ed 100644[m
[1m--- a/doc/src/sgml/event-trigger.sgml[m
[1m+++ b/doc/src/sgml/event-trigger.sgml[m
[36m@@ -27,17 +27,19 @@[m
<para>[m
An event trigger fires whenever the event with which it is associated[m
occurs in the database in which it is defined. Currently, the only[m
[31m- supported events are <literal>ddl_command_start</>[m
[31m- and <literal>ddl_command_end</>. Support for additional events may be[m
[31m- added in future releases.[m
[32m+[m[32m supported events are[m
[32m+[m[32m <literal>ddl_command_start</>,[m
[32m+[m[32m <literal>ddl_command_end</>[m
[32m+[m[32m and <literal>sql_drop</>.[m
[32m+[m[32m Support for additional events may be added in future releases.[m
</para>[m
[m
<para>[m
The <literal>ddl_command_start</> event occurs just before the[m
execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>DROP</>[m
command. As an exception, however, this event does not occur for[m
[31m- DDL commands targeting shared objects - databases, roles, and tablespaces[m
[31m- - or for command targeting event triggers themselves. The event trigger[m
[32m+[m[32m DDL commands targeting shared objects — databases, roles, and tablespaces[m
[32m+[m[32m — or for command targeting event triggers themselves. The event trigger[m
mechanism does not support these object types.[m
<literal>ddl_command_start</> also occurs just before the execution of a[m
<literal>SELECT INTO</literal> command, since this is equivalent to[m
[36m@@ -46,6 +48,17 @@[m
</para>[m
[m
<para>[m
[32m+[m[32m The <literal>sql_drop</> event occurs just before the[m
[32m+[m[32m <literal>ddl_command_end</> event trigger for any operation that drops[m
[32m+[m[32m database objects. To list the objects that have been dropped, use the set[m
[32m+[m[32m returning function <literal>pg_event_trigger_dropped_objects()</> from your[m
[32m+[m[32m <literal>sql_drop</> event trigger code (see[m
[32m+[m[32m <xref linkend="functions-event-triggers">). Note that[m
[32m+[m[32m the trigger is executed after the objects have been deleted from the[m
[32m+[m[32m system catalogs, so it's not possible to look them up anymore.[m
[32m+[m[32m </para>[m
[32m+[m
[32m+[m[32m <para>[m
Event triggers (like other functions) cannot be executed in an aborted[m
transaction. Thus, if a DDL command fails with an error, any associated[m
<literal>ddl_command_end</> triggers will not be executed. Conversely,[m
[36m@@ -99,6 +112,7 @@[m
<entry>command tag</entry>[m
<entry><literal>ddl_command_start</literal></entry>[m
<entry><literal>ddl_command_end</literal></entry>[m
[32m+[m[32m <entry><literal>sql_drop</literal></entry>[m
</row>[m
</thead>[m
<tbody>[m
[36m@@ -106,401 +120,487 @@[m
<entry align="left"><literal>ALTER AGGREGATE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER COLLATION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER CONVERSION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER DOMAIN</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER EXTENSION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER FOREIGN DATA WRAPPER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER FOREIGN TABLE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER FUNCTION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER LANGUAGE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER OPERATOR</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER OPERATOR CLASS</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER OPERATOR FAMILY</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER SCHEMA</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER SEQUENCE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER SERVER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER TABLE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER TEXT SEARCH CONFIGURATION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER TEXT SEARCH DICTIONARY</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER TEXT SEARCH PARSER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER TEXT SEARCH TEMPLATE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER TRIGGER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER TYPE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER USER MAPPING</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>ALTER VIEW</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE AGGREGATE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE CAST</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE COLLATION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE CONVERSION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE DOMAIN</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE EXTENSION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE FOREIGN DATA WRAPPER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE FOREIGN TABLE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE FUNCTION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE INDEX</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE LANGUAGE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE OPERATOR</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE OPERATOR CLASS</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE OPERATOR FAMILY</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE RULE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE SCHEMA</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE SEQUENCE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE SERVER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TABLE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TABLE AS</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TEXT SEARCH CONFIGURATION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TEXT SEARCH DICTIONARY</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TEXT SEARCH PARSER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TEXT SEARCH TEMPLATE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TRIGGER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE TYPE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE USER MAPPING</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>CREATE VIEW</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP AGGREGATE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP CAST</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP COLLATION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP CONVERSION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP DOMAIN</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP EXTENSION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP FOREIGN DATA WRAPPER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP FOREIGN TABLE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP FUNCTION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP INDEX</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP LANGUAGE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP OPERATOR</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP OPERATOR CLASS</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP OPERATOR FAMILY</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m </row>[m
[32m+[m[32m <row>[m
[32m+[m[32m <entry align="left"><literal>DROP OWNED</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP RULE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP SCHEMA</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP SEQUENCE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP SERVER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP TABLE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP TEXT SEARCH CONFIGURATION</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP TEXT SEARCH DICTIONARY</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP TEXT SEARCH PARSER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP TEXT SEARCH TEMPLATE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP TRIGGER</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP TYPE</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP USER MAPPING</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>DROP VIEW</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>X</literal></entry>[m
</row>[m
<row>[m
<entry align="left"><literal>SELECT INTO</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
<entry align="center"><literal>X</literal></entry>[m
[32m+[m[32m <entry align="center"><literal>-</literal></entry>[m
</row>[m
</tbody>[m
</tgroup>[m
[1mdiff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml[m
[1mindex 490d710..748a15d 100644[m
[1m--- a/doc/src/sgml/func.sgml[m
[1m+++ b/doc/src/sgml/func.sgml[m
[36m@@ -15980,4 +15980,51 @@[m [mFOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();[m
<xref linkend="SQL-CREATETRIGGER">.[m
</para>[m
</sect1>[m
[32m+[m
[32m+[m[32m <sect1 id="functions-event-triggers">[m
[32m+[m[32m <title>Event Trigger Functions</title>[m
[32m+[m
[32m+[m[32m <indexterm>[m
[32m+[m[32m <primary>pg_event_trigger_dropped_objects</primary>[m
[32m+[m[32m </indexterm>[m
[32m+[m
[32m+[m[32m <para>[m
[32m+[m[32m Currently <productname>PostgreSQL</> provides one built-in event trigger[m
[32m+[m[32m helper function, <function>pg_event_trigger_dropped_objects</>, which[m
[32m+[m[32m lists all object dropped by the command in whose <literal>sql_drop</>[m
[32m+[m[32m event it is called.[m
[32m+[m[32m </para>[m
[32m+[m
[32m+[m[32m <para>[m
[32m+[m[32m The <function>pg_event_trigger_dropped_objects</> function can be used[m
[32m+[m[32m in an event trigger like this:[m
[32m+[m[32m<programlisting>[m
[32m+[m[32mCREATE FUNCTION test_event_trigger_for_drops()[m
[32m+[m[32m RETURNS event_trigger LANGUAGE plpgsql AS $$[m
[32m+[m[32mDECLARE[m
[32m+[m[32m obj record;[m
[32m+[m[32mBEGIN[m
[32m+[m[32m FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()[m
[32m+[m[32m LOOP[m
[32m+[m[32m RAISE NOTICE '% dropped object: % %.% %',[m
[32m+[m[32m tg_tag,[m
[32m+[m[32m obj.object_type,[m
[32m+[m[32m obj.schema_name,[m
[32m+[m[32m obj.object_name,[m
[32m+[m[32m obj.subobject_name;[m
[32m+[m[32m END LOOP;[m
[32m+[m[32mEND[m
[32m+[m[32m$$;[m
[32m+[m[32mCREATE EVENT TRIGGER test_event_trigger_for_drops[m
[32m+[m[32m ON ddl_command_end[m
[32m+[m[32m EXECUTE PROCEDURE test_event_trigger_for_drops();[m
[32m+[m[32m</programlisting>[m
[32m+[m[32m </para>[m
[32m+[m
[32m+[m[32m <para>[m
[32m+[m[32m For more information about event triggers,[m
[32m+[m[32m see <xref linkend="event-triggers">.[m
[32m+[m[32m </para>[m
[32m+[m[32m </sect1>[m
[32m+[m
</chapter>[m
[1mdiff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c[m
[1mindex ddf1990..286137c 100644[m
[1m--- a/src/backend/catalog/dependency.c[m
[1m+++ b/src/backend/catalog/dependency.c[m
[36m@@ -191,6 +191,44 @@[m [mstatic bool stack_address_present_add_flags(const ObjectAddress *object,[m
[m
[m
/*[m
[32m+[m[32m * Go through the objects given running the final actions on them, and execute[m
[32m+[m[32m * the actual deletion.[m
[32m+[m[32m */[m
[32m+[m[32mstatic void[m
[32m+[m[32mdeleteObjectsInList(ObjectAddresses *targetObjects, Relation *depRel,[m
[32m+[m [32mint flags)[m
[32m+[m[32m{[m
[32m+[m [32mint i;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * Keep track of objects for event triggers, if necessary.[m
[32m+[m [32m */[m
[32m+[m [32mif (trackDroppedObjectsNeeded())[m
[32m+[m [32m{[m
[32m+[m [32mfor (i = 0; i < targetObjects->numrefs; i++)[m
[32m+[m [32m{[m
[32m+[m [32mObjectAddress *thisobj = targetObjects->refs + i;[m
[32m+[m
[32m+[m [32mif ((!(flags & PERFORM_DELETION_INTERNAL)) &&[m
[32m+[m [32mEventTriggerSupportsObjectType(getObjectClass(thisobj)))[m
[32m+[m [32m{[m
[32m+[m [32mEventTriggerSQLDropAddObject(thisobj);[m
[32m+[m [32m}[m
[32m+[m [32m}[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * Delete all the objects in the proper order.[m
[32m+[m [32m */[m
[32m+[m [32mfor (i = 0; i < targetObjects->numrefs; i++)[m
[32m+[m [32m{[m
[32m+[m [32mObjectAddress *thisobj = targetObjects->refs + i;[m
[32m+[m
[32m+[m [32mdeleteOneObject(thisobj, depRel, flags);[m
[32m+[m [32m}[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
* performDeletion: attempt to drop the specified object. If CASCADE[m
* behavior is specified, also drop any dependent objects (recursively).[m
* If RESTRICT behavior is specified, error out if there are any dependent[m
[36m@@ -215,7 +253,6 @@[m [mperformDeletion(const ObjectAddress *object,[m
{[m
Relation depRel;[m
ObjectAddresses *targetObjects;[m
[31m- int i;[m
[m
/*[m
* We save some cycles by opening pg_depend just once and passing the[m
[36m@@ -250,15 +287,8 @@[m [mperformDeletion(const ObjectAddress *object,[m
NOTICE,[m
object);[m
[m
[31m- /*[m
[31m- * Delete all the objects in the proper order.[m
[31m- */[m
[31m- for (i = 0; i < targetObjects->numrefs; i++)[m
[31m- {[m
[31m- ObjectAddress *thisobj = targetObjects->refs + i;[m
[31m-[m
[31m- deleteOneObject(thisobj, &depRel, flags);[m
[31m- }[m
[32m+[m [32m/* do the deed */[m
[32m+[m [32mdeleteObjectsInList(targetObjects, &depRel, flags);[m
[m
/* And clean up */[m
free_object_addresses(targetObjects);[m
[36m@@ -332,15 +362,8 @@[m [mperformMultipleDeletions(const ObjectAddresses *objects,[m
NOTICE,[m
(objects->numrefs == 1 ? objects->refs : NULL));[m
[m
[31m- /*[m
[31m- * Delete all the objects in the proper order.[m
[31m- */[m
[31m- for (i = 0; i < targetObjects->numrefs; i++)[m
[31m- {[m
[31m- ObjectAddress *thisobj = targetObjects->refs + i;[m
[31m-[m
[31m- deleteOneObject(thisobj, &depRel, flags);[m
[31m- }[m
[32m+[m [32m/* do the deed */[m
[32m+[m [32mdeleteObjectsInList(targetObjects, &depRel, flags);[m
[m
/* And clean up */[m
free_object_addresses(targetObjects);[m
[36m@@ -356,6 +379,10 @@[m [mperformMultipleDeletions(const ObjectAddresses *objects,[m
* This is currently used only to clean out the contents of a schema[m
* (namespace): the passed object is a namespace. We normally want this[m
* to be done silently, so there's an option to suppress NOTICE messages.[m
[32m+[m[32m *[m
[32m+[m[32m * Note we don't fire object drop event triggers here; it would be wrong to do[m
[32m+[m[32m * so for the current only use of this function, but if more callers are added[m
[32m+[m[32m * this might need to be reconsidered.[m
*/[m
void[m
deleteWhatDependsOn(const ObjectAddress *object,[m
[1mdiff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c[m
[1mindex fbe8f49..fa00c1e 100644[m
[1m--- a/src/backend/commands/event_trigger.c[m
[1m+++ b/src/backend/commands/event_trigger.c[m
[36m@@ -19,12 +19,14 @@[m
#include "catalog/indexing.h"[m
#include "catalog/objectaccess.h"[m
#include "catalog/pg_event_trigger.h"[m
[32m+[m[32m#include "catalog/pg_namespace.h"[m
#include "catalog/pg_proc.h"[m
#include "catalog/pg_trigger.h"[m
#include "catalog/pg_type.h"[m
#include "commands/dbcommands.h"[m
#include "commands/event_trigger.h"[m
#include "commands/trigger.h"[m
[32m+[m[32m#include "funcapi.h"[m
#include "parser/parse_func.h"[m
#include "pgstat.h"[m
#include "miscadmin.h"[m
[36m@@ -39,6 +41,16 @@[m
#include "utils/syscache.h"[m
#include "tcop/utility.h"[m
[m
[32m+[m
[32m+[m[32mtypedef struct EventTriggerQueryState[m
[32m+[m[32m{[m
[32m+[m [32mslist_head SQLDropList;[m
[32m+[m [32mMemoryContext cxt;[m
[32m+[m [32mstruct EventTriggerQueryState *previous;[m
[32m+[m[32m} EventTriggerQueryState;[m
[32m+[m
[32m+[m[32mEventTriggerQueryState *currentEventTriggerState = NULL;[m
[32m+[m
typedef struct[m
{[m
const char *obtypename;[m
[36m@@ -89,6 +101,17 @@[m [mstatic event_trigger_support_data event_trigger_support[] = {[m
{ NULL, false }[m
};[m
[m
[32m+[m[32m/* Support for dropped objects */[m
[32m+[m[32mtypedef struct SQLDropObject[m
[32m+[m[32m{[m
[32m+[m [32mObjectAddress address;[m
[32m+[m [32mchar *schemaname;[m
[32m+[m [32mchar *objname;[m
[32m+[m [32mchar *objidentity;[m
[32m+[m [32mchar *objecttype;[m
[32m+[m [32mslist_node next;[m
[32m+[m[32m} SQLDropObject;[m
[32m+[m
static void AlterEventTriggerOwner_internal(Relation rel,[m
HeapTuple tup,[m
Oid newOwnerId);[m
[36m@@ -127,7 +150,8 @@[m [mCreateEventTrigger(CreateEventTrigStmt *stmt)[m
[m
/* Validate event name. */[m
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&[m
[31m- strcmp(stmt->eventname, "ddl_command_end") != 0)[m
[32m+[m [32mstrcmp(stmt->eventname, "ddl_command_end") != 0 &&[m
[32m+[m [32mstrcmp(stmt->eventname, "sql_drop") != 0)[m
ereport(ERROR,[m
(errcode(ERRCODE_SYNTAX_ERROR),[m
errmsg("unrecognized event name \"%s\"",[m
[36m@@ -151,7 +175,10 @@[m [mCreateEventTrigger(CreateEventTrigStmt *stmt)[m
}[m
[m
/* Validate tag list, if any. */[m
[31m- if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL)[m
[32m+[m [32mif ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||[m
[32m+[m [32m strcmp(stmt->eventname, "ddl_command_end") == 0 ||[m
[32m+[m [32m strcmp(stmt->eventname, "sql_drop") == 0)[m
[32m+[m [32m&& tags != NULL)[m
validate_ddl_tags("tag", tags);[m
[m
/*[m
[36m@@ -220,7 +247,8 @@[m [mcheck_ddl_tag(const char *tag)[m
pg_strcasecmp(tag, "SELECT INTO") == 0 ||[m
pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 ||[m
pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||[m
[31m- pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)[m
[32m+[m [32mpg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 ||[m
[32m+[m [32mpg_strcasecmp(tag, "DROP OWNED") == 0)[m
return EVENT_TRIGGER_COMMAND_TAG_OK;[m
[m
/*[m
[36m@@ -749,6 +777,91 @@[m [mEventTriggerDDLCommandEnd(Node *parsetree)[m
}[m
[m
/*[m
[32m+[m[32m * Fire sql_drop triggers.[m
[32m+[m[32m */[m
[32m+[m[32mvoid[m
[32m+[m[32mEventTriggerSQLDrop(Node *parsetree)[m
[32m+[m[32m{[m
[32m+[m [32mList *cachelist;[m
[32m+[m [32mList *runlist = NIL;[m
[32m+[m [32mListCell *lc;[m
[32m+[m [32mconst char *tag;[m
[32m+[m [32mEventTriggerData trigdata;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * See EventTriggerDDLCommandStart for a discussion about why event[m
[32m+[m [32m * triggers are disabled in single user mode.[m
[32m+[m [32m */[m
[32m+[m [32mif (!IsUnderPostmaster)[m
[32m+[m [32mreturn;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * See EventTriggerDDLCommandStart for a discussion about why this check is[m
[32m+[m [32m * important.[m
[32m+[m [32m *[m
[32m+[m [32m */[m
[32m+[m[32m#ifdef USE_ASSERT_CHECKING[m
[32m+[m [32mif (assert_enabled)[m
[32m+[m [32m{[m
[32m+[m [32mconst char *dbgtag;[m
[32m+[m
[32m+[m [32mdbgtag = CreateCommandTag(parsetree);[m
[32m+[m [32mif (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)[m
[32m+[m [32melog(ERROR, "unexpected command tag \"%s\"", dbgtag);[m
[32m+[m [32m}[m
[32m+[m[32m#endif[m
[32m+[m
[32m+[m [32m/* Use current state to determine whether this trigger fires at all */[m
[32m+[m [32mif (!currentEventTriggerState ||[m
[32m+[m [32mslist_is_empty(¤tEventTriggerState->SQLDropList))[m
[32m+[m [32mreturn;[m
[32m+[m
[32m+[m [32m/* Use cache to find triggers for this event; fast exit if none. */[m
[32m+[m [32mcachelist = EventCacheLookup(EVT_SQLDrop);[m
[32m+[m [32mif (cachelist == NULL)[m
[32m+[m [32mreturn;[m
[32m+[m
[32m+[m [32m/* Get the command tag. */[m
[32m+[m [32mtag = CreateCommandTag(parsetree);[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * Filter list of event triggers by command tag, and copy them into[m
[32m+[m [32m * our memory context. Once we start running the command trigers, or[m
[32m+[m [32m * indeed once we do anything at all that touches the catalogs, an[m
[32m+[m [32m * invalidation might leave cachelist pointing at garbage, so we must[m
[32m+[m [32m * do this before we can do much else.[m
[32m+[m [32m */[m
[32m+[m [32mforeach (lc, cachelist)[m
[32m+[m [32m{[m
[32m+[m [32mEventTriggerCacheItem *item = lfirst(lc);[m
[32m+[m
[32m+[m [32mif (filter_event_trigger(&tag, item))[m
[32m+[m [32m{[m
[32m+[m [32m/* We must plan to fire this trigger. */[m
[32m+[m [32mrunlist = lappend_oid(runlist, item->fnoid);[m
[32m+[m [32m}[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32m/* Construct event trigger data. */[m
[32m+[m [32mtrigdata.type = T_EventTriggerData;[m
[32m+[m [32mtrigdata.event = "sql_drop";[m
[32m+[m [32mtrigdata.parsetree = parsetree;[m
[32m+[m [32mtrigdata.tag = tag;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * Make sure anything the main command did will be visible to the[m
[32m+[m [32m * event triggers.[m
[32m+[m [32m */[m
[32m+[m [32mCommandCounterIncrement();[m
[32m+[m
[32m+[m [32m/* Run the triggers. */[m
[32m+[m [32mEventTriggerInvoke(runlist, &trigdata);[m
[32m+[m
[32m+[m [32m/* Cleanup. */[m
[32m+[m [32mlist_free(runlist);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
* Invoke each event trigger in a list of event triggers.[m
*/[m
static void[m
[36m@@ -832,3 +945,279 @@[m [mEventTriggerSupportsObjectType(ObjectType obtype)[m
}[m
return true;[m
}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Prepare event trigger state for a new complete query to run, if necessary;[m
[32m+[m[32m * returns whether this was done. If it was, EventTriggerEndCompleteQuery must[m
[32m+[m[32m * be called when the query is done, regardless of whether it succeeds or fails[m
[32m+[m[32m * -- so use of a PG_TRY block is mandatory.[m
[32m+[m[32m */[m
[32m+[m[32mbool[m
[32m+[m[32mEventTriggerBeginCompleteQuery(void)[m
[32m+[m[32m{[m
[32m+[m [32mEventTriggerQueryState *state;[m
[32m+[m [32mMemoryContext cxt;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * Currently, sql_drop events are the only reason to have event trigger[m
[32m+[m [32m * state at all; so if there are none, don't install one.[m
[32m+[m [32m */[m
[32m+[m [32mif (!trackDroppedObjectsNeeded())[m
[32m+[m [32mreturn false;[m
[32m+[m
[32m+[m [32mcxt = AllocSetContextCreate(TopMemoryContext,[m
[32m+[m [32m"event trigger state",[m
[32m+[m [32mALLOCSET_DEFAULT_MINSIZE,[m
[32m+[m [32mALLOCSET_DEFAULT_INITSIZE,[m
[32m+[m [32mALLOCSET_DEFAULT_MAXSIZE);[m
[32m+[m [32mstate = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));[m
[32m+[m [32mstate->cxt = cxt;[m
[32m+[m [32mslist_init(&(state->SQLDropList));[m
[32m+[m
[32m+[m [32mstate->previous = currentEventTriggerState;[m
[32m+[m [32mcurrentEventTriggerState = state;[m
[32m+[m
[32m+[m [32mreturn true;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Query completed (or errored out) -- clean up local state, return to previous[m
[32m+[m[32m * one.[m
[32m+[m[32m *[m
[32m+[m[32m * Note: it's an error to call this routine if EventTriggerBeginCompleteQuery[m
[32m+[m[32m * returned false previously.[m
[32m+[m[32m *[m
[32m+[m[32m * Note: this might be called in the PG_CATCH block of a failing transaction,[m
[32m+[m[32m * so be wary of running anything unnecessary. (In particular, it's probably[m
[32m+[m[32m * unwise to try to allocate memory.)[m
[32m+[m[32m */[m
[32m+[m[32mvoid[m
[32m+[m[32mEventTriggerEndCompleteQuery(void)[m
[32m+[m[32m{[m
[32m+[m [32mEventTriggerQueryState *prevstate;[m
[32m+[m
[32m+[m [32mprevstate = currentEventTriggerState->previous;[m
[32m+[m
[32m+[m [32m/* this avoids the need for retail pfree of SQLDropList items: */[m
[32m+[m [32mMemoryContextDelete(currentEventTriggerState->cxt);[m
[32m+[m
[32m+[m [32mcurrentEventTriggerState = prevstate;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Do we need to keep close track of objects being dropped?[m
[32m+[m[32m *[m
[32m+[m[32m * This is useful because there is a cost to running with them enabled.[m
[32m+[m[32m */[m
[32m+[m[32mbool[m
[32m+[m[32mtrackDroppedObjectsNeeded(void)[m
[32m+[m[32m{[m
[32m+[m [32m/* true if any sql_drop event trigger exists */[m
[32m+[m [32mreturn list_length(EventCacheLookup(EVT_SQLDrop)) > 0;[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Support for dropped objects information on event trigger functions.[m
[32m+[m[32m *[m
[32m+[m[32m * We keep the list of objects dropped by the current command in current[m
[32m+[m[32m * state's SQLDropList (comprising SQLDropObject items). Each time a new[m
[32m+[m[32m * command is to start, a clean EventTriggerQueryState is created; commands[m
[32m+[m[32m * that drop objects do the dependency.c dance to drop objects, which[m
[32m+[m[32m * populates the current state's SQLDropList; when the event triggers are[m
[32m+[m[32m * invoked they can consume the list via pg_event_trigger_dropped_objects().[m
[32m+[m[32m * When the command finishes, the EventTriggerQueryState is cleared, and[m
[32m+[m[32m * the one from the previous command is restored (when no command is in[m
[32m+[m[32m * execution, the current state is NULL).[m
[32m+[m[32m *[m
[32m+[m[32m * All this lets us support the case that an event trigger function drops[m
[32m+[m[32m * objects "reentrantly".[m
[32m+[m[32m */[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * Register one object as being dropped by the current command.[m
[32m+[m[32m */[m
[32m+[m[32mvoid[m
[32m+[m[32mEventTriggerSQLDropAddObject(ObjectAddress *object)[m
[32m+[m[32m{[m
[32m+[m [32mSQLDropObject *obj;[m
[32m+[m [32mMemoryContext oldcxt;[m
[32m+[m
[32m+[m [32mif (!currentEventTriggerState)[m
[32m+[m [32mreturn;[m
[32m+[m
[32m+[m [32mAssert(EventTriggerSupportsObjectType(getObjectClass(object)));[m
[32m+[m
[32m+[m [32m/* don't report temp schemas */[m
[32m+[m [32mif (object->classId == NamespaceRelationId &&[m
[32m+[m [32misAnyTempNamespace(object->objectId))[m
[32m+[m [32mreturn;[m
[32m+[m
[32m+[m [32moldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);[m
[32m+[m
[32m+[m [32mobj = palloc0(sizeof(SQLDropObject));[m
[32m+[m [32mobj->address = *object;[m
[32m+[m
[32m+[m [32m/*[m
[32m+[m [32m * Obtain schema names from the object's catalog tuple, if one exists;[m
[32m+[m [32m * this lets us skip objects in temp schemas. We trust that ObjectProperty[m
[32m+[m [32m * contains all object classes that can be schema-qualified.[m
[32m+[m [32m */[m
[32m+[m [32mif (is_objectclass_supported(object->classId))[m
[32m+[m [32m{[m
[32m+[m [32mRelation catalog;[m
[32m+[m [32mHeapTuple tuple;[m
[32m+[m
[32m+[m [32mcatalog = heap_open(obj->address.classId, AccessShareLock);[m
[32m+[m [32mtuple = get_catalog_object_by_oid(catalog, obj->address.objectId);[m
[32m+[m
[32m+[m [32mif (tuple)[m
[32m+[m [32m{[m
[32m+[m [32mAttrNumber attnum;[m
[32m+[m [32mDatum datum;[m
[32m+[m [32mbool isnull;[m
[32m+[m
[32m+[m [32mattnum = get_object_attnum_namespace(obj->address.classId);[m
[32m+[m [32mif (attnum != InvalidAttrNumber)[m
[32m+[m [32m{[m
[32m+[m [32mdatum = heap_getattr(tuple, attnum,[m
[32m+[m [32m RelationGetDescr(catalog), &isnull);[m
[32m+[m [32mif (!isnull)[m
[32m+[m [32m{[m
[32m+[m [32mOid namespaceId;[m
[32m+[m
[32m+[m [32mnamespaceId = DatumGetObjectId(datum);[m
[32m+[m [32m/* Don't report objects in temp namespaces */[m
[32m+[m [32mif (isAnyTempNamespace(namespaceId))[m
[32m+[m [32m{[m
[32m+[m [32mpfree(obj);[m
[32m+[m [32mheap_close(catalog, AccessShareLock);[m
[32m+[m [32mMemoryContextSwitchTo(oldcxt);[m
[32m+[m [32mreturn;[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mobj->schemaname = get_namespace_name(namespaceId);[m
[32m+[m [32m}[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mattnum = get_object_attnum_name(obj->address.classId);[m
[32m+[m [32mif (attnum != InvalidAttrNumber)[m
[32m+[m [32m{[m
[32m+[m [32mdatum = heap_getattr(tuple, attnum,[m
[32m+[m [32m RelationGetDescr(catalog), &isnull);[m
[32m+[m [32mif (!isnull)[m
[32m+[m [32mobj->objname = pstrdup(NameStr(*DatumGetName(datum)));[m
[32m+[m [32m}[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32mheap_close(catalog, AccessShareLock);[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32m/* object identity */[m
[32m+[m [32mobj->objidentity = getObjectIdentity(&obj->address);[m
[32m+[m
[32m+[m [32m/* and object type, too */[m
[32m+[m [32mobj->objecttype = getObjectTypeDescription(&obj->address);[m
[32m+[m
[32m+[m [32mslist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);[m
[32m+[m
[32m+[m [32mMemoryContextSwitchTo(oldcxt);[m
[32m+[m[32m}[m
[32m+[m
[32m+[m[32m/*[m
[32m+[m[32m * pg_event_trigger_dropped_objects[m
[32m+[m[32m *[m
[32m+[m[32m * Make the list of dropped objects available to the user function run by the[m
[32m+[m[32m * Event Trigger.[m
[32m+[m[32m */[m
[32m+[m[32mDatum[m
[32m+[m[32mpg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)[m
[32m+[m[32m{[m
[32m+[m [32mReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;[m
[32m+[m [32mTupleDesc tupdesc;[m
[32m+[m [32mTuplestorestate *tupstore;[m
[32m+[m [32mMemoryContext per_query_ctx;[m
[32m+[m [32mMemoryContext oldcontext;[m
[32m+[m [32mslist_iter iter;[m
[32m+[m
[32m+[m [32m/* XXX can this actually happen? */[m
[32m+[m [32mif (!currentEventTriggerState)[m
[32m+[m [32mereport(ERROR,[m
[32m+[m [32m(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),[m
[32m+[m [32m errmsg("%s can only be called when there's a command in execution",[m
[32m+[m [32m"pg_event_trigger_dropped_objects()")));[m
[32m+[m
[32m+[m [32m/* check to see if caller supports us returning a tuplestore */[m
[32m+[m [32mif (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))[m
[32m+[m [32mereport(ERROR,[m
[32m+[m [32m(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),[m
[32m+[m [32m errmsg("set-valued function called in context that cannot accept a set")));[m
[32m+[m [32mif (!(rsinfo->allowedModes & SFRM_Materialize))[m
[32m+[m [32mereport(ERROR,[m
[32m+[m [32m(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),[m
[32m+[m [32m errmsg("materialize mode required, but it is not allowed in this context")));[m
[32m+[m
[32m+[m [32m/* Build a tuple descriptor for our result type */[m
[32m+[m [32mif (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)[m
[32m+[m [32melog(ERROR, "return type must be a row type");[m
[32m+[m
[32m+[m [32m/* Build tuplestore to hold the result rows */[m
[32m+[m [32mper_query_ctx = rsinfo->econtext->ecxt_per_query_memory;[m
[32m+[m [32moldcontext = MemoryContextSwitchTo(per_query_ctx);[m
[32m+[m
[32m+[m [32mtupstore = tuplestore_begin_heap(true, false, work_mem);[m
[32m+[m [32mrsinfo->returnMode = SFRM_Materialize;[m
[32m+[m [32mrsinfo->setResult = tupstore;[m
[32m+[m [32mrsinfo->setDesc = tupdesc;[m
[32m+[m
[32m+[m [32mMemoryContextSwitchTo(oldcontext);[m
[32m+[m
[32m+[m [32mslist_foreach(iter, &(currentEventTriggerState->SQLDropList))[m
[32m+[m [32m{[m
[32m+[m [32mSQLDropObject *obj;[m
[32m+[m [32mint i = 0;[m
[32m+[m [32mDatum values[7];[m
[32m+[m [32mbool nulls[7];[m
[32m+[m
[32m+[m [32mobj = slist_container(SQLDropObject, next, iter.cur);[m
[32m+[m
[32m+[m [32mMemSet(values, 0, sizeof(values));[m
[32m+[m [32mMemSet(nulls, 0, sizeof(nulls));[m
[32m+[m
[32m+[m [32m/* classid */[m
[32m+[m [32mvalues[i++] = ObjectIdGetDatum(obj->address.classId);[m
[32m+[m
[32m+[m [32m/* objid */[m
[32m+[m [32mvalues[i++] = ObjectIdGetDatum(obj->address.objectId);[m
[32m+[m
[32m+[m [32m/* objsubid */[m
[32m+[m [32mvalues[i++] = Int32GetDatum(obj->address.objectSubId);[m
[32m+[m
[32m+[m [32m/* object_type */[m
[32m+[m [32mvalues[i++] = CStringGetTextDatum(obj->objecttype);[m
[32m+[m
[32m+[m [32m/* schema_name */[m
[32m+[m [32mif (obj->schemaname)[m
[32m+[m [32mvalues[i++] = CStringGetTextDatum(obj->schemaname);[m
[32m+[m [32melse[m
[32m+[m [32mnulls[i++] = true;[m
[32m+[m
[32m+[m [32m/* object_name */[m
[32m+[m [32mif (obj->objname)[m
[32m+[m [32mvalues[i++] = CStringGetTextDatum(obj->objname);[m
[32m+[m [32melse[m
[32m+[m [32mnulls[i++] = true;[m
[32m+[m
[32m+[m [32m/* object_identity */[m
[32m+[m [32mif (obj->objidentity)[m
[32m+[m [32mvalues[i++] = CStringGetTextDatum(obj->objidentity);[m
[32m+[m [32melse[m
[32m+[m [32mnulls[i++] = true;[m
[32m+[m
[32m+[m [32mtuplestore_putvalues(tupstore, tupdesc, values, nulls);[m
[32m+[m [32m}[m
[32m+[m
[32m+[m [32m/* clean up and return the tuplestore */[m
[32m+[m [32mtuplestore_donestoring(tupstore);[m
[32m+[m
[32m+[m [32mreturn (Datum) 0;[m
[32m+[m[32m}[m
[1mdiff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y[m
[1mindex 0d82141..ed8502c 100644[m
[1m--- a/src/backend/parser/gram.y[m
[1m+++ b/src/backend/parser/gram.y[m
[36m@@ -4478,7 +4478,6 @@[m [mDropTrigStmt:[m
*[m
* QUERIES :[m
* CREATE EVENT TRIGGER ...[m
[31m- * DROP EVENT TRIGGER ...[m
* ALTER EVENT TRIGGER ...[m
*[m
*****************************************************************************/[m
[1mdiff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c[m
[1mindex a1c03f1..78765c2 100644[m
[1m--- a/src/backend/tcop/utility.c[m
[1m+++ b/src/backend/tcop/utility.c[m
[36m@@ -351,6 +351,7 @@[m [mProcessUtility(Node *parsetree,[m
fncall; \[m
if (isCompleteQuery) \[m
{ \[m
[32m+[m [32mEventTriggerSQLDrop(parsetree); \[m
EventTriggerDDLCommandEnd(parsetree); \[m
} \[m
} while (0)[m
[36m@@ -366,10 +367,48 @@[m [mProcessUtility(Node *parsetree,[m
fncall; \[m
if (_supported) \[m
{ \[m
[32m+[m [32mEventTriggerSQLDrop(parsetree); \[m
EventTriggerDDLCommandEnd(parsetree); \[m
} \[m
} while (0)[m
[m
[32m+[m[32m/*[m
[32m+[m[32m * UTILITY_BEGIN_QUERY and UTILITY_END_QUERY are a pair of macros to enclose[m
[32m+[m[32m * execution of a single DDL command, to ensure the event trigger environment[m
[32m+[m[32m * is appropriately set up before starting, and tore down after completion or[m
[32m+[m[32m * error.[m
[32m+[m[32m */[m
[32m+[m[32m#define UTILITY_BEGIN_QUERY(isComplete) \[m
[32m+[m [32mdo { \[m
[32m+[m [32mbool _needCleanup = false; \[m
[32m+[m [32m\[m
[32m+[m [32mif (isComplete) \[m
[32m+[m [32m{ \[m
[32m+[m [32m_needCleanup = EventTriggerBeginCompleteQuery(); \[m
[32m+[m [32m} \[m
[32m+[m [32m\[m
[32m+[m [32mPG_TRY(); \[m
[32m+[m [32m{ \[m
[32m+[m [32m/* avoid empty statement when followed by a semicolon */ \[m
[32m+[m [32m(void) 0[m
[32m+[m
[32m+[m[32m#define UTILITY_END_QUERY() \[m
[32m+[m [32m} \[m
[32m+[m [32mPG_CATCH(); \[m
[32m+[m [32m{ \[m
[32m+[m [32mif (_needCleanup) \[m
[32m+[m [32m{ \[m
[32m+[m [32mEventTriggerEndCompleteQuery(); \[m
[32m+[m [32m} \[m
[32m+[m [32mPG_RE_THROW(); \[m
[32m+[m [32m} \[m
[32m+[m [32mPG_END_TRY(); \[m
[32m+[m [32mif (_needCleanup) \[m
[32m+[m [32m{ \[m
[32m+[m [32mEventTriggerEndCompleteQuery(); \[m
[32m+[m [32m} \[m
[32m+[m [32m} while (0)[m
[32m+[m
void[m
standard_ProcessUtility(Node *parsetree,[m
const char *queryString,[m
[36m@@ -386,6 +425,8 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
if (completionTag)[m
completionTag[0] = '\0';[m
[m
[32m+[m [32mUTILITY_BEGIN_QUERY(isCompleteQuery);[m
[32m+[m
switch (nodeTag(parsetree))[m
{[m
/*[m
[36m@@ -615,7 +656,10 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
}[m
[m
if (isCompleteQuery)[m
[32m+[m [32m{[m
[32m+[m [32mEventTriggerSQLDrop(parsetree);[m
EventTriggerDDLCommandEnd(parsetree);[m
[32m+[m [32m}[m
}[m
break;[m
[m
[36m@@ -726,7 +770,10 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
[m
if (isCompleteQuery[m
&& EventTriggerSupportsObjectType(stmt->removeType))[m
[32m+[m [32m{[m
[32m+[m [32mEventTriggerSQLDrop(parsetree);[m
EventTriggerDDLCommandEnd(parsetree);[m
[32m+[m [32m}[m
[m
break;[m
}[m
[36m@@ -856,6 +903,12 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
ereport(NOTICE,[m
(errmsg("relation \"%s\" does not exist, skipping",[m
atstmt->relation->relname)));[m
[32m+[m
[32m+[m [32mif (isCompleteQuery)[m
[32m+[m [32m{[m
[32m+[m [32mEventTriggerSQLDrop(parsetree);[m
[32m+[m [32mEventTriggerDDLCommandEnd(parsetree);[m
[32m+[m [32m}[m
}[m
break;[m
[m
[36m@@ -1248,8 +1301,9 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
break;[m
[m
case T_DropOwnedStmt:[m
[31m- /* no event triggers for global objects */[m
[31m- DropOwnedObjects((DropOwnedStmt *) parsetree);[m
[32m+[m [32mInvokeDDLCommandEventTriggers([m
[32m+[m [32mparsetree,[m
[32m+[m [32mDropOwnedObjects((DropOwnedStmt *) parsetree));[m
break;[m
[m
case T_ReassignOwnedStmt:[m
[36m@@ -1372,6 +1426,8 @@[m [mstandard_ProcessUtility(Node *parsetree,[m
(int) nodeTag(parsetree));[m
break;[m
}[m
[32m+[m
[32m+[m [32mUTILITY_END_QUERY();[m
}[m
[m
/*[m
[1mdiff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c[m
[1mindex 94599aa..9e1706b 100644[m
[1m--- a/src/backend/utils/adt/regproc.c[m
[1m+++ b/src/backend/utils/adt/regproc.c[m
[36m@@ -345,7 +345,7 @@[m [mformat_procedure_internal(Oid procedure_oid, bool force_qualify)[m
[m
/*[m
* Would this proc be found (given the right args) by regprocedurein?[m
[31m- * If not, we need to qualify it.[m
[32m+[m [32m * If not, we need to qualify it -- unless caller wants it bare.[m
*/[m
if (!force_qualify && FunctionIsVisible(procedure_oid))[m
nspname = NULL;[m
[1mdiff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c[m
[1mindex 34c6128..bbd3ae3 100644[m
[1m--- a/src/backend/utils/cache/evtcache.c[m
[1m+++ b/src/backend/utils/cache/evtcache.c[m
[36m@@ -169,6 +169,8 @@[m [mBuildEventTriggerCache(void)[m
event = EVT_DDLCommandStart;[m
else if (strcmp(evtevent, "ddl_command_end") == 0)[m
event = EVT_DDLCommandEnd;[m
[32m+[m [32melse if (strcmp(evtevent, "sql_drop") == 0)[m
[32m+[m [32mevent = EVT_SQLDrop;[m
else[m
continue;[m
[m
[1mdiff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h[m
[1mindex 4aee002..86bfed4 100644[m
[1m--- a/src/include/catalog/pg_proc.h[m
[1m+++ b/src/include/catalog/pg_proc.h[m
[36m@@ -4693,6 +4693,9 @@[m [mDATA(insert OID = 3473 ( spg_range_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0[m
DESCR("SP-GiST support for quad tree over range");[m
[m
[m
[32m+[m[32m/* event triggers */[m
[32m+[m[32mDATA(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,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_ ));[m
[32m+[m[32mDESCR("list objects dropped by the current command");[m
/*[m
* Symbolic values for provolatile column: these indicate whether the result[m
* of a function is dependent *only* on the values of its explicit arguments,[m
[1mdiff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h[m
[1mindex 74c150b..b11fe6a 100644[m
[1m--- a/src/include/commands/event_trigger.h[m
[1m+++ b/src/include/commands/event_trigger.h[m
[36m@@ -13,7 +13,9 @@[m
#ifndef EVENT_TRIGGER_H[m
#define EVENT_TRIGGER_H[m
[m
[32m+[m[32m#include "catalog/objectaddress.h"[m
#include "catalog/pg_event_trigger.h"[m
[32m+[m[32m#include "lib/ilist.h"[m
#include "nodes/parsenodes.h"[m
[m
typedef struct EventTriggerData[m
[36m@@ -42,5 +44,11 @@[m [mextern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);[m
extern bool EventTriggerSupportsObjectType(ObjectType obtype);[m
extern void EventTriggerDDLCommandStart(Node *parsetree);[m
extern void EventTriggerDDLCommandEnd(Node *parsetree);[m
[32m+[m[32mextern void EventTriggerSQLDrop(Node *parsetree);[m
[32m+[m
[32m+[m[32mextern bool EventTriggerBeginCompleteQuery(void);[m
[32m+[m[32mextern void EventTriggerEndCompleteQuery(void);[m
[32m+[m[32mextern bool trackDroppedObjectsNeeded(void);[m
[32m+[m[32mextern void EventTriggerSQLDropAddObject(ObjectAddress *object);[m
[m
#endif /* EVENT_TRIGGER_H */[m
[1mdiff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h[m
[1mindex cd8ac94..e718765 100644[m
[1m--- a/src/include/utils/builtins.h[m
[1m+++ b/src/include/utils/builtins.h[m
[36m@@ -1151,6 +1151,9 @@[m [mextern Datum pg_identify_object(PG_FUNCTION_ARGS);[m
/* commands/constraint.c */[m
extern Datum unique_key_recheck(PG_FUNCTION_ARGS);[m
[m
[32m+[m[32m/* commands/event_trigger.c */[m
[32m+[m[32mextern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);[m
[32m+[m
/* commands/extension.c */[m
extern Datum pg_available_extensions(PG_FUNCTION_ARGS);[m
extern Datum pg_available_extension_versions(PG_FUNCTION_ARGS);[m
[1mdiff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h[m
[1mindex c230995..945e5b5 100644[m
[1m--- a/src/include/utils/evtcache.h[m
[1m+++ b/src/include/utils/evtcache.h[m
[36m@@ -19,7 +19,8 @@[m
typedef enum[m
{[m
EVT_DDLCommandStart,[m
[31m- EVT_DDLCommandEnd[m
[32m+[m [32mEVT_DDLCommandEnd,[m
[32m+[m [32mEVT_SQLDrop[m
} EventTriggerEvent;[m
[m
typedef struct[m
[1mdiff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out[m
[1mindex bf020de..57c5ecf 100644[m
[1m--- a/src/test/regress/expected/event_trigger.out[m
[1m+++ b/src/test/regress/expected/event_trigger.out[m
[36m@@ -93,11 +93,123 @@[m [mERROR: event trigger "regress_event_trigger" does not exist[m
drop role regression_bob;[m
ERROR: role "regression_bob" cannot be dropped because some objects depend on it[m
DETAIL: owner of event trigger regress_event_trigger3[m
[32m+[m[32m-- cleanup before next test[m
-- these are all OK; the second one should emit a NOTICE[m
drop event trigger if exists regress_event_trigger2;[m
drop event trigger if exists regress_event_trigger2;[m
NOTICE: event trigger "regress_event_trigger2" does not exist, skipping[m
drop event trigger regress_event_trigger3;[m
drop event trigger regress_event_trigger_end;[m
[31m-drop function test_event_trigger();[m
[31m-drop role regression_bob;[m
[32m+[m[32m-- test support for dropped objects[m
[32m+[m[32mCREATE SCHEMA schema_one authorization regression_bob;[m
[32m+[m[32mCREATE SCHEMA schema_two authorization regression_bob;[m
[32m+[m[32mCREATE SCHEMA audit_tbls authorization regression_bob;[m
[32m+[m[32mSET SESSION AUTHORIZATION regression_bob;[m
[32m+[m[32mCREATE TABLE schema_one.table_one(a int);[m
[32m+[m[32mCREATE TABLE schema_one."table two"(a int);[m
[32m+[m[32mCREATE TABLE schema_one.table_three(a int);[m
[32m+[m[32mCREATE TABLE audit_tbls.schema_one_table_two(the_value text);[m
[32m+[m[32mCREATE TABLE schema_two.table_two(a int);[m
[32m+[m[32mCREATE TABLE schema_two.table_three(a int, b text);[m
[32m+[m[32mCREATE TABLE audit_tbls.schema_two_table_three(the_value text);[m
[32m+[m[32mCREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql[m
[32m+[m[32m CALLED ON NULL INPUT[m
[32m+[m[32m AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$;[m
[32m+[m[32mCREATE AGGREGATE schema_two.newton[m
[32m+[m[32m (BASETYPE = int, SFUNC = schema_two.add, STYPE = int);[m
[32m+[m[32mRESET SESSION AUTHORIZATION;[m
[32m+[m[32mCREATE TABLE dropped_objects ([m
[32m+[m [32mtype text,[m
[32m+[m [32mschema text,[m
[32m+[m [32mobject text);[m
[32m+[m[32mCREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger[m
[32m+[m[32mLANGUAGE plpgsql AS $$[m
[32m+[m[32mDECLARE[m
[32m+[m[32m obj record;[m
[32m+[m[32mBEGIN[m
[32m+[m[32m FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()[m
[32m+[m[32m LOOP[m
[32m+[m[32m IF obj.object_type = 'table' THEN[m
[32m+[m[32m EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%I',[m
[32m+[m [32mformat('%s_%s', obj.schema_name, obj.object_name));[m
[32m+[m[32m END IF;[m
[32m+[m
[32m+[m [32mINSERT INTO dropped_objects[m
[32m+[m [32m(type, schema, object) VALUES[m
[32m+[m [32m(obj.object_type, obj.schema_name, obj.object_identity);[m
[32m+[m[32m END LOOP;[m
[32m+[m[32mEND[m
[32m+[m[32m$$;[m
[32m+[m[32mCREATE EVENT TRIGGER regress_event_trigger_drop_objects ON sql_drop[m
[32m+[m [32mWHEN TAG IN ('drop table', 'drop function', 'drop view',[m
[32m+[m [32m'drop owned', 'drop schema', 'alter table')[m
[32m+[m [32mEXECUTE PROCEDURE test_evtrig_dropped_objects();[m
[32m+[m[32mALTER TABLE schema_one.table_one DROP COLUMN a;[m
[32m+[m[32mDROP SCHEMA schema_one, schema_two CASCADE;[m
[32m+[m[32mNOTICE: drop cascades to 7 other objects[m
[32m+[m[32mDETAIL: drop cascades to table schema_two.table_two[m
[32m+[m[32mdrop cascades to table schema_two.table_three[m
[32m+[m[32mdrop cascades to function schema_two.add(integer,integer)[m
[32m+[m[32mdrop cascades to function schema_two.newton(integer)[m
[32m+[m[32mdrop cascades to table schema_one.table_one[m
[32m+[m[32mdrop cascades to table schema_one."table two"[m
[32m+[m[32mdrop cascades to table schema_one.table_three[m
[32m+[m[32mNOTICE: table "schema_two_table_two" does not exist, skipping[m
[32m+[m[32mCONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_two"[m
[32m+[m[32mPL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement[m
[32m+[m[32mNOTICE: table "audit_tbls_schema_two_table_three" does not exist, skipping[m
[32m+[m[32mCONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.audit_tbls_schema_two_table_three"[m
[32m+[m[32mPL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement[m
[32m+[m[32mSQL statement "DROP TABLE IF EXISTS audit_tbls.schema_two_table_three"[m
[32m+[m[32mPL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement[m
[32m+[m[32mNOTICE: table "schema_one_table_one" does not exist, skipping[m
[32m+[m[32mCONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_one_table_one"[m
[32m+[m[32mPL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement[m
[32m+[m[32mNOTICE: table "schema_one_table two" does not exist, skipping[m
[32m+[m[32mCONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls."schema_one_table two""[m
[32m+[m[32mPL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement[m
[32m+[m[32mNOTICE: table "schema_one_table_three" does not exist, skipping[m
[32m+[m[32mCONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.schema_one_table_three"[m
[32m+[m[32mPL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement[m
[32m+[m[32mSELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast';[m
[32m+[m[32m type | schema | object[m[41m [m
[32m+[m[32m--------------+------------+-------------------------------------[m
[32m+[m[32m table column | schema_one | schema_one.table_one.a[m
[32m+[m[32m schema | | schema_two[m
[32m+[m[32m table | schema_two | schema_two.table_two[m
[32m+[m[32m type | schema_two | schema_two.table_two[m
[32m+[m[32m type | schema_two | schema_two.table_two[][m
[32m+[m[32m table | audit_tbls | audit_tbls.schema_two_table_three[m
[32m+[m[32m type | audit_tbls | audit_tbls.schema_two_table_three[m
[32m+[m[32m type | audit_tbls | audit_tbls.schema_two_table_three[][m
[32m+[m[32m table | schema_two | schema_two.table_three[m
[32m+[m[32m type | schema_two | schema_two.table_three[m
[32m+[m[32m type | schema_two | schema_two.table_three[][m
[32m+[m[32m function | schema_two | schema_two.add(integer,integer)[m
[32m+[m[32m aggregate | schema_two | schema_two.newton(integer)[m
[32m+[m[32m schema | | schema_one[m
[32m+[m[32m table | schema_one | schema_one.table_one[m
[32m+[m[32m type | schema_one | schema_one.table_one[m
[32m+[m[32m type | schema_one | schema_one.table_one[][m
[32m+[m[32m table | schema_one | schema_one."table two"[m
[32m+[m[32m type | schema_one | schema_one."table two"[m
[32m+[m[32m type | schema_one | schema_one."table two"[][m
[32m+[m[32m table | schema_one | schema_one.table_three[m
[32m+[m[32m type | schema_one | schema_one.table_three[m
[32m+[m[32m type | schema_one | schema_one.table_three[][m
[32m+[m[32m(23 rows)[m
[32m+[m
[32m+[m[32mDROP OWNED BY regression_bob;[m
[32m+[m[32mNOTICE: table "audit_tbls_schema_one_table_two" does not exist, skipping[m
[32m+[m[32mCONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.audit_tbls_schema_one_table_two"[m
[32m+[m[32mPL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement[m
[32m+[m[32mSELECT * FROM dropped_objects WHERE type = 'schema';[m
[32m+[m[32m type | schema | object[m[41m [m
[32m+[m[32m--------+--------+------------[m
[32m+[m[32m schema | | schema_two[m
[32m+[m[32m schema | | schema_one[m
[32m+[m[32m schema | | audit_tbls[m
[32m+[m[32m(3 rows)[m
[32m+[m
[32m+[m[32mDROP ROLE regression_bob;[m
[32m+[m[32mDROP EVENT TRIGGER regress_event_trigger_drop_objects;[m
[1mdiff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql[m
[1mindex a07dcd7..8dd297a 100644[m
[1m--- a/src/test/regress/sql/event_trigger.sql[m
[1m+++ b/src/test/regress/sql/event_trigger.sql[m
[36m@@ -97,10 +97,73 @@[m [mdrop event trigger regress_event_trigger;[m
-- should fail, regression_bob owns regress_event_trigger2/3[m
drop role regression_bob;[m
[m
[32m+[m[32m-- cleanup before next test[m
-- these are all OK; the second one should emit a NOTICE[m
drop event trigger if exists regress_event_trigger2;[m
drop event trigger if exists regress_event_trigger2;[m
drop event trigger regress_event_trigger3;[m
drop event trigger regress_event_trigger_end;[m
[31m-drop function test_event_trigger();[m
[31m-drop role regression_bob;[m
[32m+[m
[32m+[m[32m-- test support for dropped objects[m
[32m+[m[32mCREATE SCHEMA schema_one authorization regression_bob;[m
[32m+[m[32mCREATE SCHEMA schema_two authorization regression_bob;[m
[32m+[m[32mCREATE SCHEMA audit_tbls authorization regression_bob;[m
[32m+[m[32mSET SESSION AUTHORIZATION regression_bob;[m
[32m+[m
[32m+[m[32mCREATE TABLE schema_one.table_one(a int);[m
[32m+[m[32mCREATE TABLE schema_one."table two"(a int);[m
[32m+[m[32mCREATE TABLE schema_one.table_three(a int);[m
[32m+[m[32mCREATE TABLE audit_tbls.schema_one_table_two(the_value text);[m
[32m+[m
[32m+[m[32mCREATE TABLE schema_two.table_two(a int);[m
[32m+[m[32mCREATE TABLE schema_two.table_three(a int, b text);[m
[32m+[m[32mCREATE TABLE audit_tbls.schema_two_table_three(the_value text);[m
[32m+[m
[32m+[m[32mCREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql[m
[32m+[m[32m CALLED ON NULL INPUT[m
[32m+[m[32m AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$;[m
[32m+[m[32mCREATE AGGREGATE schema_two.newton[m
[32m+[m[32m (BASETYPE = int, SFUNC = schema_two.add, STYPE = int);[m
[32m+[m
[32m+[m[32mRESET SESSION AUTHORIZATION;[m
[32m+[m
[32m+[m[32mCREATE TABLE dropped_objects ([m
[32m+[m [32mtype text,[m
[32m+[m [32mschema text,[m
[32m+[m [32mobject text);[m
[32m+[m
[32m+[m[32mCREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger[m
[32m+[m[32mLANGUAGE plpgsql AS $$[m
[32m+[m[32mDECLARE[m
[32m+[m[32m obj record;[m
[32m+[m[32mBEGIN[m
[32m+[m[32m FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()[m
[32m+[m[32m LOOP[m
[32m+[m[32m IF obj.object_type = 'table' THEN[m
[32m+[m[32m EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%I',[m
[32m+[m [32mformat('%s_%s', obj.schema_name, obj.object_name));[m
[32m+[m[32m END IF;[m
[32m+[m
[32m+[m [32mINSERT INTO dropped_objects[m
[32m+[m [32m(type, schema, object) VALUES[m
[32m+[m [32m(obj.object_type, obj.schema_name, obj.object_identity);[m
[32m+[m[32m END LOOP;[m
[32m+[m[32mEND[m
[32m+[m[32m$$;[m
[32m+[m
[32m+[m[32mCREATE EVENT TRIGGER regress_event_trigger_drop_objects ON sql_drop[m
[32m+[m [32mWHEN TAG IN ('drop table', 'drop function', 'drop view',[m
[32m+[m [32m'drop owned', 'drop schema', 'alter table')[m
[32m+[m [32mEXECUTE PROCEDURE test_evtrig_dropped_objects();[m
[32m+[m
[32m+[m[32mALTER TABLE schema_one.table_one DROP COLUMN a;[m
[32m+[m[32mDROP SCHEMA schema_one, schema_two CASCADE;[m
[32m+[m
[32m+[m[32mSELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast';[m
[32m+[m
[32m+[m[32mDROP OWNED BY regression_bob;[m
[32m+[m[32mSELECT * FROM dropped_objects WHERE type = 'schema';[m
[32m+[m
[32m+[m[32mDROP ROLE regression_bob;[m
[32m+[m
[32m+[m[32mDROP EVENT TRIGGER regress_event_trigger_drop_objects;[m
Alvaro Herrera escribió:
Alvaro Herrera escribió:
However, I think previous discussions have conflated many different
things, and we've been slowly fixing them one by one; so perhaps at this
point it does make sense to have a new object-drop event. Let's discuss
it -- we would define it as taking place just before ddl_command_end,
and firing any time a command (with matching tag?) has called
performDeletion or performMultipleDeletions. Does that sound okay?
[1mdiff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml[m
[1mindex 71241c8..492a4ed 100644[m
[1m--- a/doc/src/sgml/event-trigger.sgml[m
[1m+++ b/doc/src/sgml/event-trigger.sgml[m
Uh, seems I posted the --color version of the patch last night. Easy
mistake to make. Here's a readable version (context diff, even), with a
couple of bugs fixed that I noticed while writing the final docs. Some
random minor cleanup too. I also took the opportunity to refactor some
common code in the invoking sequence of existing events; there was way
too much unnecessary duplication there.
This horse is about to become corpse; last chance for beating it up.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
dropped_objects.11.patchtext/x-diff; charset=us-asciiDownload
*** a/doc/src/sgml/event-trigger.sgml
--- b/doc/src/sgml/event-trigger.sgml
***************
*** 27,43 ****
<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.
</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
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
--- 27,45 ----
<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</>,
! <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
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 ****
--- 48,64 ----
</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
<literal>ddl_command_end</> triggers will not be executed. Conversely,
***************
*** 99,104 ****
--- 112,118 ----
<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,506 ****
--- 120,606 ----
<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>
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 15980,15983 **** FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
--- 15980,16090 ----
<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>sql_drop</>
+ event it is called. This function 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. 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>
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 191,196 **** static bool stack_address_present_add_flags(const ObjectAddress *object,
--- 191,234 ----
/*
+ * Go through the objects given running the final actions on them, and execute
+ * the actual deletion.
+ */
+ static void
+ deleteObjectsInList(ObjectAddresses *targetObjects, Relation *depRel,
+ int flags)
+ {
+ int i;
+
+ /*
+ * Keep track of objects for event triggers, if necessary.
+ */
+ if (trackDroppedObjectsNeeded())
+ {
+ for (i = 0; i < targetObjects->numrefs; i++)
+ {
+ ObjectAddress *thisobj = targetObjects->refs + i;
+
+ if ((!(flags & PERFORM_DELETION_INTERNAL)) &&
+ EventTriggerSupportsObjectType(getObjectClass(thisobj)))
+ {
+ EventTriggerSQLDropAddObject(thisobj);
+ }
+ }
+ }
+
+ /*
+ * Delete all the objects in the proper order.
+ */
+ for (i = 0; i < targetObjects->numrefs; i++)
+ {
+ ObjectAddress *thisobj = targetObjects->refs + i;
+
+ deleteOneObject(thisobj, depRel, flags);
+ }
+ }
+
+ /*
* performDeletion: attempt to drop the specified object. If CASCADE
* behavior is specified, also drop any dependent objects (recursively).
* If RESTRICT behavior is specified, error out if there are any dependent
***************
*** 215,221 **** performDeletion(const ObjectAddress *object,
{
Relation depRel;
ObjectAddresses *targetObjects;
- int i;
/*
* We save some cycles by opening pg_depend just once and passing the
--- 253,258 ----
***************
*** 250,264 **** 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);
! }
/* And clean up */
free_object_addresses(targetObjects);
--- 287,294 ----
NOTICE,
object);
! /* do the deed */
! deleteObjectsInList(targetObjects, &depRel, flags);
/* And clean up */
free_object_addresses(targetObjects);
***************
*** 332,346 **** 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);
! }
/* And clean up */
free_object_addresses(targetObjects);
--- 362,369 ----
NOTICE,
(objects->numrefs == 1 ? objects->refs : NULL));
! /* do the deed */
! deleteObjectsInList(targetObjects, &depRel, flags);
/* And clean up */
free_object_addresses(targetObjects);
***************
*** 356,361 **** performMultipleDeletions(const ObjectAddresses *objects,
--- 379,388 ----
* 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,32 ****
--- 19,35 ----
#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,44 ****
--- 42,57 ----
#include "utils/syscache.h"
#include "tcop/utility.h"
+
+ typedef struct EventTriggerQueryState
+ {
+ slist_head SQLDropList;
+ MemoryContext cxt;
+ struct EventTriggerQueryState *previous;
+ } EventTriggerQueryState;
+
+ EventTriggerQueryState *currentEventTriggerState = NULL;
+
typedef struct
{
const char *obtypename;
***************
*** 89,94 **** static event_trigger_support_data event_trigger_support[] = {
--- 102,118 ----
{ 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,133 **** CreateEventTrigger(CreateEventTrigStmt *stmt)
/* Validate event name. */
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
! strcmp(stmt->eventname, "ddl_command_end") != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized event name \"%s\"",
--- 151,158 ----
/* Validate event name. */
if (strcmp(stmt->eventname, "ddl_command_start") != 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,157 **** CreateEventTrigger(CreateEventTrigStmt *stmt)
}
/* Validate tag list, if any. */
! if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL)
validate_ddl_tags("tag", tags);
/*
--- 176,185 ----
}
/* Validate tag list, if any. */
! 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,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;
/*
--- 248,255 ----
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;
/*
***************
*** 568,602 **** filter_event_trigger(const char **tag, EventTriggerCacheItem *item)
}
/*
! * Fire ddl_command_start triggers.
*/
! void
! EventTriggerDDLCommandStart(Node *parsetree)
{
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;
/*
* We want the list of command tags for which this procedure is actually
--- 597,615 ----
}
/*
! * 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.
*/
! static List *
! EventTriggerCommonSetup(Node *parsetree,
! EventTriggerEvent event, const char *eventstr,
! EventTriggerData *trigdata)
{
+ const char *tag;
List *cachelist;
ListCell *lc;
! List *runlist = NIL;
/*
* We want the list of command tags for which this procedure is actually
***************
*** 624,632 **** EventTriggerDDLCommandStart(Node *parsetree)
#endif
/* Use cache to find triggers for this event; fast exit if none. */
! cachelist = EventCacheLookup(EVT_DDLCommandStart);
! if (cachelist == NULL)
! return;
/* Get the command tag. */
tag = CreateCommandTag(parsetree);
--- 637,645 ----
#endif
/* Use cache to find triggers for this event; fast exit if none. */
! cachelist = EventCacheLookup(event);
! if (cachelist == NIL)
! return NIL;
/* Get the command tag. */
tag = CreateCommandTag(parsetree);
***************
*** 649,659 **** EventTriggerDDLCommandStart(Node *parsetree)
}
}
! /* Construct event trigger data. */
! trigdata.type = T_EventTriggerData;
! trigdata.event = "ddl_command_start";
! trigdata.parsetree = parsetree;
! trigdata.tag = tag;
/* Run the triggers. */
EventTriggerInvoke(runlist, &trigdata);
--- 662,712 ----
}
}
! /* 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,683 **** EventTriggerDDLCommandStart(Node *parsetree)
void
EventTriggerDDLCommandEnd(Node *parsetree)
{
! List *cachelist;
! List *runlist = NIL;
! ListCell *lc;
! const char *tag;
EventTriggerData trigdata;
/*
--- 727,733 ----
void
EventTriggerDDLCommandEnd(Node *parsetree)
{
! List *runlist;
EventTriggerData trigdata;
/*
***************
*** 687,739 **** EventTriggerDDLCommandEnd(Node *parsetree)
if (!IsUnderPostmaster)
return;
/*
! * See EventTriggerDDLCommandStart for a discussion about why this check is
! * important.
! *
*/
! #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)
! return;
! /* Get the command tag. */
! tag = CreateCommandTag(parsetree);
/*
! * 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.
*/
! 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;
/*
* Make sure anything the main command did will be visible to the
--- 737,797 ----
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;
/*
! * See EventTriggerDDLCommandStart for a discussion about why event
! * triggers are disabled in single user mode.
*/
! if (!IsUnderPostmaster)
! return;
! /*
! * Use current state to determine whether this event fires at all. If there
! * are no triggers for the sql_drop event, then we don't have anything to do
! * here. Note that dropped object collection is disabled if this is the case,
! * so even if we were to try to run, the list would be empty.
! */
! if (!currentEventTriggerState ||
! slist_is_empty(¤tEventTriggerState->SQLDropList))
! return;
! runlist = EventTriggerCommonSetup(parsetree,
! EVT_SQLDrop, "sql_drop",
! &trigdata);
! /*
! * Nothing to do if run list is empty. Note this shouldn't happen, because
! * if there are no sql_drop events, then objects-to-drop wouldn't have been
! * collected in the first place and we would have quitted above.
! */
! if (runlist == NIL)
! return;
/*
* Make sure anything the main command did will be visible to the
***************
*** 832,834 **** EventTriggerSupportsObjectType(ObjectType obtype)
--- 890,1172 ----
}
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->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;
+
+ /* 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[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;
+ }
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 4478,4484 **** DropTrigStmt:
*
* QUERIES :
* CREATE EVENT TRIGGER ...
- * DROP EVENT TRIGGER ...
* ALTER EVENT TRIGGER ...
*
*****************************************************************************/
--- 4478,4483 ----
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 346,357 **** ProcessUtility(Node *parsetree,
do { \
if (isCompleteQuery) \
{ \
! EventTriggerDDLCommandStart(parsetree); \
} \
fncall; \
if (isCompleteQuery) \
{ \
! EventTriggerDDLCommandEnd(parsetree); \
} \
} while (0)
--- 346,358 ----
do { \
if (isCompleteQuery) \
{ \
! EventTriggerDDLCommandStart(parsetree); \
} \
fncall; \
if (isCompleteQuery) \
{ \
! EventTriggerSQLDrop(parsetree); \
! EventTriggerDDLCommandEnd(parsetree); \
} \
} while (0)
***************
*** 366,375 **** ProcessUtility(Node *parsetree,
--- 367,414 ----
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,391 **** standard_ProcessUtility(Node *parsetree,
--- 425,432 ----
if (completionTag)
completionTag[0] = '\0';
+ UTILITY_BEGIN_QUERY(isCompleteQuery);
+
switch (nodeTag(parsetree))
{
/*
***************
*** 615,621 **** standard_ProcessUtility(Node *parsetree,
--- 656,665 ----
}
if (isCompleteQuery)
+ {
+ EventTriggerSQLDrop(parsetree);
EventTriggerDDLCommandEnd(parsetree);
+ }
}
break;
***************
*** 726,732 **** standard_ProcessUtility(Node *parsetree,
--- 770,779 ----
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
+ {
+ EventTriggerSQLDrop(parsetree);
EventTriggerDDLCommandEnd(parsetree);
+ }
break;
}
***************
*** 856,861 **** standard_ProcessUtility(Node *parsetree,
--- 903,914 ----
ereport(NOTICE,
(errmsg("relation \"%s\" does not exist, skipping",
atstmt->relation->relname)));
+
+ if (isCompleteQuery)
+ {
+ EventTriggerSQLDrop(parsetree);
+ 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:
--- 1301,1309 ----
break;
case T_DropOwnedStmt:
! InvokeDDLCommandEventTriggers(
! parsetree,
! DropOwnedObjects((DropOwnedStmt *) parsetree));
break;
case T_ReassignOwnedStmt:
***************
*** 1372,1377 **** standard_ProcessUtility(Node *parsetree,
--- 1426,1433 ----
(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, or if caller requests it, we need to qualify it.
*/
if (!force_qualify && FunctionIsVisible(procedure_oid))
nspname = NULL;
*** a/src/backend/utils/cache/evtcache.c
--- b/src/backend/utils/cache/evtcache.c
***************
*** 169,174 **** BuildEventTriggerCache(void)
--- 169,176 ----
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;
*** 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,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,
*** a/src/include/commands/event_trigger.h
--- b/src/include/commands/event_trigger.h
***************
*** 19,25 ****
typedef struct EventTriggerData
{
NodeTag type;
! char *event; /* event name */
Node *parsetree; /* parse tree */
const char *tag; /* command tag */
} EventTriggerData;
--- 19,25 ----
typedef struct EventTriggerData
{
NodeTag type;
! const char *event; /* event name */
Node *parsetree; /* parse tree */
const char *tag; /* command tag */
} EventTriggerData;
***************
*** 42,46 **** extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
--- 42,52 ----
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 */
*** 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/include/utils/evtcache.h
--- b/src/include/utils/evtcache.h
***************
*** 19,25 ****
typedef enum
{
EVT_DDLCommandStart,
! EVT_DDLCommandEnd
} EventTriggerEvent;
typedef struct
--- 19,26 ----
typedef enum
{
EVT_DDLCommandStart,
! EVT_DDLCommandEnd,
! EVT_SQLDrop
} EventTriggerEvent;
typedef struct
*** 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,215 ----
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.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 dropped_objects (
! type text,
! schema text,
! object 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.%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
! 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;
*** 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,169 ----
-- 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.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 dropped_objects (
! type text,
! schema text,
! object 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.%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;
!
! 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;
Pushed, with some further minor changes. One not-so-minor change I
introduced was that pg_event_trigger_dropped_objects() now only works
within a sql_drop event function. The reason I decided to do this was
that if we don't have that protection, then it is possible to have a
ddl_command_end trigger calling pg_event_trigger_dropped_objects(); and
if there is an sql_drop trigger, then it'd return the list of dropped
objects, but if there's no sql_drop trigger, it'd raise an error. That
seemed surprising enough action-at-a-distance that some protection is
warranted.
Thanks for all the review.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Pushed, with some further minor changes. One not-so-minor change I
Thanks a lot for all the work you did put into this patch!
introduced was that pg_event_trigger_dropped_objects() now only works
within a sql_drop event function. The reason I decided to do this was
that if we don't have that protection, then it is possible to have a
ddl_command_end trigger calling pg_event_trigger_dropped_objects(); and
if there is an sql_drop trigger, then it'd return the list of dropped
objects, but if there's no sql_drop trigger, it'd raise an error. That
seemed surprising enough action-at-a-distance that some protection is
warranted.
+1
I hope that we can add another such function for ddl_command_start and
ddl_command_end function to get at some object information from there,
now that the support code is in place.
That would allow making any use of the event trigger facility in 9.3
without having to write a C coded extension… which has been the goal for
the last two cycles…
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers