summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/event-trigger.sgml11
-rw-r--r--doc/src/sgml/func.sgml93
-rw-r--r--src/backend/catalog/aclchk.c37
-rw-r--r--src/backend/commands/event_trigger.c706
-rw-r--r--src/backend/commands/opclasscmds.c63
-rw-r--r--src/backend/commands/schemacmds.c12
-rw-r--r--src/backend/commands/tablecmds.c11
-rw-r--r--src/backend/commands/tsearchcmds.c5
-rw-r--r--src/backend/nodes/copyfuncs.c1
-rw-r--r--src/backend/nodes/equalfuncs.c1
-rw-r--r--src/backend/parser/gram.y6
-rw-r--r--src/backend/tcop/utility.c274
-rw-r--r--src/backend/utils/adt/format_type.c3
-rw-r--r--src/backend/utils/adt/pseudotypes.c65
-rw-r--r--src/include/catalog/catversion.h2
-rw-r--r--src/include/catalog/opfam_internal.h28
-rw-r--r--src/include/catalog/pg_proc.h11
-rw-r--r--src/include/catalog/pg_type.h4
-rw-r--r--src/include/commands/defrem.h1
-rw-r--r--src/include/commands/event_trigger.h26
-rw-r--r--src/include/commands/extension.h2
-rw-r--r--src/include/nodes/parsenodes.h10
-rw-r--r--src/include/tcop/deparse_utility.h105
-rw-r--r--src/include/utils/aclchk_internal.h45
-rw-r--r--src/include/utils/builtins.h5
-rw-r--r--src/test/modules/Makefile5
-rw-r--r--src/test/modules/test_ddl_deparse/Makefile19
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_extension.out0
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_function.out15
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_sequence.out15
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_table.out18
-rw-r--r--src/test/modules/test_ddl_deparse/expected/alter_type_enum.out7
-rw-r--r--src/test/modules/test_ddl_deparse/expected/comment_on.out25
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_conversion.out6
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_domain.out11
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_extension.out5
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_function.out0
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_operator.out0
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_rule.out30
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_schema.out19
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_sequence_1.out11
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_table.out160
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_trigger.out18
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_type.out24
-rw-r--r--src/test/modules/test_ddl_deparse/expected/create_view.out19
-rw-r--r--src/test/modules/test_ddl_deparse/expected/defprivs.out6
-rw-r--r--src/test/modules/test_ddl_deparse/expected/matviews.out8
-rw-r--r--src/test/modules/test_ddl_deparse/expected/opfamily.out67
-rw-r--r--src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out40
-rw-r--r--src/test/modules/test_ddl_deparse/regress_schedule21
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_function.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_sequence.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_table.sql13
-rw-r--r--src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql6
-rw-r--r--src/test/modules/test_ddl_deparse/sql/comment_on.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_conversion.sql6
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_domain.sql10
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_extension.sql6
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_rule.sql31
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_schema.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql12
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_table.sql142
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_trigger.sql18
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_type.sql21
-rw-r--r--src/test/modules/test_ddl_deparse/sql/create_view.sql17
-rw-r--r--src/test/modules/test_ddl_deparse/sql/defprivs.sql6
-rw-r--r--src/test/modules/test_ddl_deparse/sql/matviews.sql8
-rw-r--r--src/test/modules/test_ddl_deparse/sql/opfamily.sql53
-rw-r--r--src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql42
-rw-r--r--src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql16
-rw-r--r--src/test/modules/test_ddl_deparse/test_ddl_deparse.c267
-rw-r--r--src/test/modules/test_ddl_deparse/test_ddl_deparse.control4
72 files changed, 2667 insertions, 155 deletions
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index f151eb73754..0cb31a478fa 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -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>
@@ -52,7 +53,13 @@
<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>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fb397316048..1ee4f634d3a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c279205..943909c8225 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -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"
@@ -65,32 +67,6 @@
/*
- * 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.
*/
typedef struct
@@ -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);
}
/*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 0110b0603d3..7658c06d534 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,17 +20,22 @@
#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 */
+ }
+}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index c327cc0473e..3375f10861d 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -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"
@@ -47,24 +49,12 @@
#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;
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index c090ed220f8..5a7beff7d56 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -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);
@@ -143,6 +145,16 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
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
* references. Note that the result is still a list of raw parsetrees ---
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 299d8ccd81f..0a6b0690657 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -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;
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 4c404e73d09..ff900401935 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -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);
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ed8fa72621e..76b63afe897 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -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);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 7c86e919a49..e032142b509 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -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);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7a4c07365c1..e71d9262caa 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 59f09dc93af..78bfd349a77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -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);
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 2f0f0a1d895..a8519835cc6 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -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)
{
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 6e663c7a2bb..9ad460abfbd 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -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();
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 662ba27a414..80176dd287a 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -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
index 00000000000..f01dcbe3e31
--- /dev/null
+++ b/src/include/catalog/opfam_internal.h
@@ -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 */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 5fa65d63a87..41838c0a8d3 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -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_ ));
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 24933539aab..4284a704d3c 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -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_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 335f09cba4c..c3a1748e00f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -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);
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 7eb21560856..579e1ef8bd2 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -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 */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 40ecea2fee4..0423350c9aa 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -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;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 91ca9c6fd0e..556c1c5d9da 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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
index 00000000000..b6bcbeb3174
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -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
index 00000000000..0855bf1d0d2
--- /dev/null
+++ b/src/include/utils/aclchk_internal.h
@@ -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 */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index a90bfe29e9f..1140c17792b 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -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);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 730fa75a35e..8213e235f78 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -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
index 00000000000..a87b6912cb9
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/Makefile
@@ -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
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_extension.out
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
index 00000000000..3694f96723e
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_function.out
@@ -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
index 00000000000..319f36f9a04
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out
@@ -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
index 00000000000..e304787bc55
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out
@@ -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
index 00000000000..74107c2f495
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out
@@ -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
index 00000000000..4ebc89d611f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/comment_on.out
@@ -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
index 00000000000..e8697cf66d2
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_conversion.out
@@ -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
index 00000000000..2e7f5853f51
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_domain.out
@@ -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
index 00000000000..4042e026144
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_extension.out
@@ -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
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_function.out
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
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_operator.out
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
index 00000000000..fe3d047a411
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_rule.out
@@ -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
index 00000000000..8ab4eb03385
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out
@@ -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
index 00000000000..5837ea484e4
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out
@@ -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
index 00000000000..d27a7752570
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_table.out
@@ -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
index 00000000000..c89c8470b00
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_trigger.out
@@ -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
index 00000000000..dadbc8f7f03
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_type.out
@@ -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
index 00000000000..2ae4e2d225e
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/create_view.out
@@ -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
index 00000000000..66b2680fd8b
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/defprivs.out
@@ -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
index 00000000000..b946ff06d26
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/matviews.out
@@ -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
index 00000000000..14bd6037cdf
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/opfamily.out
@@ -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
index 00000000000..e2e49f9d7f0
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out
@@ -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
index 00000000000..1819ae5e0ec
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/regress_schedule
@@ -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
index 00000000000..8f13950902f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_function.sql
@@ -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
index 00000000000..3f9fe362f1f
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql
@@ -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
index 00000000000..6e2cca754e3
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_table.sql
@@ -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
index 00000000000..8999b38bfde
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql
@@ -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
index 00000000000..69114c32298
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/comment_on.sql
@@ -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
index 00000000000..813c66d6966
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql
@@ -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
index 00000000000..6ab55258703
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_domain.sql
@@ -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
index 00000000000..52437decb03
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_extension.sql
@@ -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
index 00000000000..60ac151a264
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_rule.sql
@@ -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
index 00000000000..f314dc2b840
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql
@@ -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
index 00000000000..d2899408fc3
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql
@@ -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
index 00000000000..5e784527297
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_table.sql
@@ -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
index 00000000000..fc0aef75935
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql
@@ -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
index 00000000000..a387cfd7929
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_type.sql
@@ -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
index 00000000000..030b76f86fa
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/create_view.sql
@@ -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
index 00000000000..a0fb4c2f026
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/defprivs.sql
@@ -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
index 00000000000..381c11e1f84
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/matviews.sql
@@ -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
index 00000000000..988936bed8c
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/opfamily.sql
@@ -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
index 00000000000..4d08aaa1c43
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql
@@ -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
index 00000000000..093005ad801
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql
@@ -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
index 00000000000..f9ba4132e7c
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -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
index 00000000000..09112ee53e5
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control
@@ -0,0 +1,4 @@
+comment = 'Test code for DDL deparse feature'
+default_version = '1.0'
+module_pathname = '$libdir/test_ddl_deparse'
+relocatable = true