]> granicus.if.org Git - postgresql/commitdiff
Allow on-the-fly capture of DDL event details
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 11 May 2015 22:14:31 +0000 (19:14 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 11 May 2015 22:14:31 +0000 (19:14 -0300)
This feature lets user code inspect and take action on DDL events.
Whenever a ddl_command_end event trigger is installed, DDL actions
executed are saved to a list which can be inspected during execution of
a function attached to ddl_command_end.

The set-returning function pg_event_trigger_ddl_commands can be used to
list actions so captured; it returns data about the type of command
executed, as well as the affected object.  This is sufficient for many
uses of this feature.  For the cases where it is not, we also provide a
"command" column of a new pseudo-type pg_ddl_command, which is a
pointer to a C structure that can be accessed by C code.  The struct
contains all the info necessary to completely inspect and even
reconstruct the executed command.

There is no actual deparse code here; that's expected to come later.
What we have is enough infrastructure that the deparsing can be done in
an external extension.  The intention is that we will add some deparsing
code in a later release, as an in-core extension.

A new test module is included.  It's probably insufficient as is, but it
should be sufficient as a starting point for a more complete and
future-proof approach.

Authors: Álvaro Herrera, with some help from Andres Freund, Ian Barwick,
Abhijit Menon-Sen.

Reviews by Andres Freund, Robert Haas, Amit Kapila, Michael Paquier,
Craig Ringer, David Steele.
Additional input from Chris Browne, Dimitri Fontaine, Stephen Frost,
Petr Jelínek, Tom Lane, Jim Nasby, Steven Singer, Pavel Stěhule.

Based on original work by Dimitri Fontaine, though I didn't use his
code.

Discussion:
  https://www.postgresql.org/message-id/m2txrsdzxa.fsf@2ndQuadrant.fr
  https://www.postgresql.org/message-id/20131108153322.GU5809@eldon.alvh.no-ip.org
  https://www.postgresql.org/message-id/20150215044814.GL3391@alvh.no-ip.org

72 files changed:
doc/src/sgml/event-trigger.sgml
doc/src/sgml/func.sgml
src/backend/catalog/aclchk.c
src/backend/commands/event_trigger.c
src/backend/commands/opclasscmds.c
src/backend/commands/schemacmds.c
src/backend/commands/tablecmds.c
src/backend/commands/tsearchcmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/backend/utils/adt/format_type.c
src/backend/utils/adt/pseudotypes.c
src/include/catalog/catversion.h
src/include/catalog/opfam_internal.h [new file with mode: 0644]
src/include/catalog/pg_proc.h
src/include/catalog/pg_type.h
src/include/commands/defrem.h
src/include/commands/event_trigger.h
src/include/commands/extension.h
src/include/nodes/parsenodes.h
src/include/tcop/deparse_utility.h [new file with mode: 0644]
src/include/utils/aclchk_internal.h [new file with mode: 0644]
src/include/utils/builtins.h
src/test/modules/Makefile
src/test/modules/test_ddl_deparse/Makefile [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/alter_extension.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/alter_function.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/alter_sequence.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/alter_table.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/alter_type_enum.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/comment_on.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_conversion.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_domain.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_extension.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_function.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_operator.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_rule.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_schema.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_sequence_1.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_table.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_trigger.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_type.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/create_view.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/defprivs.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/matviews.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/opfamily.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out [new file with mode: 0644]
src/test/modules/test_ddl_deparse/regress_schedule [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/alter_function.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/alter_sequence.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/alter_table.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/comment_on.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_conversion.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_domain.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_extension.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_rule.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_schema.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_table.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_trigger.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_type.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/create_view.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/defprivs.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/matviews.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/opfamily.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql [new file with mode: 0644]
src/test/modules/test_ddl_deparse/test_ddl_deparse.c [new file with mode: 0644]
src/test/modules/test_ddl_deparse/test_ddl_deparse.control [new file with mode: 0644]

index f151eb73754f1998367149a1ed377c7131d5e3ce..0cb31a478fada47b36ff56c188f1b618114ad768 100644 (file)
@@ -29,7 +29,8 @@
      occurs in the database in which it is defined. Currently, the only
      supported events are
      <literal>ddl_command_start</>,
-     <literal>ddl_command_end</>
+     <literal>ddl_command_end</>,
+     <literal>table_rewrite</>
      and <literal>sql_drop</>.
      Support for additional events may be added in future releases.
    </para>
 
    <para>
     The <literal>ddl_command_end</> event occurs just after the execution of
-    this same set of commands.
+    this same set of commands.  To obtain more details on the <acronym>DDL</>
+    operations that took place, use the set-returning function
+    <literal>pg_event_trigger_ddl_commands()</> from the
+    <literal>ddl_command_end</> event trigger code (see
+    <xref linkend="functions-event-triggers">).  Note that the trigger fires
+    after the actions have taken place (but before the transaction commits),
+    and thus the system catalogs can be read as already changed.
    </para>
 
    <para>
index fb397316048cca72b6293b466760f08b58e4f207..1ee4f634d3a36faa94ac5b61dc0dba4481b67f64 100644 (file)
@@ -18066,8 +18066,99 @@ FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
     see <xref linkend="event-triggers">.
    </para>
 
+  <sect2 id="pg-event-trigger-ddl-command-end-functions">
+   <title>Capturing Changes at Command End</title>
+
+   <indexterm>
+    <primary>pg_event_trigger_ddl_commands</primary>
+   </indexterm>
+
+   <para>
+    <function>pg_event_trigger_ddl_commands</> returns a list of
+    <acronym>DDL</acronym> commands executed by each user action,
+    when invoked in a function attached to a
+    <literal>ddl_command_end</> event trigger.  If called in any other
+    context, an error is raised.
+    <function>pg_event_trigger_ddl_commands</> returns one row for each
+    base command executed; some commands that are a single SQL sentence
+    may return more than one row.  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 belongs in</entry>
+       </row>
+       <row>
+        <entry><literal>objid</literal></entry>
+        <entry><type>Oid</type></entry>
+        <entry>OID of the object in the catalog</entry>
+       </row>
+       <row>
+        <entry><literal>objsubid</literal></entry>
+        <entry><type>integer</type></entry>
+        <entry>Object sub-id (e.g. attribute number for columns)</entry>
+       </row>
+       <row>
+        <entry><literal>command_tag</literal></entry>
+        <entry><type>text</type></entry>
+        <entry>command tag</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 belongs in, if any; otherwise <literal>NULL</>.
+         No quoting is applied.
+        </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>
+       <row>
+        <entry><literal>in_extension</literal></entry>
+        <entry><type>bool</type></entry>
+        <entry>whether the command is part of an extension script</entry>
+       </row>
+       <row>
+        <entry><literal>command</literal></entry>
+        <entry><type>pg_ddl_command</type></entry>
+        <entry>
+         A complete representation of the command, in internal format.
+         This cannot be output directly, but it can be passed to other
+         functions to obtain different pieces of information about the
+         command.
+        </entry>
+       </row>
+      </tbody>
+     </tgroup>
+    </informaltable>
+   </para>
+  </sect2>
+
   <sect2 id="pg-event-trigger-sql-drop-functions">
-   <title>Processing objects dropped by a DDL command.</title>
+   <title>Processing Objects Dropped by a DDL Command</title>
 
    <indexterm>
      <primary>pg_event_trigger_dropped_objects</primary>
index 8e75c2792056789cf2c742c2678933fd7cf76c2b..943909c82254657dcd8894f2fb58561573154f7a 100644 (file)
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk_internal.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/tqual.h"
 
 
-/*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-       bool            is_grant;
-       GrantObjectType objtype;
-       List       *objects;
-       bool            all_privs;
-       AclMode         privileges;
-       List       *col_privs;
-       List       *grantees;
-       bool            grant_option;
-       DropBehavior behavior;
-} InternalGrant;
-
 /*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
                        elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                                 (int) istmt->objtype);
        }
+
+       /*
+        * Pass the info to event triggers about the just-executed GRANT.  Note
+        * that we prefer to do it after actually executing it, because that gives
+        * the functions a chance to adjust the istmt with privileges actually
+        * granted.
+        */
+       if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+               EventTriggerCollectGrant(istmt);
 }
 
 /*
index 0110b0603d3ce12c47e295a5a808898f588398cc..7658c06d53448fa23948daec422ac4c268a2dcee 100644 (file)
 #include "catalog/objectaccess.h"
 #include "catalog/pg_event_trigger.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_config.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/event_trigger.h"
+#include "commands/extension.h"
 #include "commands/trigger.h"
 #include "funcapi.h"
 #include "parser/parse_func.h"
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
@@ -44,6 +49,9 @@
 
 typedef struct EventTriggerQueryState
 {
+       /* memory context for this state's objects */
+       MemoryContext cxt;
+
        /* sql_drop */
        slist_head      SQLDropList;
        bool            in_sql_drop;
@@ -52,7 +60,10 @@ typedef struct EventTriggerQueryState
        Oid                     table_rewrite_oid;      /* InvalidOid, or set for table_rewrite event */
        int                     table_rewrite_reason;   /* AT_REWRITE reason */
 
-       MemoryContext cxt;
+       /* Support for command collection */
+       bool            commandCollectionInhibited;
+       CollectedCommand *currentCommand;
+       List       *commandList;                /* list of CollectedCommand; see deparse_utility.h */
        struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -71,6 +82,7 @@ typedef enum
        EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
+/* XXX merge this with ObjectTypeMap? */
 static event_trigger_support_data event_trigger_support[] = {
        {"AGGREGATE", true},
        {"CAST", true},
@@ -139,6 +151,8 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
 static void validate_ddl_tags(const char *filtervar, List *taglist);
 static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
 static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
+static const char *stringify_grantobjtype(GrantObjectType objtype);
+static const char *stringify_adefprivs_objtype(GrantObjectType objtype);
 
 /*
  * Create an event trigger.
@@ -1206,9 +1220,9 @@ EventTriggerBeginCompleteQuery(void)
        MemoryContext cxt;
 
        /*
-        * Currently, sql_drop and table_rewrite events are the only reason to
-        * have event trigger state at all; so if there are none, don't install
-        * one.
+        * Currently, sql_drop, table_rewrite, ddl_command_end 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;
@@ -1224,6 +1238,10 @@ EventTriggerBeginCompleteQuery(void)
        state->in_sql_drop = false;
        state->table_rewrite_oid = InvalidOid;
 
+       state->commandCollectionInhibited = currentEventTriggerState ?
+               currentEventTriggerState->commandCollectionInhibited : false;
+       state->currentCommand = NULL;
+       state->commandList = NIL;
        state->previous = currentEventTriggerState;
        currentEventTriggerState = state;
 
@@ -1262,9 +1280,13 @@ EventTriggerEndCompleteQuery(void)
 bool
 trackDroppedObjectsNeeded(void)
 {
-       /* true if any sql_drop or table_rewrite event trigger exists */
+       /*
+        * true if any sql_drop, table_rewrite, ddl_command_end event trigger
+        * exists
+        */
        return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
-               list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
+               list_length(EventCacheLookup(EVT_TableRewrite)) > 0 ||
+               list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0;
 }
 
 /*
@@ -1566,3 +1588,675 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
 
        PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
 }
+
+/*-------------------------------------------------------------------------
+ * Support for DDL command deparsing
+ *
+ * The routines below enable an event trigger function to obtain a list of
+ * DDL commands as they are executed.  There are three main pieces to this
+ * feature:
+ *
+ * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL command
+ * adds a struct CollectedCommand representation of itself to the command list,
+ * using the routines below.
+ *
+ * 2) Some time after that, ddl_command_end fires and the command list is made
+ * available to the event trigger function via pg_event_trigger_ddl_commands();
+ * the complete command details are exposed as a column of type pg_ddl_command.
+ *
+ * 3) An extension can install a function capable of taking a value of type
+ * pg_ddl_command and transform it into some external, user-visible and/or
+ * -modifiable representation.
+ *-------------------------------------------------------------------------
+ */
+
+/*
+ * Inhibit DDL command collection.
+ */
+void
+EventTriggerInhibitCommandCollection(void)
+{
+       if (!currentEventTriggerState)
+               return;
+
+       currentEventTriggerState->commandCollectionInhibited = true;
+}
+
+/*
+ * Re-establish DDL command collection.
+ */
+void
+EventTriggerUndoInhibitCommandCollection(void)
+{
+       if (!currentEventTriggerState)
+               return;
+
+       currentEventTriggerState->commandCollectionInhibited = false;
+}
+
+/*
+ * EventTriggerCollectSimpleCommand
+ *             Save data about a simple DDL command that was just executed
+ *
+ * address identifies the object being operated on.  secondaryObject is an
+ * object address that was related in some way to the executed command; its
+ * meaning is command-specific.
+ *
+ * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of
+ * object being moved, objectId is its OID, and secondaryOid is the OID of the
+ * old schema.  (The destination schema OID can be obtained by catalog lookup
+ * of the object.)
+ */
+void
+EventTriggerCollectSimpleCommand(ObjectAddress address,
+                                                                ObjectAddress secondaryObject,
+                                                                Node *parsetree)
+{
+       MemoryContext oldcxt;
+       CollectedCommand *command;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       command = palloc(sizeof(CollectedCommand));
+
+       command->type = SCT_Simple;
+       command->in_extension = creating_extension;
+
+       command->d.simple.address = address;
+       command->d.simple.secondaryObject = secondaryObject;
+       command->parsetree = copyObject(parsetree);
+
+       currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList,
+                                                                                         command);
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableStart
+ *             Prepare to receive data on an ALTER TABLE command about to be executed
+ *
+ * Note we don't collect the command immediately; instead we keep it in
+ * currentCommand, and only when we're done processing the subcommands we will
+ * add it to the command list.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+       MemoryContext   oldcxt;
+       CollectedCommand *command;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       command = palloc(sizeof(CollectedCommand));
+
+       command->type = SCT_AlterTable;
+       command->in_extension = creating_extension;
+
+       command->d.alterTable.classId = RelationRelationId;
+       command->d.alterTable.objectId = InvalidOid;
+       command->d.alterTable.subcmds = NIL;
+       command->parsetree = copyObject(parsetree);
+
+       currentEventTriggerState->currentCommand = command;
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by an ALTER TABLE.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerCollectAlterTableSubcmd
+ *             Save data about a single part of an ALTER TABLE.
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
+{
+       MemoryContext   oldcxt;
+       CollectedATSubcmd *newsub;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       Assert(IsA(subcmd, AlterTableCmd));
+       Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       newsub = palloc(sizeof(CollectedATSubcmd));
+       newsub->address = address;
+       newsub->parsetree = copyObject(subcmd);
+
+       currentEventTriggerState->currentCommand->d.alterTable.subcmds =
+               lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ *             Finish up saving an ALTER TABLE command, and add it to command list.
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       /* If no subcommands, don't collect */
+       if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0)
+       {
+               currentEventTriggerState->commandList =
+                       lappend(currentEventTriggerState->commandList,
+                                       currentEventTriggerState->currentCommand);
+       }
+       else
+               pfree(currentEventTriggerState->currentCommand);
+
+       currentEventTriggerState->currentCommand = NULL;
+}
+
+/*
+ * EventTriggerCollectGrant
+ *             Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerCollectGrant(InternalGrant *istmt)
+{
+       MemoryContext oldcxt;
+       CollectedCommand *command;
+       InternalGrant  *icopy;
+       ListCell           *cell;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       /*
+        * This is tedious, but necessary.
+        */
+       icopy = palloc(sizeof(InternalGrant));
+       memcpy(icopy, istmt, sizeof(InternalGrant));
+       icopy->objects = list_copy(istmt->objects);
+       icopy->grantees = list_copy(istmt->grantees);
+       icopy->col_privs = NIL;
+       foreach(cell, istmt->col_privs)
+               icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
+
+       /* Now collect it, using the copied InternalGrant */
+       command = palloc(sizeof(CollectedCommand));
+       command->type = SCT_Grant;
+       command->in_extension = creating_extension;
+       command->d.grant.istmt = icopy;
+       command->parsetree = NULL;
+
+       currentEventTriggerState->commandList =
+               lappend(currentEventTriggerState->commandList, command);
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterOpFam
+ *             Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
+ *             executed
+ */
+void
+EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+                                                       List *operators, List *procedures)
+{
+       MemoryContext   oldcxt;
+       CollectedCommand *command;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       command = palloc(sizeof(CollectedCommand));
+       command->type = SCT_AlterOpFamily;
+       command->in_extension = creating_extension;
+       ObjectAddressSet(command->d.opfam.address,
+                                        OperatorFamilyRelationId, opfamoid);
+       command->d.opfam.operators = operators;
+       command->d.opfam.procedures = procedures;
+       command->parsetree = copyObject(stmt);
+
+       currentEventTriggerState->commandList =
+               lappend(currentEventTriggerState->commandList, command);
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectCreateOpClass
+ *             Save data about a CREATE OPERATOR CLASS command being executed
+ */
+void
+EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+                                                          List *operators, List *procedures)
+{
+       MemoryContext   oldcxt;
+       CollectedCommand *command;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       command = palloc0(sizeof(CollectedCommand));
+       command->type = SCT_CreateOpClass;
+       command->in_extension = creating_extension;
+       ObjectAddressSet(command->d.createopc.address,
+                                        OperatorClassRelationId, opcoid);
+       command->d.createopc.operators = operators;
+       command->d.createopc.procedures = procedures;
+       command->parsetree = copyObject(stmt);
+
+       currentEventTriggerState->commandList =
+               lappend(currentEventTriggerState->commandList, command);
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterTSConfig
+ *             Save data about an ALTER TEXT SEARCH CONFIGURATION command being
+ *             executed
+ */
+void
+EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
+                                                                Oid *dictIds, int ndicts)
+{
+       MemoryContext   oldcxt;
+       CollectedCommand *command;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       command = palloc0(sizeof(CollectedCommand));
+       command->type = SCT_AlterTSConfig;
+       command->in_extension = creating_extension;
+       ObjectAddressSet(command->d.atscfg.address,
+                                        TSConfigRelationId, cfgId);
+       command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts);
+       memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+       command->d.atscfg.ndicts = ndicts;
+       command->parsetree = copyObject(stmt);
+
+       currentEventTriggerState->commandList =
+               lappend(currentEventTriggerState->commandList, command);
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerCollectAlterDefPrivs
+ *             Save data about an ALTER DEFAULT PRIVILEGES command being
+ *             executed
+ */
+void
+EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+{
+       MemoryContext   oldcxt;
+       CollectedCommand *command;
+
+       /* ignore if event trigger context not set, or collection disabled */
+       if (!currentEventTriggerState ||
+               currentEventTriggerState->commandCollectionInhibited)
+               return;
+
+       oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+       command = palloc0(sizeof(CollectedCommand));
+       command->type = SCT_AlterDefaultPrivileges;
+       command->d.defprivs.objtype = stmt->action->objtype;
+       command->in_extension = creating_extension;
+       command->parsetree = copyObject(stmt);
+
+       currentEventTriggerState->commandList =
+               lappend(currentEventTriggerState->commandList, command);
+       MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * In a ddl_command_end event trigger, this function reports the DDL commands
+ * being run.
+ */
+Datum
+pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
+{
+       ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       TupleDesc       tupdesc;
+       Tuplestorestate *tupstore;
+       MemoryContext per_query_ctx;
+       MemoryContext oldcontext;
+       ListCell   *lc;
+
+       /*
+        * Protect this function from being called out of context
+        */
+       if (!currentEventTriggerState)
+               ereport(ERROR,
+                               (errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED),
+                                errmsg("%s can only be called in an event trigger function",
+                                               "pg_event_trigger_ddl_commands()")));
+
+       /* 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);
+
+       foreach(lc, currentEventTriggerState->commandList)
+       {
+               CollectedCommand *cmd = lfirst(lc);
+               Datum           values[9];
+               bool            nulls[9];
+               ObjectAddress addr;
+               int                     i = 0;
+
+               /*
+                * For IF NOT EXISTS commands that attempt to create an existing
+                * object, the returned OID is Invalid.  Don't return anything.
+                *
+                * One might think that a viable alternative would be to look up the
+                * Oid of the existing object and run the deparse with that.  But since
+                * the parse tree might be different from the one that created the
+                * object in the first place, we might not end up in a consistent state
+                * anyway.
+                */
+               if (cmd->type == SCT_Simple &&
+                       !OidIsValid(cmd->d.simple.address.objectId))
+                       continue;
+
+               MemSet(nulls, 0, sizeof(nulls));
+
+               switch (cmd->type)
+               {
+                       case SCT_Simple:
+                       case SCT_AlterTable:
+                       case SCT_AlterOpFamily:
+                       case SCT_CreateOpClass:
+                       case SCT_AlterTSConfig:
+                               {
+                                       char       *identity;
+                                       char       *type;
+                                       char       *schema = NULL;
+
+                                       if (cmd->type == SCT_Simple)
+                                               addr = cmd->d.simple.address;
+                                       else if (cmd->type == SCT_AlterTable)
+                                               ObjectAddressSet(addr,
+                                                                                cmd->d.alterTable.classId,
+                                                                                cmd->d.alterTable.objectId);
+                                       else if (cmd->type == SCT_AlterOpFamily)
+                                               addr = cmd->d.opfam.address;
+                                       else if (cmd->type == SCT_CreateOpClass)
+                                               addr = cmd->d.createopc.address;
+                                       else if (cmd->type == SCT_AlterTSConfig)
+                                               addr = cmd->d.atscfg.address;
+
+                                       type = getObjectTypeDescription(&addr);
+                                       identity = getObjectIdentity(&addr);
+
+                                       /*
+                                        * Obtain schema name, if any ("pg_temp" if a temp object).
+                                        * If the object class is not in the supported list here,
+                                        * we assume it's a schema-less object type, and thus
+                                        * "schema" remains set to NULL.
+                                        */
+                                       if (is_objectclass_supported(addr.classId))
+                                       {
+                                               AttrNumber      nspAttnum;
+
+                                               nspAttnum = get_object_attnum_namespace(addr.classId);
+                                               if (nspAttnum != InvalidAttrNumber)
+                                               {
+                                                       Relation        catalog;
+                                                       HeapTuple       objtup;
+                                                       Oid                     schema_oid;
+                                                       bool            isnull;
+
+                                                       catalog = heap_open(addr.classId, AccessShareLock);
+                                                       objtup = get_catalog_object_by_oid(catalog,
+                                                                                                                          addr.objectId);
+                                                       if (!HeapTupleIsValid(objtup))
+                                                               elog(ERROR, "cache lookup failed for object %u/%u",
+                                                                        addr.classId, addr.objectId);
+                                                       schema_oid =
+                                                               heap_getattr(objtup, nspAttnum,
+                                                                                        RelationGetDescr(catalog), &isnull);
+                                                       if (isnull)
+                                                               elog(ERROR,
+                                                                        "invalid null namespace in object %u/%u/%d",
+                                                                        addr.classId, addr.objectId, addr.objectSubId);
+                                                       /* XXX not quite get_namespace_name_or_temp */
+                                                       if (isAnyTempNamespace(schema_oid))
+                                                               schema = pstrdup("pg_temp");
+                                                       else
+                                                               schema = get_namespace_name(schema_oid);
+
+                                                       heap_close(catalog, AccessShareLock);
+                                               }
+                                       }
+
+                                       /* classid */
+                                       values[i++] = ObjectIdGetDatum(addr.classId);
+                                       /* objid */
+                                       values[i++] = ObjectIdGetDatum(addr.objectId);
+                                       /* objsubid */
+                                       values[i++] = Int32GetDatum(addr.objectSubId);
+                                       /* command tag */
+                                       values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+                                       /* object_type */
+                                       values[i++] = CStringGetTextDatum(type);
+                                       /* schema */
+                                       if (schema == NULL)
+                                               nulls[i++] = true;
+                                       else
+                                               values[i++] = CStringGetTextDatum(schema);
+                                       /* identity */
+                                       values[i++] = CStringGetTextDatum(identity);
+                                       /* in_extension */
+                                       values[i++] = BoolGetDatum(cmd->in_extension);
+                                       /* command */
+                                       values[i++] = PointerGetDatum(cmd);
+                               }
+                               break;
+
+                       case SCT_AlterDefaultPrivileges:
+                               /* classid */
+                               nulls[i++] = true;
+                               /* objid */
+                               nulls[i++] = true;
+                               /* objsubid */
+                               nulls[i++] = true;
+                               /* command tag */
+                               values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree));
+                               /* object_type */
+                               values[i++] = CStringGetTextDatum(stringify_adefprivs_objtype(
+                                                                                                                                                         cmd->d.defprivs.objtype));
+                               /* schema */
+                               nulls[i++] = true;
+                               /* identity */
+                               nulls[i++] = true;
+                               /* in_extension */
+                               values[i++] = BoolGetDatum(cmd->in_extension);
+                               /* command */
+                               values[i++] = PointerGetDatum(cmd);
+                               break;
+
+                       case SCT_Grant:
+                               /* classid */
+                               nulls[i++] = true;
+                               /* objid */
+                               nulls[i++] = true;
+                               /* objsubid */
+                               nulls[i++] = true;
+                               /* command tag */
+                               values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ?
+                                                                                                 "GRANT" : "REVOKE");
+                               /* object_type */
+                               values[i++] = CStringGetTextDatum(stringify_grantobjtype(
+                                                                                                                                                cmd->d.grant.istmt->objtype));
+                               /* schema */
+                               nulls[i++] = true;
+                               /* identity */
+                               nulls[i++] = true;
+                               /* in_extension */
+                               values[i++] = BoolGetDatum(cmd->in_extension);
+                               /* command */
+                               values[i++] = PointerGetDatum(cmd);
+                               break;
+               }
+
+               tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+       }
+
+       /* clean up and return the tuplestore */
+       tuplestore_donestoring(tupstore);
+
+       PG_RETURN_VOID();
+}
+
+/*
+ * Return the GrantObjectType as a string, as it would appear in GRANT and
+ * REVOKE commands.
+ */
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+       switch (objtype)
+       {
+               case ACL_OBJECT_COLUMN:
+                       return "COLUMN";
+               case ACL_OBJECT_RELATION:
+                       return "TABLE";
+               case ACL_OBJECT_SEQUENCE:
+                       return "SEQUENCE";
+               case ACL_OBJECT_DATABASE:
+                       return "DATABASE";
+               case ACL_OBJECT_DOMAIN:
+                       return "DOMAIN";
+               case ACL_OBJECT_FDW:
+                       return "FOREIGN DATA WRAPPER";
+               case ACL_OBJECT_FOREIGN_SERVER:
+                       return "FOREIGN SERVER";
+               case ACL_OBJECT_FUNCTION:
+                       return "FUNCTION";
+               case ACL_OBJECT_LANGUAGE:
+                       return "LANGUAGE";
+               case ACL_OBJECT_LARGEOBJECT:
+                       return "LARGE OBJECT";
+               case ACL_OBJECT_NAMESPACE:
+                       return "SCHEMA";
+               case ACL_OBJECT_TABLESPACE:
+                       return "TABLESPACE";
+               case ACL_OBJECT_TYPE:
+                       return "TYPE";
+               default:
+                       elog(ERROR, "unrecognized type %d", objtype);
+                       return "???";   /* keep compiler quiet */
+       }
+}
+
+/*
+ * Return the GrantObjectType as a string; as above, but use the spelling
+ * in ALTER DEFAULT PRIVILEGES commands instead.
+ */
+static const char *
+stringify_adefprivs_objtype(GrantObjectType objtype)
+{
+       switch (objtype)
+       {
+               case ACL_OBJECT_RELATION:
+                       return "TABLES";
+                       break;
+               case ACL_OBJECT_FUNCTION:
+                       return "FUNCTIONS";
+                       break;
+               case ACL_OBJECT_SEQUENCE:
+                       return "SEQUENCES";
+                       break;
+               case ACL_OBJECT_TYPE:
+                       return "TYPES";
+                       break;
+               default:
+                       elog(ERROR, "unrecognized type %d", objtype);
+                       return "???";   /* keep compiler quiet */
+       }
+}
index c327cc0473e0dcf453410af54759b245cf6af592..3375f10861db97c0b4e9ebc9d7f775a69f113f8a 100644 (file)
@@ -25,6 +25,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
+#include "catalog/opfam_internal.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
 #include "catalog/pg_namespace.h"
@@ -35,6 +36,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
 #include "utils/tqual.h"
 
 
-/*
- * We use lists of this struct type to keep track of both operators and
- * procedures while building or adding to an opfamily.
- */
-typedef struct
-{
-       Oid                     object;                 /* operator or support proc's OID */
-       int                     number;                 /* strategy or support proc number */
-       Oid                     lefttype;               /* lefttype */
-       Oid                     righttype;              /* righttype */
-       Oid                     sortfamily;             /* ordering operator's sort opfamily, or 0 */
-} OpFamilyMember;
-
-
-static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt,
+                                Oid amoid, Oid opfamilyoid,
                                 int maxOpNumber, int maxProcNumber,
                                 List *items);
-static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
+static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt,
+                                 Oid amoid, Oid opfamilyoid,
                                  int maxOpNumber, int maxProcNumber,
                                  List *items);
 static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype);
@@ -675,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
        storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
                                        opclassoid, procedures, false);
 
+       /* let event triggers know what happened */
+       EventTriggerCollectCreateOpClass(stmt, opclassoid, operators, procedures);
+
        /*
         * Create dependencies for the opclass proper.  Note: we do not create a
         * dependency link to the AM, because we don't currently support DROP
@@ -822,13 +815,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
         * ADD and DROP cases need separate code from here on down.
         */
        if (stmt->isDrop)
-               AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid,
-                                                 maxOpNumber, maxProcNumber,
-                                                 stmt->items);
+               AlterOpFamilyDrop(stmt, amoid, opfamilyoid,
+                                                 maxOpNumber, maxProcNumber, stmt->items);
        else
-               AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid,
-                                                maxOpNumber, maxProcNumber,
-                                                stmt->items);
+               AlterOpFamilyAdd(stmt, amoid, opfamilyoid,
+                                                maxOpNumber, maxProcNumber, stmt->items);
 
        return opfamilyoid;
 }
@@ -837,9 +828,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt)
  * ADD part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
-                                int maxOpNumber, int maxProcNumber,
-                                List *items)
+AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
+                                int maxOpNumber, int maxProcNumber, List *items)
 {
        List       *operators;          /* OpFamilyMember list for operators */
        List       *procedures;         /* OpFamilyMember list for support procs */
@@ -958,19 +948,22 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid,
         * Add tuples to pg_amop and pg_amproc tying in the operators and
         * functions.  Dependencies on them are inserted, too.
         */
-       storeOperators(opfamilyname, amoid, opfamilyoid,
+       storeOperators(stmt->opfamilyname, amoid, opfamilyoid,
                                   InvalidOid, operators, true);
-       storeProcedures(opfamilyname, amoid, opfamilyoid,
+       storeProcedures(stmt->opfamilyname, amoid, opfamilyoid,
                                        InvalidOid, procedures, true);
+
+       /* make information available to event triggers */
+       EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+                                                                 operators, procedures);
 }
 
 /*
  * DROP part of ALTER OP FAMILY
  */
 static void
-AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
-                                 int maxOpNumber, int maxProcNumber,
-                                 List *items)
+AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid,
+                                 int maxOpNumber, int maxProcNumber, List *items)
 {
        List       *operators;          /* OpFamilyMember list for operators */
        List       *procedures;         /* OpFamilyMember list for support procs */
@@ -1033,8 +1026,12 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid,
        /*
         * Remove tuples from pg_amop and pg_amproc.
         */
-       dropOperators(opfamilyname, amoid, opfamilyoid, operators);
-       dropProcedures(opfamilyname, amoid, opfamilyoid, procedures);
+       dropOperators(stmt->opfamilyname, amoid, opfamilyoid, operators);
+       dropProcedures(stmt->opfamilyname, amoid, opfamilyoid, procedures);
+
+       /* make information available to event triggers */
+       EventTriggerCollectAlterOpFam(stmt, opfamilyoid,
+                                                                 operators, procedures);
 }
 
 
@@ -1673,7 +1670,7 @@ RemoveAmProcEntryById(Oid entryOid)
        heap_close(rel, RowExclusiveLock);
 }
 
-static char *
+char *
 get_am_name(Oid amOid)
 {
        HeapTuple       tup;
index c090ed220f8ee3d7fc46feec62de249d07e66eef..5a7beff7d56c89ef280090657e3f860b7c5d964e 100644 (file)
@@ -25,6 +25,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
        Oid                     saved_uid;
        int                     save_sec_context;
        AclResult       aclresult;
+       ObjectAddress address;
 
        GetUserIdAndSecContext(&saved_uid, &save_sec_context);
 
@@ -142,6 +144,16 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
        /* XXX should we clear overridePath->useTemp? */
        PushOverrideSearchPath(overridePath);
 
+       /*
+        * Report the new schema to possibly interested event triggers.  Note we
+        * must do this here and not in ProcessUtilitySlow because otherwise the
+        * objects created below are reported before the schema, which would be
+        * wrong.
+        */
+       ObjectAddressSet(address, NamespaceRelationId, namespaceId);
+       EventTriggerCollectSimpleCommand(address, InvalidObjectAddress,
+                                                                        (Node *) stmt);
+
        /*
         * Examine the list of commands embedded in the CREATE SCHEMA command, and
         * reorganize them into a sequentially executable order with no forward
index 299d8ccd81f7fbfd8edbcabb00c22c1a08999eaf..0a6b069065790639e615d234ed71358a75f04d03 100644 (file)
@@ -2789,6 +2789,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
        rel = relation_open(relid, lockmode);
 
+       EventTriggerAlterTableRelid(relid);
+
        ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3672,8 +3674,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                        break;
        }
 
-       /* supress compiler warning until we have some use for the address */
-       (void) address;
+       /*
+        * Report the subcommand to interested event triggers.
+        */
+       EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
 
        /*
         * Bump the command counter to ensure the next subcommand in the sequence
@@ -9728,7 +9732,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
                cmds = lappend(cmds, cmd);
 
+               EventTriggerAlterTableStart((Node *) stmt);
+               /* OID is set by AlterTableInternal */
                AlterTableInternal(lfirst_oid(l), cmds, false);
+               EventTriggerAlterTableEnd();
        }
 
        return new_tablespaceoid;
index 4c404e73d0913a2ea97ed66b1465577e38730081..ff900401935beed095ce1d821a7e1107f6522d16 100644 (file)
@@ -34,6 +34,7 @@
 #include "catalog/pg_type.h"
 #include "commands/alter.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_func.h"
@@ -1442,6 +1443,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
                        }
                }
        }
+
+       EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
 }
 
 /*
@@ -1509,6 +1512,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
                i++;
        }
+
+       EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
 }
 
 
index ed8fa72621ece43657d7d647600946f7d0e3ec2d..76b63afe897265d34045bddf4374607bcf9dac3b 100644 (file)
@@ -3972,6 +3972,7 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 {
        AlterTSConfigurationStmt *newnode = makeNode(AlterTSConfigurationStmt);
 
+       COPY_SCALAR_FIELD(kind);
        COPY_NODE_FIELD(cfgname);
        COPY_NODE_FIELD(tokentype);
        COPY_NODE_FIELD(dicts);
index 7c86e919a493a72d5ee5e753450fa433c4c3ad7b..e032142b50967168bafaf66bb14e9afb5f12c199 100644 (file)
@@ -2032,6 +2032,7 @@ static bool
 _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
                                                           const AlterTSConfigurationStmt *b)
 {
+       COMPARE_SCALAR_FIELD(kind);
        COMPARE_NODE_FIELD(cfgname);
        COMPARE_NODE_FIELD(tokentype);
        COMPARE_NODE_FIELD(dicts);
index 7a4c07365c1d3222be563c7a7695a7c06cfa4bad..e71d9262caa4b9434a8cd73e49a14242880aaf01 100644 (file)
@@ -8998,6 +8998,7 @@ AlterTSConfigurationStmt:
                        ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list
                                {
                                        AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+                                       n->kind = ALTER_TSCONFIG_ADD_MAPPING;
                                        n->cfgname = $5;
                                        n->tokentype = $9;
                                        n->dicts = $11;
@@ -9008,6 +9009,7 @@ AlterTSConfigurationStmt:
                        | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list
                                {
                                        AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+                                       n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN;
                                        n->cfgname = $5;
                                        n->tokentype = $9;
                                        n->dicts = $11;
@@ -9018,6 +9020,7 @@ AlterTSConfigurationStmt:
                        | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name any_with any_name
                                {
                                        AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+                                       n->kind = ALTER_TSCONFIG_REPLACE_DICT;
                                        n->cfgname = $5;
                                        n->tokentype = NIL;
                                        n->dicts = list_make2($9,$11);
@@ -9028,6 +9031,7 @@ AlterTSConfigurationStmt:
                        | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name any_with any_name
                                {
                                        AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+                                       n->kind = ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN;
                                        n->cfgname = $5;
                                        n->tokentype = $9;
                                        n->dicts = list_make2($11,$13);
@@ -9038,6 +9042,7 @@ AlterTSConfigurationStmt:
                        | ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING FOR name_list
                                {
                                        AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+                                       n->kind = ALTER_TSCONFIG_DROP_MAPPING;
                                        n->cfgname = $5;
                                        n->tokentype = $9;
                                        n->missing_ok = false;
@@ -9046,6 +9051,7 @@ AlterTSConfigurationStmt:
                        | ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING IF_P EXISTS FOR name_list
                                {
                                        AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt);
+                                       n->kind = ALTER_TSCONFIG_DROP_MAPPING;
                                        n->cfgname = $5;
                                        n->tokentype = $11;
                                        n->missing_ok = true;
index 59f09dc93afb92852ccddd27fe5c8524ddef7c8f..78bfd349a77e59893e4e716be31a8f0381e3aed9 100644 (file)
@@ -912,7 +912,9 @@ ProcessUtilitySlow(Node *parsetree,
        bool            isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
        bool            isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
        bool            needCleanup;
+       bool            commandCollected = false;
        ObjectAddress address;
+       ObjectAddress secondaryObject = InvalidObjectAddress;
 
        /* All event trigger calls are done only when isCompleteQuery is true */
        needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -931,6 +933,11 @@ ProcessUtilitySlow(Node *parsetree,
                        case T_CreateSchemaStmt:
                                CreateSchemaCommand((CreateSchemaStmt *) parsetree,
                                                                        queryString);
+                               /*
+                                * EventTriggerCollectSimpleCommand called by
+                                * CreateSchemaCommand
+                                */
+                               commandCollected = true;
                                break;
 
                        case T_CreateStmt:
@@ -957,6 +964,9 @@ ProcessUtilitySlow(Node *parsetree,
                                                        address = DefineRelation((CreateStmt *) stmt,
                                                                                                         RELKIND_RELATION,
                                                                                                         InvalidOid, NULL);
+                                                       EventTriggerCollectSimpleCommand(address,
+                                                                                                                        secondaryObject,
+                                                                                                                        stmt);
 
                                                        /*
                                                         * Let NewRelationCreateToastTable decide if this
@@ -989,10 +999,17 @@ ProcessUtilitySlow(Node *parsetree,
                                                                                                         InvalidOid, NULL);
                                                        CreateForeignTable((CreateForeignTableStmt *) stmt,
                                                                                           address.objectId);
+                                                       EventTriggerCollectSimpleCommand(address,
+                                                                                                                        secondaryObject,
+                                                                                                                        stmt);
                                                }
                                                else
                                                {
-                                                       /* Recurse for anything else */
+                                                       /*
+                                                        * Recurse for anything else.  Note the recursive
+                                                        * call will stash the objects so created into our
+                                                        * event trigger context.
+                                                        */
                                                        ProcessUtility(stmt,
                                                                                   queryString,
                                                                                   PROCESS_UTILITY_SUBCOMMAND,
@@ -1005,6 +1022,12 @@ ProcessUtilitySlow(Node *parsetree,
                                                if (lnext(l) != NULL)
                                                        CommandCounterIncrement();
                                        }
+
+                                       /*
+                                        * The multiple commands generated here are stashed
+                                        * individually, so disable collection below.
+                                        */
+                                       commandCollected = true;
                                }
                                break;
 
@@ -1031,6 +1054,10 @@ ProcessUtilitySlow(Node *parsetree,
                                                stmts = transformAlterTableStmt(relid, atstmt,
                                                                                                                queryString);
 
+                                               /* ... ensure we have an event trigger context ... */
+                                               EventTriggerAlterTableStart(parsetree);
+                                               EventTriggerAlterTableRelid(relid);
+
                                                /* ... and do it */
                                                foreach(l, stmts)
                                                {
@@ -1044,25 +1071,41 @@ ProcessUtilitySlow(Node *parsetree,
                                                        }
                                                        else
                                                        {
-                                                               /* Recurse for anything else */
+                                                               /*
+                                                                * Recurse for anything else.  If we need to do
+                                                                * so, "close" the current complex-command set,
+                                                                * and start a new one at the bottom; this is
+                                                                * needed to ensure the ordering of queued
+                                                                * commands is consistent with the way they are
+                                                                * executed here.
+                                                                */
+                                                               EventTriggerAlterTableEnd();
                                                                ProcessUtility(stmt,
                                                                                           queryString,
                                                                                           PROCESS_UTILITY_SUBCOMMAND,
                                                                                           params,
                                                                                           None_Receiver,
                                                                                           NULL);
+                                                               EventTriggerAlterTableStart(parsetree);
+                                                               EventTriggerAlterTableRelid(relid);
                                                        }
 
                                                        /* Need CCI between commands */
                                                        if (lnext(l) != NULL)
                                                                CommandCounterIncrement();
                                                }
+
+                                               /* done */
+                                               EventTriggerAlterTableEnd();
                                        }
                                        else
                                                ereport(NOTICE,
                                                  (errmsg("relation \"%s\" does not exist, skipping",
                                                                  atstmt->relation->relname)));
                                }
+
+                               /* ALTER TABLE stashes commands internally */
+                               commandCollected = true;
                                break;
 
                        case T_AlterDomainStmt:
@@ -1081,31 +1124,37 @@ ProcessUtilitySlow(Node *parsetree,
                                                         * Recursively alter column default for table and,
                                                         * if requested, for descendants
                                                         */
-                                                       AlterDomainDefault(stmt->typeName,
-                                                                                          stmt->def);
+                                                       address =
+                                                               AlterDomainDefault(stmt->typeName,
+                                                                                                  stmt->def);
                                                        break;
                                                case 'N':               /* ALTER DOMAIN DROP NOT NULL */
-                                                       AlterDomainNotNull(stmt->typeName,
-                                                                                          false);
+                                                       address =
+                                                               AlterDomainNotNull(stmt->typeName,
+                                                                                                  false);
                                                        break;
                                                case 'O':               /* ALTER DOMAIN SET NOT NULL */
-                                                       AlterDomainNotNull(stmt->typeName,
-                                                                                          true);
+                                                       address =
+                                                               AlterDomainNotNull(stmt->typeName,
+                                                                                                  true);
                                                        break;
                                                case 'C':               /* ADD CONSTRAINT */
-                                                       AlterDomainAddConstraint(stmt->typeName,
-                                                                                                        stmt->def,
-                                                                                                        NULL);
+                                                       address =
+                                                               AlterDomainAddConstraint(stmt->typeName,
+                                                                                                                stmt->def,
+                                                                                                                &secondaryObject);
                                                        break;
                                                case 'X':               /* DROP CONSTRAINT */
-                                                       AlterDomainDropConstraint(stmt->typeName,
-                                                                                                         stmt->name,
-                                                                                                         stmt->behavior,
-                                                                                                         stmt->missing_ok);
+                                                       address =
+                                                               AlterDomainDropConstraint(stmt->typeName,
+                                                                                                                 stmt->name,
+                                                                                                                 stmt->behavior,
+                                                                                                                 stmt->missing_ok);
                                                        break;
                                                case 'V':               /* VALIDATE CONSTRAINT */
-                                                       AlterDomainValidateConstraint(stmt->typeName,
-                                                                                                                 stmt->name);
+                                                       address =
+                                                               AlterDomainValidateConstraint(stmt->typeName,
+                                                                                                                         stmt->name);
                                                        break;
                                                default:                /* oops */
                                                        elog(ERROR, "unrecognized alter domain type: %d",
@@ -1125,41 +1174,46 @@ ProcessUtilitySlow(Node *parsetree,
                                        switch (stmt->kind)
                                        {
                                                case OBJECT_AGGREGATE:
-                                                       DefineAggregate(stmt->defnames, stmt->args,
-                                                                                       stmt->oldstyle, stmt->definition,
-                                                                                       queryString);
+                                                       address =
+                                                               DefineAggregate(stmt->defnames, stmt->args,
+                                                                                               stmt->oldstyle,
+                                                                                               stmt->definition, queryString);
                                                        break;
                                                case OBJECT_OPERATOR:
                                                        Assert(stmt->args == NIL);
-                                                       DefineOperator(stmt->defnames, stmt->definition);
+                                                       address = DefineOperator(stmt->defnames,
+                                                                                                         stmt->definition);
                                                        break;
                                                case OBJECT_TYPE:
                                                        Assert(stmt->args == NIL);
-                                                       DefineType(stmt->defnames, stmt->definition);
+                                                       address = DefineType(stmt->defnames,
+                                                                                                 stmt->definition);
                                                        break;
                                                case OBJECT_TSPARSER:
                                                        Assert(stmt->args == NIL);
-                                                       DefineTSParser(stmt->defnames, stmt->definition);
+                                                       address = DefineTSParser(stmt->defnames,
+                                                                                                         stmt->definition);
                                                        break;
                                                case OBJECT_TSDICTIONARY:
                                                        Assert(stmt->args == NIL);
-                                                       DefineTSDictionary(stmt->defnames,
-                                                                                          stmt->definition);
+                                                       address = DefineTSDictionary(stmt->defnames,
+                                                                                                                 stmt->definition);
                                                        break;
                                                case OBJECT_TSTEMPLATE:
                                                        Assert(stmt->args == NIL);
-                                                       DefineTSTemplate(stmt->defnames,
-                                                                                        stmt->definition);
+                                                       address = DefineTSTemplate(stmt->defnames,
+                                                                                                               stmt->definition);
                                                        break;
                                                case OBJECT_TSCONFIGURATION:
                                                        Assert(stmt->args == NIL);
-                                                       DefineTSConfiguration(stmt->defnames,
-                                                                                                 stmt->definition,
-                                                                                                 NULL);
+                                                       address = DefineTSConfiguration(stmt->defnames,
+                                                                                                                        stmt->definition,
+                                                                                                                        &secondaryObject);
                                                        break;
                                                case OBJECT_COLLATION:
                                                        Assert(stmt->args == NIL);
-                                                       DefineCollation(stmt->defnames, stmt->definition);
+                                                       address = DefineCollation(stmt->defnames,
+                                                                                                          stmt->definition);
                                                        break;
                                                default:
                                                        elog(ERROR, "unrecognized define stmt type: %d",
@@ -1200,143 +1254,184 @@ ProcessUtilitySlow(Node *parsetree,
                                        stmt = transformIndexStmt(relid, stmt, queryString);
 
                                        /* ... and do it */
-                                       DefineIndex(relid,      /* OID of heap relation */
-                                                               stmt,
-                                                               InvalidOid,             /* no predefined OID */
-                                                               false,  /* is_alter_table */
-                                                               true,   /* check_rights */
-                                                               false,  /* skip_build */
-                                                               false); /* quiet */
+                                       EventTriggerAlterTableStart(parsetree);
+                                       address =
+                                               DefineIndex(relid,      /* OID of heap relation */
+                                                                       stmt,
+                                                                       InvalidOid,             /* no predefined OID */
+                                                                       false,  /* is_alter_table */
+                                                                       true,   /* check_rights */
+                                                                       false,  /* skip_build */
+                                                                       false); /* quiet */
+                                       /*
+                                        * Add the CREATE INDEX node itself to stash right away; if
+                                        * there were any commands stashed in the ALTER TABLE code,
+                                        * we need them to appear after this one.
+                                        */
+                                       EventTriggerCollectSimpleCommand(address, secondaryObject,
+                                                                                                        parsetree);
+                                       commandCollected = true;
+                                       EventTriggerAlterTableEnd();
                                }
                                break;
 
                        case T_CreateExtensionStmt:
-                               CreateExtension((CreateExtensionStmt *) parsetree);
+                               address = CreateExtension((CreateExtensionStmt *) parsetree);
                                break;
 
                        case T_AlterExtensionStmt:
-                               ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+                               address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
                                break;
 
                        case T_AlterExtensionContentsStmt:
-                               ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
-                                                                                          NULL);
+                               address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree,
+                                                                                                                 &secondaryObject);
                                break;
 
                        case T_CreateFdwStmt:
-                               CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+                               address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
                                break;
 
                        case T_AlterFdwStmt:
-                               AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
+                               address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
                                break;
 
                        case T_CreateForeignServerStmt:
-                               CreateForeignServer((CreateForeignServerStmt *) parsetree);
+                               address = CreateForeignServer((CreateForeignServerStmt *) parsetree);
                                break;
 
                        case T_AlterForeignServerStmt:
-                               AlterForeignServer((AlterForeignServerStmt *) parsetree);
+                               address = AlterForeignServer((AlterForeignServerStmt *) parsetree);
                                break;
 
                        case T_CreateUserMappingStmt:
-                               CreateUserMapping((CreateUserMappingStmt *) parsetree);
+                               address = CreateUserMapping((CreateUserMappingStmt *) parsetree);
                                break;
 
                        case T_AlterUserMappingStmt:
-                               AlterUserMapping((AlterUserMappingStmt *) parsetree);
+                               address = AlterUserMapping((AlterUserMappingStmt *) parsetree);
                                break;
 
                        case T_DropUserMappingStmt:
                                RemoveUserMapping((DropUserMappingStmt *) parsetree);
+                               /* no commands stashed for DROP */
+                               commandCollected = true;
                                break;
 
                        case T_ImportForeignSchemaStmt:
                                ImportForeignSchema((ImportForeignSchemaStmt *) parsetree);
+                               /* commands are stashed inside ImportForeignSchema */
+                               commandCollected = true;
                                break;
 
                        case T_CompositeTypeStmt:       /* CREATE TYPE (composite) */
                                {
                                        CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-                                       DefineCompositeType(stmt->typevar, stmt->coldeflist);
+                                       address = DefineCompositeType(stmt->typevar,
+                                                                                                 stmt->coldeflist);
                                }
                                break;
 
                        case T_CreateEnumStmt:          /* CREATE TYPE AS ENUM */
-                               DefineEnum((CreateEnumStmt *) parsetree);
+                               address = DefineEnum((CreateEnumStmt *) parsetree);
                                break;
 
                        case T_CreateRangeStmt:         /* CREATE TYPE AS RANGE */
-                               DefineRange((CreateRangeStmt *) parsetree);
+                               address = DefineRange((CreateRangeStmt *) parsetree);
                                break;
 
                        case T_AlterEnumStmt:           /* ALTER TYPE (enum) */
-                               AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+                               address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
                                break;
 
                        case T_ViewStmt:        /* CREATE VIEW */
-                               DefineView((ViewStmt *) parsetree, queryString);
+                               EventTriggerAlterTableStart(parsetree);
+                               address = DefineView((ViewStmt *) parsetree, queryString);
+                               EventTriggerCollectSimpleCommand(address, secondaryObject,
+                                                                                                parsetree);
+                               /* stashed internally */
+                               commandCollected = true;
+                               EventTriggerAlterTableEnd();
                                break;
 
                        case T_CreateFunctionStmt:      /* CREATE FUNCTION */
-                               CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+                               address = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
                                break;
 
                        case T_AlterFunctionStmt:       /* ALTER FUNCTION */
-                               AlterFunction((AlterFunctionStmt *) parsetree);
+                               address = AlterFunction((AlterFunctionStmt *) parsetree);
                                break;
 
                        case T_RuleStmt:        /* CREATE RULE */
-                               DefineRule((RuleStmt *) parsetree, queryString);
+                               address = DefineRule((RuleStmt *) parsetree, queryString);
                                break;
 
                        case T_CreateSeqStmt:
-                               DefineSequence((CreateSeqStmt *) parsetree);
+                               address = DefineSequence((CreateSeqStmt *) parsetree);
                                break;
 
                        case T_AlterSeqStmt:
-                               AlterSequence((AlterSeqStmt *) parsetree);
+                               address = AlterSequence((AlterSeqStmt *) parsetree);
                                break;
 
                        case T_CreateTableAsStmt:
-                               ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+                               address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
                                                                  queryString, params, completionTag);
                                break;
 
                        case T_RefreshMatViewStmt:
-                               ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
-                                                                  queryString, params, completionTag);
+                               /*
+                                * REFRSH CONCURRENTLY executes some DDL commands internally.
+                                * Inhibit DDL command collection here to avoid those commands
+                                * from showing up in the deparsed command queue.  The refresh
+                                * command itself is queued, which is enough.
+                                */
+                               EventTriggerInhibitCommandCollection();
+                               PG_TRY();
+                               {
+                                       address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+                                                                                                queryString, params, completionTag);
+                               }
+                               PG_CATCH();
+                               {
+                                       EventTriggerUndoInhibitCommandCollection();
+                                       PG_RE_THROW();
+                               }
+                               PG_END_TRY();
+                               EventTriggerUndoInhibitCommandCollection();
                                break;
 
                        case T_CreateTrigStmt:
-                               (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
-                                                                        InvalidOid, InvalidOid, InvalidOid,
-                                                                        InvalidOid, false);
+                               address = CreateTrigger((CreateTrigStmt *) parsetree,
+                                                                                queryString, InvalidOid, InvalidOid,
+                                                                                InvalidOid, InvalidOid, false);
                                break;
 
                        case T_CreatePLangStmt:
-                               CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+                               address = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
                                break;
 
                        case T_CreateDomainStmt:
-                               DefineDomain((CreateDomainStmt *) parsetree);
+                               address = DefineDomain((CreateDomainStmt *) parsetree);
                                break;
 
                        case T_CreateConversionStmt:
-                               CreateConversionCommand((CreateConversionStmt *) parsetree);
+                               address = CreateConversionCommand((CreateConversionStmt *) parsetree);
                                break;
 
                        case T_CreateCastStmt:
-                               CreateCast((CreateCastStmt *) parsetree);
+                               address = CreateCast((CreateCastStmt *) parsetree);
                                break;
 
                        case T_CreateOpClassStmt:
                                DefineOpClass((CreateOpClassStmt *) parsetree);
+                               /* command is stashed in DefineOpClass */
+                               commandCollected = true;
                                break;
 
                        case T_CreateOpFamilyStmt:
-                               DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+                               address = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
                                break;
 
                        case T_CreateTransformStmt:
@@ -1345,63 +1440,76 @@ ProcessUtilitySlow(Node *parsetree,
 
                        case T_AlterOpFamilyStmt:
                                AlterOpFamily((AlterOpFamilyStmt *) parsetree);
+                               /* commands are stashed in AlterOpFamily */
+                               commandCollected = true;
                                break;
 
                        case T_AlterTSDictionaryStmt:
-                               AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
+                               address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
                                break;
 
                        case T_AlterTSConfigurationStmt:
-                               AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
+                               address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
                                break;
 
                        case T_AlterTableMoveAllStmt:
                                AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree);
+                               /* commands are stashed in AlterTableMoveAll */
+                               commandCollected = true;
                                break;
 
                        case T_DropStmt:
                                ExecDropStmt((DropStmt *) parsetree, isTopLevel);
+                               /* no commands stashed for DROP */
+                               commandCollected = true;
                                break;
 
                        case T_RenameStmt:
-                               ExecRenameStmt((RenameStmt *) parsetree);
+                               address = ExecRenameStmt((RenameStmt *) parsetree);
                                break;
 
                        case T_AlterObjectSchemaStmt:
-                               ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
-                                                                                 NULL);
+                               address =
+                                       ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree,
+                                                                                         &secondaryObject);
                                break;
 
                        case T_AlterOwnerStmt:
-                               ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+                               address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
                                break;
 
                        case T_CommentStmt:
-                               CommentObject((CommentStmt *) parsetree);
+                               address = CommentObject((CommentStmt *) parsetree);
                                break;
 
                        case T_GrantStmt:
                                ExecuteGrantStmt((GrantStmt *) parsetree);
+                               /* commands are stashed in ExecGrantStmt_oids */
+                               commandCollected = true;
                                break;
 
                        case T_DropOwnedStmt:
                                DropOwnedObjects((DropOwnedStmt *) parsetree);
+                               /* no commands stashed for DROP */
+                               commandCollected = true;
                                break;
 
                        case T_AlterDefaultPrivilegesStmt:
                                ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
+                               EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree);
+                               commandCollected = true;
                                break;
 
                        case T_CreatePolicyStmt:        /* CREATE POLICY */
-                               CreatePolicy((CreatePolicyStmt *) parsetree);
+                               address = CreatePolicy((CreatePolicyStmt *) parsetree);
                                break;
 
                        case T_AlterPolicyStmt:         /* ALTER POLICY */
-                               AlterPolicy((AlterPolicyStmt *) parsetree);
+                               address = AlterPolicy((AlterPolicyStmt *) parsetree);
                                break;
 
                        case T_SecLabelStmt:
-                               ExecSecLabelStmt((SecLabelStmt *) parsetree);
+                               address = ExecSecLabelStmt((SecLabelStmt *) parsetree);
                                break;
 
                        default:
@@ -1410,6 +1518,14 @@ ProcessUtilitySlow(Node *parsetree,
                                break;
                }
 
+               /*
+                * Remember the object so that ddl_command_end event triggers have
+                * access to it.
+                */
+               if (!commandCollected)
+                       EventTriggerCollectSimpleCommand(address, secondaryObject,
+                                                                                        parsetree);
+
                if (isCompleteQuery)
                {
                        EventTriggerSQLDrop(parsetree);
index 2f0f0a1d8952587b7f34913c465cf78aaedba208..a8519835cc66215aebd193ead8f682438c1d0d12 100644 (file)
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
        return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
index 6e663c7a2bb0a21164fda3085d1a7da1379c87b0..9ad460abfbdbcc8f1e75613b00841b7926af5592 100644 (file)
@@ -522,11 +522,12 @@ pg_node_tree_in(PG_FUNCTION_ARGS)
         */
        ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("cannot accept a value of type pg_node_tree")));
+                        errmsg("cannot accept a value of type %s", "pg_node_tree")));
 
        PG_RETURN_VOID();                       /* keep compiler quiet */
 }
 
+
 /*
  * pg_node_tree_out            - output routine for type PG_NODE_TREE.
  *
@@ -546,7 +547,7 @@ pg_node_tree_recv(PG_FUNCTION_ARGS)
 {
        ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("cannot accept a value of type pg_node_tree")));
+                        errmsg("cannot accept a value of type %s", "pg_node_tree")));
 
        PG_RETURN_VOID();                       /* keep compiler quiet */
 }
@@ -559,3 +560,63 @@ pg_node_tree_send(PG_FUNCTION_ARGS)
 {
        return textsend(fcinfo);
 }
+
+/*
+ * pg_ddl_command_in   - input routine for type PG_DDL_COMMAND.
+ *
+ * Like pg_node_tree, pg_ddl_command isn't really a pseudotype; it's here for
+ * the same reasons as that one.
+ */
+Datum
+pg_ddl_command_in(PG_FUNCTION_ARGS)
+{
+       /*
+        * Disallow input of pg_ddl_command value.
+        */
+       ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("cannot accept a value of type %s", "pg_ddl_command")));
+
+       PG_RETURN_VOID();                       /* keep compiler quiet */
+}
+
+/*
+ * pg_ddl_command_out          - output routine for type PG_DDL_COMMAND.
+ *
+ * We don't have any good way to output this type directly, so punt.
+ */
+Datum
+pg_ddl_command_out(PG_FUNCTION_ARGS)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("cannot output a value of type %s", "pg_ddl_command")));
+
+       PG_RETURN_VOID();
+}
+
+/*
+ * pg_ddl_command_recv         - binary input routine for type PG_DDL_COMMAND.
+ */
+Datum
+pg_ddl_command_recv(PG_FUNCTION_ARGS)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("cannot accept a value of type %s", "pg_ddl_command")));
+
+       PG_RETURN_VOID();
+}
+
+/*
+ * pg_ddl_command_send         - binary output routine for type PG_DDL_COMMAND.
+ */
+Datum
+pg_ddl_command_send(PG_FUNCTION_ARGS)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("cannot output a value of type %s", "pg_ddl_command")));
+
+       PG_RETURN_VOID();
+}
index 662ba27a414095f0231cdac955cd2120d47144af..80176dd287a3d991eca095a6a927b0efe51004aa 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201505091
+#define CATALOG_VERSION_NO     201505111
 
 #endif
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644 (file)
index 0000000..f01dcbe
--- /dev/null
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------
+ *
+ * opfam_internal.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/opfam_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef OPFAM_INTERNAL_H
+#define OPFAM_INTERNAL_H
+
+/*
+ * We use lists of this struct type to keep track of both operators and
+ * procedures while building or adding to an opfamily.
+ */
+typedef struct
+{
+       Oid                     object;                 /* operator or support proc's OID */
+       int                     number;                 /* strategy or support proc number */
+       Oid                     lefttype;               /* lefttype */
+       Oid                     righttype;              /* righttype */
+       Oid                     sortfamily;             /* ordering operator's sort opfamily, or 0 */
+} OpFamilyMember;
+
+#endif         /* OPFAM_INTERNAL_H */
index 5fa65d63a87ec5267b405ff93713bab120e2f00c..41838c0a8d36d318fac4d0a31f34b10b0e7c2b84 100644 (file)
@@ -233,6 +233,15 @@ DATA(insert OID =  84 (  boolne                       PGNSP PGUID 12 1 0 0 0 f f f t t f i 2 0 16
 DATA(insert OID =  89 (  version                  PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 25 "" _null_ _null_ _null_ _null_ _null_ pgsql_version _null_ _null_ _null_ ));
 DESCR("PostgreSQL version string");
 
+DATA(insert OID = 86  (  pg_ddl_command_in             PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 32 "2275" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 87  (  pg_ddl_command_out            PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "32" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_out _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 88  (  pg_ddl_command_recv   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 32 "2281" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_recv _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 90  (  pg_ddl_command_send   PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "32" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_send _null_ _null_ _null_ ));
+DESCR("I/O");
+
 /* OIDS 100 - 199 */
 
 DATA(insert OID = 101 (  eqsel                    PGNSP PGUID 12 1 0 0 0 f f f f t f s 4 0 701 "2281 26 2281 23" _null_ _null_ _null_ _null_ _null_    eqsel _null_ _null_ _null_ ));
@@ -5163,6 +5172,8 @@ DATA(insert OID = 4566 (  pg_event_trigger_table_rewrite_oid      PGNSP PGUID 12 1 0
 DESCR("return Oid of the table getting rewritten");
 DATA(insert OID = 4567 (  pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
 DESCR("return reason code for table getting rewritten");
+DATA(insert OID = 4568 (  pg_event_trigger_ddl_commands                        PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,32}" "{o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, command_tag, object_type, schema_name, object_identity, in_extension, command}" _null_ _null_ pg_event_trigger_ddl_commands _null_ _null_ _null_ ));
+DESCR("list DDL actions being executed by the current command");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition                        PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
index 24933539aabe384f893f115f368d54c273a094a0..4284a704d3c0486a5d7136dc5c464666ddeb727a 100644 (file)
@@ -364,6 +364,10 @@ DATA(insert OID = 194 ( pg_node_tree       PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node
 DESCR("string representing an internal node tree");
 #define PGNODETREEOID  194
 
+DATA(insert OID = 32 ( pg_ddl_command   PGNSP PGUID SIZEOF_POINTER t p P f t \054 0 0 0 pg_ddl_command_in pg_ddl_command_out pg_ddl_command_recv pg_ddl_command_send - - - ALIGNOF_POINTER p f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("internal type for passing CollectedCommand");
+#define PGDDLCOMMANDOID 32
+
 /* OIDS 200 - 299 */
 
 DATA(insert OID = 210 (  smgr     PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 0 _null_ _null_ _null_ ));
index 335f09cba4cc9e9c09cc7dedd0dcdd54425867ae..c3a1748e00f9a1af07b0a0cb8241ad64385a976c 100644 (file)
@@ -90,6 +90,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod,
 extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod,
                                                   Oid opfnamespace);
 extern Oid     get_am_oid(const char *amname, bool missing_ok);
+extern char *get_am_name(Oid amOid);
 extern Oid     get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
 extern Oid     get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
 
index 7eb2156085680772bea75c0b078f218f45bf8496..579e1ef8bd2a915f39af97106a4f4d67c60a81ed 100644 (file)
@@ -17,6 +17,8 @@
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
+#include "utils/aclchk_internal.h"
+#include "tcop/deparse_utility.h"
 
 typedef struct EventTriggerData
 {
@@ -60,4 +62,28 @@ extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(const ObjectAddress *object,
                                                         bool original, bool normal);
 
+extern void EventTriggerInhibitCommandCollection(void);
+extern void EventTriggerUndoInhibitCommandCollection(void);
+
+extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
+                                                                ObjectAddress secondaryObject,
+                                                                Node *parsetree);
+
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd,
+                                                                 ObjectAddress address);
+extern void EventTriggerAlterTableEnd(void);
+
+extern void EventTriggerCollectGrant(InternalGrant *istmt);
+extern void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt,
+                                                         Oid opfamoid, List *operators,
+                                                         List *procedures);
+extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt,
+                                                                Oid opcoid, List *operators,
+                                                                List *procedures);
+extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt,
+                                                                Oid cfgId, Oid *dictIds, int ndicts);
+extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
+
 #endif   /* EVENT_TRIGGER_H */
index 40ecea2fee46bf944543cc95078aea6810bebaa5..0423350c9aa56a69d1bf21ce7da1d90344ab2a85 100644 (file)
@@ -24,7 +24,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid     CurrentExtensionObject;
 
 
index 91ca9c6fd0e1a465aa91f12f5636e235efd3f950..556c1c5d9da5cf411e39f0f127c92d142bf3b93e 100644 (file)
@@ -2911,9 +2911,19 @@ typedef struct AlterTSDictionaryStmt
 /*
  * TS Configuration stmts: DefineStmt, RenameStmt and DropStmt are default
  */
+typedef enum AlterTSConfigType
+{
+       ALTER_TSCONFIG_ADD_MAPPING,
+       ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN,
+       ALTER_TSCONFIG_REPLACE_DICT,
+       ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN,
+       ALTER_TSCONFIG_DROP_MAPPING
+} AlterTSConfigType;
+
 typedef struct AlterTSConfigurationStmt
 {
        NodeTag         type;
+       AlterTSConfigType       kind;   /* ALTER_TSCONFIG_ADD_MAPPING, etc */
        List       *cfgname;            /* qualified name (list of Value strings) */
 
        /*
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644 (file)
index 0000000..b6bcbeb
--- /dev/null
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+#include "access/attnum.h"
+#include "catalog/objectaddress.h"
+#include "nodes/nodes.h"
+#include "utils/aclchk_internal.h"
+
+
+/*
+ * Support for keeping track of collected commands.
+ */
+typedef enum CollectedCommandType
+{
+       SCT_Simple,
+       SCT_AlterTable,
+       SCT_Grant,
+       SCT_AlterOpFamily,
+       SCT_AlterDefaultPrivileges,
+       SCT_CreateOpClass,
+       SCT_AlterTSConfig
+} CollectedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct CollectedATSubcmd
+{
+       ObjectAddress   address; /* affected column, constraint, index, ... */
+       Node               *parsetree;
+} CollectedATSubcmd;
+
+typedef struct CollectedCommand
+{
+       CollectedCommandType type;
+       bool            in_extension;
+       Node       *parsetree;
+
+       union
+       {
+               /* most commands */
+               struct
+               {
+                       ObjectAddress address;
+                       ObjectAddress secondaryObject;
+               } simple;
+
+               /* ALTER TABLE, and internal uses thereof */
+               struct
+               {
+                       Oid             objectId;
+                       Oid             classId;
+                       List   *subcmds;
+               } alterTable;
+
+               /* GRANT / REVOKE */
+               struct
+               {
+                       InternalGrant *istmt;
+               } grant;
+
+               /* ALTER OPERATOR FAMILY */
+               struct
+               {
+                       ObjectAddress address;
+                       List   *operators;
+                       List   *procedures;
+               } opfam;
+
+               /* CREATE OPERATOR CLASS */
+               struct
+               {
+                       ObjectAddress address;
+                       List   *operators;
+                       List   *procedures;
+               } createopc;
+
+               /* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER/DROP MAPPING */
+               struct
+               {
+                       ObjectAddress address;
+                       Oid        *dictIds;
+                       int             ndicts;
+               } atscfg;
+
+               /* ALTER DEFAULT PRIVILEGES */
+               struct
+               {
+                       GrantObjectType objtype;
+               } defprivs;
+       } d;
+} CollectedCommand;
+
+#endif /* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
new file mode 100644 (file)
index 0000000..0855bf1
--- /dev/null
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk_internal.h
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_INTERNAL_H
+#define ACLCHK_INTERNAL_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+       bool            is_grant;
+       GrantObjectType objtype;
+       List       *objects;
+       bool            all_privs;
+       AclMode         privileges;
+       List       *col_privs;
+       List       *grantees;
+       bool            grant_option;
+       DropBehavior behavior;
+} InternalGrant;
+
+
+#endif /* ACLCHK_INTERNAL_H */
index a90bfe29e9f5c93fd19419f4ddca3950332c567e..1140c17792bde8e8e986fe04a25d3c9aa257b8c0 100644 (file)
@@ -576,6 +576,10 @@ extern Datum pg_node_tree_in(PG_FUNCTION_ARGS);
 extern Datum pg_node_tree_out(PG_FUNCTION_ARGS);
 extern Datum pg_node_tree_recv(PG_FUNCTION_ARGS);
 extern Datum pg_node_tree_send(PG_FUNCTION_ARGS);
+extern Datum pg_ddl_command_in(PG_FUNCTION_ARGS);
+extern Datum pg_ddl_command_out(PG_FUNCTION_ARGS);
+extern Datum pg_ddl_command_recv(PG_FUNCTION_ARGS);
+extern Datum pg_ddl_command_send(PG_FUNCTION_ARGS);
 
 /* regexp.c */
 extern Datum nameregexeq(PG_FUNCTION_ARGS);
@@ -1231,6 +1235,7 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
 extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
index 730fa75a35e30ddfb0d1e9b7d3f9f67253bcb117..8213e235f7823418f02665780c4c43026c9b3716 100644 (file)
@@ -6,11 +6,12 @@ include $(top_builddir)/src/Makefile.global
 
 SUBDIRS = \
                  commit_ts \
-                 worker_spi \
                  dummy_seclabel \
+                 test_ddl_deparse \
+                 test_parser \
                  test_rls_hooks \
                  test_shm_mq \
-                 test_parser
+                 worker_spi
 
 all: submake-errcodes
 
diff --git a/src/test/modules/test_ddl_deparse/Makefile b/src/test/modules/test_ddl_deparse/Makefile
new file mode 100644 (file)
index 0000000..a87b691
--- /dev/null
@@ -0,0 +1,19 @@
+MODULES = test_ddl_deparse
+PGFILEDESC = "test_ddl_deparse - regression testing for DDL deparsing"
+
+EXTENSION = test_ddl_deparse
+DATA = test_ddl_deparse--1.0.sql
+
+REGRESS = --schedule=$(srcdir)/regress_schedule
+EXTRA_INSTALL = contrib/pg_stat_statements
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_ddl_deparse
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_extension.out b/src/test/modules/test_ddl_deparse/expected/alter_extension.out
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_function.out b/src/test/modules/test_ddl_deparse/expected/alter_function.out
new file mode 100644 (file)
index 0000000..3694f96
--- /dev/null
@@ -0,0 +1,15 @@
+--
+-- ALTER_FUNCTION
+--
+ALTER FUNCTION plpgsql_function_trigger_1 ()
+   SET SCHEMA foo;
+NOTICE:  DDL test: type simple, tag ALTER FUNCTION
+ALTER FUNCTION foo.plpgsql_function_trigger_1()
+  COST 10;
+NOTICE:  DDL test: type simple, tag ALTER FUNCTION
+CREATE ROLE tmprole;
+ALTER FUNCTION plpgsql_function_trigger_2()
+  OWNER TO tmprole;
+ERROR:  function plpgsql_function_trigger_2() does not exist
+DROP OWNED BY tmprole;
+DROP ROLE tmprole;
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_sequence.out b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out
new file mode 100644 (file)
index 0000000..319f36f
--- /dev/null
@@ -0,0 +1,15 @@
+--
+-- ALTER_SEQUENCE
+--
+ALTER SEQUENCE fkey_table_seq
+  MINVALUE 10
+  START 20
+  CACHE 1
+  NO CYCLE;
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+ALTER SEQUENCE fkey_table_seq
+  RENAME TO fkey_table_seq_renamed;
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+ALTER SEQUENCE fkey_table_seq_renamed
+  SET SCHEMA foo;
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out
new file mode 100644 (file)
index 0000000..e304787
--- /dev/null
@@ -0,0 +1,18 @@
+CREATE TABLE parent (
+       a int
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE child () INHERITS (parent);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE grandchild () INHERITS (child);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+ALTER TABLE parent ADD COLUMN b serial;
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type alter table, tag ALTER TABLE
+NOTICE:    subcommand: ADD COLUMN (and recurse)
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+ALTER TABLE parent RENAME COLUMN b TO c;
+NOTICE:  DDL test: type simple, tag ALTER TABLE
+ALTER TABLE parent ADD CONSTRAINT a_pos CHECK (a > 0);
+NOTICE:  DDL test: type alter table, tag ALTER TABLE
+NOTICE:    subcommand: ADD CONSTRAINT (and recurse)
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out
new file mode 100644 (file)
index 0000000..74107c2
--- /dev/null
@@ -0,0 +1,7 @@
+---
+--- ALTER_TYPE_ENUM
+---
+ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz';
+NOTICE:  DDL test: type simple, tag ALTER TYPE
+ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo';
+NOTICE:  DDL test: type simple, tag ALTER TYPE
diff --git a/src/test/modules/test_ddl_deparse/expected/comment_on.out b/src/test/modules/test_ddl_deparse/expected/comment_on.out
new file mode 100644 (file)
index 0000000..4ebc89d
--- /dev/null
@@ -0,0 +1,25 @@
+--
+-- COMMENT_ON
+--
+COMMENT ON SCHEMA foo IS 'This is schema foo';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON TYPE enum_test IS 'ENUM test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON TYPE int2range  IS 'RANGE test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON VIEW datatype_view IS 'This is a view';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test';
+ERROR:  function c_function_test() does not exist
+COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test';
+NOTICE:  DDL test: type simple, tag COMMENT
+COMMENT ON RULE rule_1 IS 'RULE test';
+NOTICE:  DDL test: type simple, tag COMMENT
+-- should not fire
+COMMENT ON DATABASE contrib_regression IS 'contrib regression';
diff --git a/src/test/modules/test_ddl_deparse/expected/create_conversion.out b/src/test/modules/test_ddl_deparse/expected/create_conversion.out
new file mode 100644 (file)
index 0000000..e8697cf
--- /dev/null
@@ -0,0 +1,6 @@
+---
+--- CREATE_CONVERSION
+---
+-- Simple test should suffice for this
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
+NOTICE:  DDL test: type simple, tag CREATE CONVERSION
diff --git a/src/test/modules/test_ddl_deparse/expected/create_domain.out b/src/test/modules/test_ddl_deparse/expected/create_domain.out
new file mode 100644 (file)
index 0000000..2e7f585
--- /dev/null
@@ -0,0 +1,11 @@
+---
+--- CREATE_DOMAIN
+---
+CREATE DOMAIN domainvarchar VARCHAR(5);
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
+CREATE DOMAIN japanese_postal_code AS TEXT
+CHECK(
+   VALUE ~ '^\d{3}$'
+OR VALUE ~ '^\d{3}-\d{4}$'
+);
+NOTICE:  DDL test: type simple, tag CREATE DOMAIN
diff --git a/src/test/modules/test_ddl_deparse/expected/create_extension.out b/src/test/modules/test_ddl_deparse/expected/create_extension.out
new file mode 100644 (file)
index 0000000..4042e02
--- /dev/null
@@ -0,0 +1,5 @@
+---
+--- CREATE_EXTENSION
+---
+CREATE EXTENSION pg_stat_statements;
+NOTICE:  DDL test: type simple, tag CREATE EXTENSION
diff --git a/src/test/modules/test_ddl_deparse/expected/create_function.out b/src/test/modules/test_ddl_deparse/expected/create_function.out
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/create_operator.out b/src/test/modules/test_ddl_deparse/expected/create_operator.out
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/create_rule.out b/src/test/modules/test_ddl_deparse/expected/create_rule.out
new file mode 100644 (file)
index 0000000..fe3d047
--- /dev/null
@@ -0,0 +1,30 @@
+---
+--- CREATE_RULE
+---
+CREATE RULE rule_1 AS
+  ON INSERT
+  TO datatype_table
+  DO NOTHING;
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_2 AS
+  ON UPDATE
+  TO datatype_table
+  DO INSERT INTO unlogged_table (id) VALUES(NEW.id);
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO datatype_table
+  DO ALSO NOTHING;
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE "_RETURN" AS
+  ON SELECT
+  TO like_datatype_table
+  DO INSTEAD
+    SELECT * FROM datatype_view;
+NOTICE:  DDL test: type simple, tag CREATE RULE
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO like_datatype_table
+  WHERE id < 100
+  DO ALSO NOTHING;
+NOTICE:  DDL test: type simple, tag CREATE RULE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_schema.out b/src/test/modules/test_ddl_deparse/expected/create_schema.out
new file mode 100644 (file)
index 0000000..8ab4eb0
--- /dev/null
@@ -0,0 +1,19 @@
+--
+-- CREATE_SCHEMA
+--
+CREATE SCHEMA foo;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+CREATE SCHEMA IF NOT EXISTS bar;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+CREATE SCHEMA baz;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+-- Will not be created, and will not be handled by the
+-- event trigger
+CREATE SCHEMA IF NOT EXISTS baz;
+NOTICE:  schema "baz" already exists, skipping
+CREATE SCHEMA element_test
+  CREATE TABLE foo (id int)
+  CREATE VIEW bar AS SELECT * FROM foo;
+NOTICE:  DDL test: type simple, tag CREATE SCHEMA
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
new file mode 100644 (file)
index 0000000..5837ea4
--- /dev/null
@@ -0,0 +1,11 @@
+--
+-- CREATE_SEQUENCE
+--
+CREATE SEQUENCE fkey_table_seq
+  INCREMENT BY 1
+  MINVALUE 0
+  MAXVALUE 1000000
+  START 10
+  CACHE 10
+  CYCLE;
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/src/test/modules/test_ddl_deparse/expected/create_table.out
new file mode 100644 (file)
index 0000000..d27a775
--- /dev/null
@@ -0,0 +1,160 @@
+--
+-- CREATE_TABLE
+--
+-- Datatypes
+CREATE TABLE datatype_table (
+    id             SERIAL,
+    id_big         BIGSERIAL,
+    is_small       SMALLSERIAL,
+    v_bytea        BYTEA,
+    v_smallint     SMALLINT,
+    v_int          INT,
+    v_bigint       BIGINT,
+    v_char         CHAR(1),
+    v_varchar      VARCHAR(10),
+    v_text         TEXT,
+    v_bool         BOOLEAN,
+    v_inet         INET,
+    v_cidr         CIDR,
+    v_macaddr      MACADDR,
+    v_numeric      NUMERIC(1,0),
+    v_real         REAL,
+    v_float        FLOAT(1),
+    v_float8       FLOAT8,
+    v_money        MONEY,
+    v_tsquery      TSQUERY,
+    v_tsvector     TSVECTOR,
+    v_date         DATE,
+    v_time         TIME,
+    v_time_tz      TIME WITH TIME ZONE,
+    v_timestamp    TIMESTAMP,
+    v_timestamp_tz TIMESTAMP WITH TIME ZONE,
+    v_interval     INTERVAL,
+    v_bit          BIT,
+    v_bit4         BIT(4),
+    v_varbit       VARBIT,
+    v_varbit4      VARBIT(4),
+    v_box          BOX,
+    v_circle       CIRCLE,
+    v_lseg         LSEG,
+    v_path         PATH,
+    v_point        POINT,
+    v_polygon      POLYGON,
+    v_json         JSON,
+    v_xml          XML,
+    v_uuid         UUID,
+    v_txid_snapshot txid_snapshot,
+    v_enum         ENUM_TEST,
+    v_postal_code  japanese_postal_code,
+    v_int2range    int2range,
+    PRIMARY KEY (id),
+    UNIQUE (id_big)
+);
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type simple, tag CREATE SEQUENCE
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+NOTICE:  DDL test: type simple, tag ALTER SEQUENCE
+-- Constraint definitions
+CREATE TABLE IF NOT EXISTS fkey_table (
+    id           INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS),
+    datatype_id  INT NOT NULL REFERENCES datatype_table(id),
+    big_id       BIGINT NOT NULL,
+    sometext     TEXT COLLATE "POSIX",
+    check_col_1  INT NOT NULL CHECK(check_col_1 < 10),
+    check_col_2  INT NOT NULL,
+    PRIMARY KEY  (id),
+    CONSTRAINT fkey_big_id
+      FOREIGN KEY (big_id)
+      REFERENCES datatype_table(id_big),
+    EXCLUDE USING btree (check_col_2 WITH =)
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type alter table, tag ALTER TABLE
+NOTICE:    subcommand: ADD CONSTRAINT (and recurse)
+NOTICE:    subcommand: ADD CONSTRAINT (and recurse)
+-- Typed table
+CREATE TABLE employees OF employee_type (
+    PRIMARY KEY (name),
+    salary WITH OPTIONS DEFAULT 1000
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+-- Inheritance
+CREATE TABLE person (
+    id          INT NOT NULL PRIMARY KEY,
+       name            text,
+       age                     int4,
+       location        point
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TABLE emp (
+       salary          int4,
+       manager         name
+) INHERITS (person) WITH OIDS;
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE student (
+       gpa             float8
+) INHERITS (person);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE stud_emp (
+       percent         int4
+) INHERITS (emp, student);
+NOTICE:  merging multiple inherited definitions of column "id"
+NOTICE:  merging multiple inherited definitions of column "name"
+NOTICE:  merging multiple inherited definitions of column "age"
+NOTICE:  merging multiple inherited definitions of column "location"
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+-- Storage parameters
+CREATE TABLE storage (
+    id INT
+) WITH (
+    fillfactor = 10,
+    autovacuum_enabled = FALSE
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+-- LIKE
+CREATE TABLE like_datatype_table (
+  LIKE datatype_table
+  EXCLUDING ALL
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+CREATE TABLE like_fkey_table (
+  LIKE fkey_table
+  INCLUDING DEFAULTS
+  INCLUDING INDEXES
+  INCLUDING STORAGE
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+-- Volatile table types
+CREATE UNLOGGED TABLE unlogged_table (
+    id INT PRIMARY KEY
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table (
+    id INT PRIMARY KEY
+);
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table_commit_delete (
+    id INT PRIMARY KEY
+)
+ON COMMIT DELETE ROWS;
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
+CREATE TEMP TABLE temp_table_commit_drop (
+    id INT PRIMARY KEY
+)
+ON COMMIT DROP;
+NOTICE:  DDL test: type simple, tag CREATE TABLE
+NOTICE:  DDL test: type simple, tag CREATE INDEX
diff --git a/src/test/modules/test_ddl_deparse/expected/create_trigger.out b/src/test/modules/test_ddl_deparse/expected/create_trigger.out
new file mode 100644 (file)
index 0000000..c89c847
--- /dev/null
@@ -0,0 +1,18 @@
+---
+--- CREATE_TRIGGER
+---
+CREATE FUNCTION plpgsql_function_trigger_1()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  RETURN NEW;
+END;
+$$;
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+CREATE TRIGGER trigger_1
+  BEFORE INSERT OR UPDATE
+  ON datatype_table
+  FOR EACH ROW
+  EXECUTE PROCEDURE plpgsql_function_trigger_1();
+NOTICE:  DDL test: type simple, tag CREATE TRIGGER
diff --git a/src/test/modules/test_ddl_deparse/expected/create_type.out b/src/test/modules/test_ddl_deparse/expected/create_type.out
new file mode 100644 (file)
index 0000000..dadbc8f
--- /dev/null
@@ -0,0 +1,24 @@
+---
+--- CREATE_TYPE
+---
+CREATE FUNCTION text_w_default_in(cstring)
+   RETURNS text_w_default
+   AS 'textin'
+   LANGUAGE internal STABLE STRICT;
+NOTICE:  type "text_w_default" is not yet defined
+DETAIL:  Creating a shell type definition.
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+CREATE FUNCTION text_w_default_out(text_w_default)
+   RETURNS cstring
+   AS 'textout'
+   LANGUAGE internal STABLE STRICT ;
+NOTICE:  argument type text_w_default is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+CREATE TYPE employee_type AS (name TEXT, salary NUMERIC);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz');
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+CREATE TYPE int2range AS RANGE (
+  SUBTYPE = int2
+);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_view.out b/src/test/modules/test_ddl_deparse/expected/create_view.out
new file mode 100644 (file)
index 0000000..2ae4e2d
--- /dev/null
@@ -0,0 +1,19 @@
+--
+-- CREATE_VIEW
+--
+CREATE VIEW static_view AS
+  SELECT 'foo'::TEXT AS col;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
+CREATE OR REPLACE VIEW static_view AS
+  SELECT 'bar'::TEXT AS col;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
+NOTICE:  DDL test: type alter table, tag CREATE VIEW
+NOTICE:    subcommand: REPLACE RELOPTIONS
+CREATE VIEW datatype_view AS
+  SELECT * FROM datatype_table;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
+CREATE RECURSIVE VIEW nums_1_100 (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums_1_100 WHERE n < 100;
+NOTICE:  DDL test: type simple, tag CREATE VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/defprivs.out b/src/test/modules/test_ddl_deparse/expected/defprivs.out
new file mode 100644 (file)
index 0000000..66b2680
--- /dev/null
@@ -0,0 +1,6 @@
+--
+-- ALTER DEFAULT PRIVILEGES
+--
+ALTER DEFAULT PRIVILEGES IN SCHEMA public
+  REVOKE ALL PRIVILEGES ON TABLES FROM public;
+NOTICE:  DDL test: type alter default privileges, tag ALTER DEFAULT PRIVILEGES
diff --git a/src/test/modules/test_ddl_deparse/expected/matviews.out b/src/test/modules/test_ddl_deparse/expected/matviews.out
new file mode 100644 (file)
index 0000000..b946ff0
--- /dev/null
@@ -0,0 +1,8 @@
+--
+-- Materialized views
+--
+CREATE MATERIALIZED VIEW pg_class_mv AS
+  SELECT * FROM pg_class LIMIT 1 WITH NO DATA;
+NOTICE:  DDL test: type simple, tag CREATE MATERIALIZED VIEW
+REFRESH MATERIALIZED VIEW pg_class_mv;
+NOTICE:  DDL test: type simple, tag REFRESH MATERIALIZED VIEW
diff --git a/src/test/modules/test_ddl_deparse/expected/opfamily.out b/src/test/modules/test_ddl_deparse/expected/opfamily.out
new file mode 100644 (file)
index 0000000..14bd603
--- /dev/null
@@ -0,0 +1,67 @@
+-- copied from equivclass.sql
+create type int8alias1;
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create function int8alias1in(cstring) returns int8alias1
+  strict immutable language internal as 'int8in';
+NOTICE:  return type int8alias1 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create function int8alias1out(int8alias1) returns cstring
+  strict immutable language internal as 'int8out';
+NOTICE:  argument type int8alias1 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create type int8alias1 (
+    input = int8alias1in,
+    output = int8alias1out,
+    like = int8
+);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create type int8alias2;
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create function int8alias2in(cstring) returns int8alias2
+  strict immutable language internal as 'int8in';
+NOTICE:  return type int8alias2 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create function int8alias2out(int8alias2) returns cstring
+  strict immutable language internal as 'int8out';
+NOTICE:  argument type int8alias2 is only a shell
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create type int8alias2 (
+    input = int8alias2in,
+    output = int8alias2out,
+    like = int8
+);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create cast (int8 as int8alias1) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create cast (int8 as int8alias2) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create cast (int8alias1 as int8) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create cast (int8alias2 as int8) without function;
+NOTICE:  DDL test: type simple, tag CREATE CAST
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+  strict immutable language internal as 'int8eq';
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create operator = (
+    procedure = int8alias1eq,
+    leftarg = int8alias1, rightarg = int8alias1,
+    commutator = =,
+    restrict = eqsel, join = eqjoinsel,
+    merges
+);
+NOTICE:  DDL test: type simple, tag CREATE OPERATOR
+alter operator family integer_ops using btree add
+  operator 3 = (int8alias1, int8alias1);
+NOTICE:  DDL test: type alter operator family, tag ALTER OPERATOR FAMILY
+-- copied from alter_table.sql
+create type ctype as (f1 int, f2 text);
+NOTICE:  DDL test: type simple, tag CREATE TYPE
+create function same(ctype, ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+NOTICE:  DDL test: type simple, tag CREATE FUNCTION
+create operator =(procedure = same, leftarg  = ctype, rightarg = ctype);
+NOTICE:  DDL test: type simple, tag CREATE OPERATOR
+create operator class ctype_hash_ops
+  default for type ctype using hash as
+  operator 1 =(ctype, ctype);
+NOTICE:  DDL test: type create operator class, tag CREATE OPERATOR CLASS
diff --git a/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
new file mode 100644 (file)
index 0000000..e2e49f9
--- /dev/null
@@ -0,0 +1,40 @@
+CREATE EXTENSION test_ddl_deparse;
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+  RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+       r record;
+       r2 record;
+       cmdtype text;
+       objtype text;
+       tag text;
+BEGIN
+       FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+       LOOP
+               -- verify that tags match
+               tag = get_command_tag(r.command);
+               IF tag <> r.command_tag THEN
+                       RAISE NOTICE 'tag % doesn''t match %', tag, r.command_tag;
+               END IF;
+
+               -- log the operation
+               cmdtype = get_command_type(r.command);
+               IF cmdtype <> 'grant' THEN
+                       RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag;
+               ELSE
+                       RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type;
+               END IF;
+
+               -- if alter table, log more
+               IF cmdtype = 'alter table' THEN
+                       FOR r2 IN SELECT *
+                                               FROM unnest(get_altertable_subcmdtypes(r.command))
+                       LOOP
+                               RAISE NOTICE '  subcommand: %', r2.unnest;
+                       END LOOP;
+               END IF;
+       END LOOP;
+END;
+$$;
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
diff --git a/src/test/modules/test_ddl_deparse/regress_schedule b/src/test/modules/test_ddl_deparse/regress_schedule
new file mode 100644 (file)
index 0000000..1819ae5
--- /dev/null
@@ -0,0 +1,21 @@
+# must be first
+test: test_ddl_deparse
+
+test: create_extension
+test: create_schema
+test: create_type
+test: create_conversion
+test: create_domain
+test: create_sequence_1
+test: create_table
+test: alter_table
+test: create_view
+test: create_trigger
+test: create_rule
+test: comment_on
+test: alter_function
+test: alter_sequence
+test: alter_type_enum
+test: opfamily
+test: defprivs
+test: matviews
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_function.sql b/src/test/modules/test_ddl_deparse/sql/alter_function.sql
new file mode 100644 (file)
index 0000000..8f13950
--- /dev/null
@@ -0,0 +1,17 @@
+--
+-- ALTER_FUNCTION
+--
+
+ALTER FUNCTION plpgsql_function_trigger_1 ()
+   SET SCHEMA foo;
+
+ALTER FUNCTION foo.plpgsql_function_trigger_1()
+  COST 10;
+
+CREATE ROLE tmprole;
+
+ALTER FUNCTION plpgsql_function_trigger_2()
+  OWNER TO tmprole;
+
+DROP OWNED BY tmprole;
+DROP ROLE tmprole;
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql
new file mode 100644 (file)
index 0000000..3f9fe36
--- /dev/null
@@ -0,0 +1,17 @@
+--
+-- ALTER_SEQUENCE
+--
+
+ALTER SEQUENCE fkey_table_seq
+  MINVALUE 10
+  START 20
+  CACHE 1
+  NO CYCLE;
+
+ALTER SEQUENCE fkey_table_seq
+  RENAME TO fkey_table_seq_renamed;
+
+ALTER SEQUENCE fkey_table_seq_renamed
+  SET SCHEMA foo;
+
+
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_table.sql b/src/test/modules/test_ddl_deparse/sql/alter_table.sql
new file mode 100644 (file)
index 0000000..6e2cca7
--- /dev/null
@@ -0,0 +1,13 @@
+CREATE TABLE parent (
+       a int
+);
+
+CREATE TABLE child () INHERITS (parent);
+
+CREATE TABLE grandchild () INHERITS (child);
+
+ALTER TABLE parent ADD COLUMN b serial;
+
+ALTER TABLE parent RENAME COLUMN b TO c;
+
+ALTER TABLE parent ADD CONSTRAINT a_pos CHECK (a > 0);
diff --git a/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql
new file mode 100644 (file)
index 0000000..8999b38
--- /dev/null
@@ -0,0 +1,6 @@
+---
+--- ALTER_TYPE_ENUM
+---
+
+ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz';
+ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo';
diff --git a/src/test/modules/test_ddl_deparse/sql/comment_on.sql b/src/test/modules/test_ddl_deparse/sql/comment_on.sql
new file mode 100644 (file)
index 0000000..69114c3
--- /dev/null
@@ -0,0 +1,17 @@
+--
+-- COMMENT_ON
+--
+
+COMMENT ON SCHEMA foo IS 'This is schema foo';
+COMMENT ON TYPE enum_test IS 'ENUM test';
+COMMENT ON TYPE int2range  IS 'RANGE test';
+COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test';
+COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test';
+COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes';
+COMMENT ON VIEW datatype_view IS 'This is a view';
+COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test';
+COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test';
+COMMENT ON RULE rule_1 IS 'RULE test';
+
+-- should not fire
+COMMENT ON DATABASE contrib_regression IS 'contrib regression';
diff --git a/src/test/modules/test_ddl_deparse/sql/create_conversion.sql b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql
new file mode 100644 (file)
index 0000000..813c66d
--- /dev/null
@@ -0,0 +1,6 @@
+---
+--- CREATE_CONVERSION
+---
+
+-- Simple test should suffice for this
+CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_domain.sql b/src/test/modules/test_ddl_deparse/sql/create_domain.sql
new file mode 100644 (file)
index 0000000..6ab5525
--- /dev/null
@@ -0,0 +1,10 @@
+---
+--- CREATE_DOMAIN
+---
+CREATE DOMAIN domainvarchar VARCHAR(5);
+
+CREATE DOMAIN japanese_postal_code AS TEXT
+CHECK(
+   VALUE ~ '^\d{3}$'
+OR VALUE ~ '^\d{3}-\d{4}$'
+);
diff --git a/src/test/modules/test_ddl_deparse/sql/create_extension.sql b/src/test/modules/test_ddl_deparse/sql/create_extension.sql
new file mode 100644 (file)
index 0000000..52437de
--- /dev/null
@@ -0,0 +1,6 @@
+---
+--- CREATE_EXTENSION
+---
+
+CREATE EXTENSION pg_stat_statements;
+
diff --git a/src/test/modules/test_ddl_deparse/sql/create_rule.sql b/src/test/modules/test_ddl_deparse/sql/create_rule.sql
new file mode 100644 (file)
index 0000000..60ac151
--- /dev/null
@@ -0,0 +1,31 @@
+---
+--- CREATE_RULE
+---
+
+
+CREATE RULE rule_1 AS
+  ON INSERT
+  TO datatype_table
+  DO NOTHING;
+
+CREATE RULE rule_2 AS
+  ON UPDATE
+  TO datatype_table
+  DO INSERT INTO unlogged_table (id) VALUES(NEW.id);
+
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO datatype_table
+  DO ALSO NOTHING;
+
+CREATE RULE "_RETURN" AS
+  ON SELECT
+  TO like_datatype_table
+  DO INSTEAD
+    SELECT * FROM datatype_view;
+
+CREATE RULE rule_3 AS
+  ON DELETE
+  TO like_datatype_table
+  WHERE id < 100
+  DO ALSO NOTHING;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_schema.sql b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
new file mode 100644 (file)
index 0000000..f314dc2
--- /dev/null
@@ -0,0 +1,17 @@
+--
+-- CREATE_SCHEMA
+--
+
+CREATE SCHEMA foo;
+
+CREATE SCHEMA IF NOT EXISTS bar;
+
+CREATE SCHEMA baz;
+
+-- Will not be created, and will not be handled by the
+-- event trigger
+CREATE SCHEMA IF NOT EXISTS baz;
+
+CREATE SCHEMA element_test
+  CREATE TABLE foo (id int)
+  CREATE VIEW bar AS SELECT * FROM foo;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql
new file mode 100644 (file)
index 0000000..d289940
--- /dev/null
@@ -0,0 +1,12 @@
+--
+-- CREATE_SEQUENCE
+--
+
+CREATE SEQUENCE fkey_table_seq
+  INCREMENT BY 1
+  MINVALUE 0
+  MAXVALUE 1000000
+  START 10
+  CACHE 10
+  CYCLE;
+
diff --git a/src/test/modules/test_ddl_deparse/sql/create_table.sql b/src/test/modules/test_ddl_deparse/sql/create_table.sql
new file mode 100644 (file)
index 0000000..5e78452
--- /dev/null
@@ -0,0 +1,142 @@
+--
+-- CREATE_TABLE
+--
+
+-- Datatypes
+CREATE TABLE datatype_table (
+    id             SERIAL,
+    id_big         BIGSERIAL,
+    is_small       SMALLSERIAL,
+    v_bytea        BYTEA,
+    v_smallint     SMALLINT,
+    v_int          INT,
+    v_bigint       BIGINT,
+    v_char         CHAR(1),
+    v_varchar      VARCHAR(10),
+    v_text         TEXT,
+    v_bool         BOOLEAN,
+    v_inet         INET,
+    v_cidr         CIDR,
+    v_macaddr      MACADDR,
+    v_numeric      NUMERIC(1,0),
+    v_real         REAL,
+    v_float        FLOAT(1),
+    v_float8       FLOAT8,
+    v_money        MONEY,
+    v_tsquery      TSQUERY,
+    v_tsvector     TSVECTOR,
+    v_date         DATE,
+    v_time         TIME,
+    v_time_tz      TIME WITH TIME ZONE,
+    v_timestamp    TIMESTAMP,
+    v_timestamp_tz TIMESTAMP WITH TIME ZONE,
+    v_interval     INTERVAL,
+    v_bit          BIT,
+    v_bit4         BIT(4),
+    v_varbit       VARBIT,
+    v_varbit4      VARBIT(4),
+    v_box          BOX,
+    v_circle       CIRCLE,
+    v_lseg         LSEG,
+    v_path         PATH,
+    v_point        POINT,
+    v_polygon      POLYGON,
+    v_json         JSON,
+    v_xml          XML,
+    v_uuid         UUID,
+    v_txid_snapshot txid_snapshot,
+    v_enum         ENUM_TEST,
+    v_postal_code  japanese_postal_code,
+    v_int2range    int2range,
+    PRIMARY KEY (id),
+    UNIQUE (id_big)
+);
+
+-- Constraint definitions
+
+CREATE TABLE IF NOT EXISTS fkey_table (
+    id           INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS),
+    datatype_id  INT NOT NULL REFERENCES datatype_table(id),
+    big_id       BIGINT NOT NULL,
+    sometext     TEXT COLLATE "POSIX",
+    check_col_1  INT NOT NULL CHECK(check_col_1 < 10),
+    check_col_2  INT NOT NULL,
+    PRIMARY KEY  (id),
+    CONSTRAINT fkey_big_id
+      FOREIGN KEY (big_id)
+      REFERENCES datatype_table(id_big),
+    EXCLUDE USING btree (check_col_2 WITH =)
+);
+
+-- Typed table
+
+CREATE TABLE employees OF employee_type (
+    PRIMARY KEY (name),
+    salary WITH OPTIONS DEFAULT 1000
+);
+
+-- Inheritance
+CREATE TABLE person (
+    id          INT NOT NULL PRIMARY KEY,
+       name            text,
+       age                     int4,
+       location        point
+);
+
+CREATE TABLE emp (
+       salary          int4,
+       manager         name
+) INHERITS (person) WITH OIDS;
+
+
+CREATE TABLE student (
+       gpa             float8
+) INHERITS (person);
+
+CREATE TABLE stud_emp (
+       percent         int4
+) INHERITS (emp, student);
+
+
+-- Storage parameters
+
+CREATE TABLE storage (
+    id INT
+) WITH (
+    fillfactor = 10,
+    autovacuum_enabled = FALSE
+);
+
+-- LIKE
+
+CREATE TABLE like_datatype_table (
+  LIKE datatype_table
+  EXCLUDING ALL
+);
+
+CREATE TABLE like_fkey_table (
+  LIKE fkey_table
+  INCLUDING DEFAULTS
+  INCLUDING INDEXES
+  INCLUDING STORAGE
+);
+
+
+-- Volatile table types
+CREATE UNLOGGED TABLE unlogged_table (
+    id INT PRIMARY KEY
+);
+
+CREATE TEMP TABLE temp_table (
+    id INT PRIMARY KEY
+);
+
+CREATE TEMP TABLE temp_table_commit_delete (
+    id INT PRIMARY KEY
+)
+ON COMMIT DELETE ROWS;
+
+CREATE TEMP TABLE temp_table_commit_drop (
+    id INT PRIMARY KEY
+)
+ON COMMIT DROP;
diff --git a/src/test/modules/test_ddl_deparse/sql/create_trigger.sql b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql
new file mode 100644 (file)
index 0000000..fc0aef7
--- /dev/null
@@ -0,0 +1,18 @@
+---
+--- CREATE_TRIGGER
+---
+
+CREATE FUNCTION plpgsql_function_trigger_1()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER trigger_1
+  BEFORE INSERT OR UPDATE
+  ON datatype_table
+  FOR EACH ROW
+  EXECUTE PROCEDURE plpgsql_function_trigger_1();
diff --git a/src/test/modules/test_ddl_deparse/sql/create_type.sql b/src/test/modules/test_ddl_deparse/sql/create_type.sql
new file mode 100644 (file)
index 0000000..a387cfd
--- /dev/null
@@ -0,0 +1,21 @@
+---
+--- CREATE_TYPE
+---
+
+CREATE FUNCTION text_w_default_in(cstring)
+   RETURNS text_w_default
+   AS 'textin'
+   LANGUAGE internal STABLE STRICT;
+
+CREATE FUNCTION text_w_default_out(text_w_default)
+   RETURNS cstring
+   AS 'textout'
+   LANGUAGE internal STABLE STRICT ;
+
+CREATE TYPE employee_type AS (name TEXT, salary NUMERIC);
+
+CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz');
+
+CREATE TYPE int2range AS RANGE (
+  SUBTYPE = int2
+);
diff --git a/src/test/modules/test_ddl_deparse/sql/create_view.sql b/src/test/modules/test_ddl_deparse/sql/create_view.sql
new file mode 100644 (file)
index 0000000..030b76f
--- /dev/null
@@ -0,0 +1,17 @@
+--
+-- CREATE_VIEW
+--
+
+CREATE VIEW static_view AS
+  SELECT 'foo'::TEXT AS col;
+
+CREATE OR REPLACE VIEW static_view AS
+  SELECT 'bar'::TEXT AS col;
+
+CREATE VIEW datatype_view AS
+  SELECT * FROM datatype_table;
+
+CREATE RECURSIVE VIEW nums_1_100 (n) AS
+    VALUES (1)
+UNION ALL
+    SELECT n+1 FROM nums_1_100 WHERE n < 100;
diff --git a/src/test/modules/test_ddl_deparse/sql/defprivs.sql b/src/test/modules/test_ddl_deparse/sql/defprivs.sql
new file mode 100644 (file)
index 0000000..a0fb4c2
--- /dev/null
@@ -0,0 +1,6 @@
+--
+-- ALTER DEFAULT PRIVILEGES
+--
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA public
+  REVOKE ALL PRIVILEGES ON TABLES FROM public;
diff --git a/src/test/modules/test_ddl_deparse/sql/matviews.sql b/src/test/modules/test_ddl_deparse/sql/matviews.sql
new file mode 100644 (file)
index 0000000..381c11e
--- /dev/null
@@ -0,0 +1,8 @@
+--
+-- Materialized views
+--
+
+CREATE MATERIALIZED VIEW pg_class_mv AS
+  SELECT * FROM pg_class LIMIT 1 WITH NO DATA;
+
+REFRESH MATERIALIZED VIEW pg_class_mv;
diff --git a/src/test/modules/test_ddl_deparse/sql/opfamily.sql b/src/test/modules/test_ddl_deparse/sql/opfamily.sql
new file mode 100644 (file)
index 0000000..988936b
--- /dev/null
@@ -0,0 +1,53 @@
+-- copied from equivclass.sql
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+  strict immutable language internal as 'int8in';
+create function int8alias1out(int8alias1) returns cstring
+  strict immutable language internal as 'int8out';
+create type int8alias1 (
+    input = int8alias1in,
+    output = int8alias1out,
+    like = int8
+);
+
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+  strict immutable language internal as 'int8in';
+create function int8alias2out(int8alias2) returns cstring
+  strict immutable language internal as 'int8out';
+create type int8alias2 (
+    input = int8alias2in,
+    output = int8alias2out,
+    like = int8
+);
+
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+  strict immutable language internal as 'int8eq';
+create operator = (
+    procedure = int8alias1eq,
+    leftarg = int8alias1, rightarg = int8alias1,
+    commutator = =,
+    restrict = eqsel, join = eqjoinsel,
+    merges
+);
+alter operator family integer_ops using btree add
+  operator 3 = (int8alias1, int8alias1);
+
+
+-- copied from alter_table.sql
+create type ctype as (f1 int, f2 text);
+
+create function same(ctype, ctype) returns boolean language sql
+as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2';
+
+create operator =(procedure = same, leftarg  = ctype, rightarg = ctype);
+
+create operator class ctype_hash_ops
+  default for type ctype using hash as
+  operator 1 =(ctype, ctype);
+
diff --git a/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
new file mode 100644 (file)
index 0000000..4d08aaa
--- /dev/null
@@ -0,0 +1,42 @@
+CREATE EXTENSION test_ddl_deparse;
+
+CREATE OR REPLACE FUNCTION test_ddl_deparse()
+  RETURNS event_trigger LANGUAGE plpgsql AS
+$$
+DECLARE
+       r record;
+       r2 record;
+       cmdtype text;
+       objtype text;
+       tag text;
+BEGIN
+       FOR r IN SELECT * FROM pg_event_trigger_ddl_commands()
+       LOOP
+               -- verify that tags match
+               tag = get_command_tag(r.command);
+               IF tag <> r.command_tag THEN
+                       RAISE NOTICE 'tag % doesn''t match %', tag, r.command_tag;
+               END IF;
+
+               -- log the operation
+               cmdtype = get_command_type(r.command);
+               IF cmdtype <> 'grant' THEN
+                       RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag;
+               ELSE
+                       RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type;
+               END IF;
+
+               -- if alter table, log more
+               IF cmdtype = 'alter table' THEN
+                       FOR r2 IN SELECT *
+                                               FROM unnest(get_altertable_subcmdtypes(r.command))
+                       LOOP
+                               RAISE NOTICE '  subcommand: %', r2.unnest;
+                       END LOOP;
+               END IF;
+       END LOOP;
+END;
+$$;
+
+CREATE EVENT TRIGGER test_ddl_deparse
+ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse();
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
new file mode 100644 (file)
index 0000000..093005a
--- /dev/null
@@ -0,0 +1,16 @@
+/* src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_ddl_deparse" to load this file. \quit
+
+CREATE FUNCTION get_command_type(pg_ddl_command)
+  RETURNS text IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_command_tag(pg_ddl_command)
+  RETURNS text IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_altertable_subcmdtypes(pg_ddl_command)
+  RETURNS text[] IMMUTABLE STRICT
+  AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
new file mode 100644 (file)
index 0000000..f9ba413
--- /dev/null
@@ -0,0 +1,267 @@
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(get_command_type);
+PG_FUNCTION_INFO_V1(get_command_tag);
+PG_FUNCTION_INFO_V1(get_altertable_subcmdtypes);
+
+Datum
+get_command_type(PG_FUNCTION_ARGS)
+{
+       CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+       const char *type;
+
+       switch (cmd->type)
+       {
+               case SCT_Simple:
+                       type = "simple";
+                       break;
+               case SCT_AlterTable:
+                       type = "alter table";
+                       break;
+               case SCT_Grant:
+                       type = "grant";
+                       break;
+               case SCT_AlterOpFamily:
+                       type = "alter operator family";
+                       break;
+               case SCT_AlterDefaultPrivileges:
+                       type = "alter default privileges";
+                       break;
+               case SCT_CreateOpClass:
+                       type = "create operator class";
+                       break;
+               case SCT_AlterTSConfig:
+                       type = "alter text search configuration";
+                       break;
+               default:
+                       type = "unknown command type";
+                       break;
+       }
+
+       PG_RETURN_TEXT_P(cstring_to_text(type));
+}
+
+Datum
+get_command_tag(PG_FUNCTION_ARGS)
+{
+       CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+
+       if (!cmd->parsetree)
+               PG_RETURN_NULL();
+
+       PG_RETURN_TEXT_P(cstring_to_text(CreateCommandTag(cmd->parsetree)));
+}
+
+Datum
+get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
+{
+       CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0);
+       ArrayBuildState *astate = NULL;
+       ListCell *cell;
+
+       if (cmd->type != SCT_AlterTable)
+               elog(ERROR, "command is not ALTER TABLE");
+
+       foreach(cell, cmd->d.alterTable.subcmds)
+       {
+               CollectedATSubcmd *sub = lfirst(cell);
+               AlterTableCmd  *subcmd = (AlterTableCmd *) sub->parsetree;
+               const char     *strtype;
+
+               Assert(IsA(subcmd, AlterTableCmd));
+
+               switch (subcmd->subtype)
+               {
+                       case AT_AddColumn:
+                               strtype = "ADD COLUMN";
+                               break;
+                       case AT_AddColumnRecurse:
+                               strtype = "ADD COLUMN (and recurse)";
+                               break;
+                       case AT_AddColumnToView:
+                               strtype = "ADD COLUMN TO VIEW";
+                               break;
+                       case AT_ColumnDefault:
+                               strtype = "ALTER COLUMN SET DEFAULT";
+                               break;
+                       case AT_DropNotNull:
+                               strtype = "DROP NOT NULL";
+                               break;
+                       case AT_SetNotNull:
+                               strtype = "SET NOT NULL";
+                               break;
+                       case AT_SetStatistics:
+                               strtype = "SET STATS";
+                               break;
+                       case AT_SetOptions:
+                               strtype = "SET OPTIONS";
+                               break;
+                       case AT_ResetOptions:
+                               strtype = "RESET OPTIONS";
+                               break;
+                       case AT_SetStorage:
+                               strtype = "SET STORAGE";
+                               break;
+                       case AT_DropColumn:
+                               strtype = "DROP COLUMN";
+                               break;
+                       case AT_DropColumnRecurse:
+                               strtype = "DROP COLUMN (and recurse)";
+                               break;
+                       case AT_AddIndex:
+                               strtype = "ADD INDEX";
+                               break;
+                       case AT_ReAddIndex:
+                               strtype = "(re) ADD INDEX";
+                               break;
+                       case AT_AddConstraint:
+                               strtype = "ADD CONSTRAINT";
+                               break;
+                       case AT_AddConstraintRecurse:
+                               strtype = "ADD CONSTRAINT (and recurse)";
+                               break;
+                       case AT_ReAddConstraint:
+                               strtype = "(re) ADD CONSTRAINT";
+                               break;
+                       case AT_AlterConstraint:
+                               strtype = "ALTER CONSTRAINT";
+                               break;
+                       case AT_ValidateConstraint:
+                               strtype = "VALIDATE CONSTRAINT";
+                               break;
+                       case AT_ValidateConstraintRecurse:
+                               strtype = "VALIDATE CONSTRAINT (and recurse)";
+                               break;
+                       case AT_ProcessedConstraint:
+                               strtype = "ADD (processed) CONSTRAINT";
+                               break;
+                       case AT_AddIndexConstraint:
+                               strtype = "ADD CONSTRAINT (using index)";
+                               break;
+                       case AT_DropConstraint:
+                               strtype = "DROP CONSTRAINT";
+                               break;
+                       case AT_DropConstraintRecurse:
+                               strtype = "DROP CONSTRAINT (and recurse)";
+                               break;
+                       case AT_AlterColumnType:
+                               strtype = "ALTER COLUMN SET TYPE";
+                               break;
+                       case AT_AlterColumnGenericOptions:
+                               strtype = "ALTER COLUMN SET OPTIONS";
+                               break;
+                       case AT_ChangeOwner:
+                               strtype = "CHANGE OWNER";
+                               break;
+                       case AT_ClusterOn:
+                               strtype = "CLUSTER";
+                               break;
+                       case AT_DropCluster:
+                               strtype = "DROP CLUSTER";
+                               break;
+                       case AT_SetLogged:
+                               strtype = "SET LOGGED";
+                               break;
+                       case AT_SetUnLogged:
+                               strtype = "SET UNLOGGED";
+                               break;
+                       case AT_AddOids:
+                               strtype = "ADD OIDS";
+                               break;
+                       case AT_AddOidsRecurse:
+                               strtype = "ADD OIDS (and recurse)";
+                               break;
+                       case AT_DropOids:
+                               strtype = "DROP OIDS";
+                               break;
+                       case AT_SetTableSpace:
+                               strtype = "SET TABLESPACE";
+                               break;
+                       case AT_SetRelOptions:
+                               strtype = "SET RELOPTIONS";
+                               break;
+                       case AT_ResetRelOptions:
+                               strtype = "RESET RELOPTIONS";
+                               break;
+                       case AT_ReplaceRelOptions:
+                               strtype = "REPLACE RELOPTIONS";
+                               break;
+                       case AT_EnableTrig:
+                               strtype = "ENABLE TRIGGER";
+                               break;
+                       case AT_EnableAlwaysTrig:
+                               strtype = "ENABLE TRIGGER (always)";
+                               break;
+                       case AT_EnableReplicaTrig:
+                               strtype = "ENABLE TRIGGER (replica)";
+                               break;
+                       case AT_DisableTrig:
+                               strtype = "DISABLE TRIGGER";
+                               break;
+                       case AT_EnableTrigAll:
+                               strtype = "ENABLE TRIGGER (all)";
+                               break;
+                       case AT_DisableTrigAll:
+                               strtype = "DISABLE TRIGGER (all)";
+                               break;
+                       case AT_EnableTrigUser:
+                               strtype = "ENABLE TRIGGER (user)";
+                               break;
+                       case AT_DisableTrigUser:
+                               strtype = "DISABLE TRIGGER (user)";
+                               break;
+                       case AT_EnableRule:
+                               strtype = "ENABLE RULE";
+                               break;
+                       case AT_EnableAlwaysRule:
+                               strtype = "ENABLE RULE (always)";
+                               break;
+                       case AT_EnableReplicaRule:
+                               strtype = "ENABLE RULE (replica)";
+                               break;
+                       case AT_DisableRule:
+                               strtype = "DISABLE RULE";
+                               break;
+                       case AT_AddInherit:
+                               strtype = "ADD INHERIT";
+                               break;
+                       case AT_DropInherit:
+                               strtype = "DROP INHERIT";
+                               break;
+                       case AT_AddOf:
+                               strtype = "OF";
+                               break;
+                       case AT_DropOf:
+                               strtype = "NOT OF";
+                               break;
+                       case AT_ReplicaIdentity:
+                               strtype = "REPLICA IDENTITY";
+                               break;
+                       case AT_EnableRowSecurity:
+                               strtype = "ENABLE ROW SECURITY";
+                               break;
+                       case AT_DisableRowSecurity:
+                               strtype = "DISABLE ROW SECURITY";
+                               break;
+                       case AT_GenericOptions:
+                               strtype = "SET OPTIONS";
+                               break;
+               }
+
+               astate =
+                       accumArrayResult(astate, CStringGetTextDatum(strtype),
+                                                        false, TEXTOID, CurrentMemoryContext);
+       }
+
+       if (astate == NULL)
+               elog(ERROR, "empty alter table subcommand list");
+
+       PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
+}
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.control b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
new file mode 100644 (file)
index 0000000..09112ee
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test code for DDL deparse feature'
+default_version = '1.0'
+module_pathname = '$libdir/test_ddl_deparse'
+relocatable = true