Alvaro Herrera wrote:
> Alvaro Herrera wrote:
> > Here's a cleaned up version of this patch; I threw together a very quick
> > test module, and updated a conflicting OID.  As far as I can tell, I'm
> > only missing the documentation updates before this is push-able.
> 
> Here is a complete version.  Barring serious problems, I intend to
> commit this on Monday.


-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fb39731..04762e8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18270,6 +18270,97 @@ CREATE EVENT TRIGGER test_table_rewrite_oid
 </programlisting>
     </para>
   </sect2>
+
+  <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> (data definition language) 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>
   </sect1>
 
 </chapter>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c27..943909c 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 0110b06..daf8ddd 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -20,21 +20,28 @@
 #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"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/jsonb.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -44,6 +51,9 @@
 
 typedef struct EventTriggerQueryState
 {
+	/* memory context for this state's objects */
+	MemoryContext cxt;
+
 	/* sql_drop */
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
@@ -52,7 +62,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 +84,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 +153,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 +1222,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 +1240,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 +1282,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 +1590,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 c327cc0..3375f10 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 c090ed2..5a7beff 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 299d8cc..0a6b069 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 4c404e7..ff90040 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 a3139d3..937a8c5 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 7c86e91..e032142 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 7a4c073..e71d926 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 59f09dc..78bfd34 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 2f0f0a1..9b674ce 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)
 {
@@ -323,7 +326,6 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
-
 /*
  * Add typmod decoration to the basic type name
  */
@@ -338,7 +340,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +352,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h
new file mode 100644
index 0000000..f01dcbe
--- /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 bd67d72..9cb6b1e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5138,6 +5138,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,88}" "{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 0a900dd..b340f06 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -364,6 +364,11 @@ 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 = 88 ( pg_ddl_command PGNSP PGUID -1 f b P f \054 0 0 0 pg_ddl_command_in pg_ddl_command_out pg_ddl_command_recv pg_ddl_command_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ )); */
+DATA(insert OID = 88 ( pg_ddl_command   PGNSP PGUID SIZEOF_POINTER t b P f t \054 0 0 0 - - - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("internal type for passing CollectedCommand");
+#define PGDDLCOMMANDOID 88
+
 /* 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 335f09c..c3a1748 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 7eb2156..579e1ef 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 40ecea2..0423350 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 91ca9c6..556c1c5 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 0000000..b6bcbeb
--- /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 0000000..0855bf1
--- /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 e8104f0..254f66c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1220,6 +1220,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 730fa75..8213e23 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 0000000..f11153c
--- /dev/null
+++ b/src/test/modules/test_ddl_deparse/Makefile
@@ -0,0 +1,18 @@
+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
+
+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 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_function.out b/src/test/modules/test_ddl_deparse/expected/alter_function.out
new file mode 100644
index 0000000..3694f96
--- /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 0000000..319f36f
--- /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_type_enum.out b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out
new file mode 100644
index 0000000..74107c2
--- /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 0000000..4ebc89d
--- /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 0000000..e8697cf
--- /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 0000000..2e7f585
--- /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 0000000..4042e02
--- /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 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/create_operator.out b/src/test/modules/test_ddl_deparse/expected/create_operator.out
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/modules/test_ddl_deparse/expected/create_rule.out b/src/test/modules/test_ddl_deparse/expected/create_rule.out
new file mode 100644
index 0000000..fe3d047
--- /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 0000000..8ab4eb0
--- /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 0000000..5837ea4
--- /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 0000000..d27a775
--- /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 0000000..c89c847
--- /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 0000000..dadbc8f
--- /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 0000000..2ae4e2d
--- /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 0000000..66b2680
--- /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 0000000..b946ff0
--- /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 0000000..14bd603
--- /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 0000000..e2e49f9
--- /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/sql/alter_function.sql b/src/test/modules/test_ddl_deparse/sql/alter_function.sql
new file mode 100644
index 0000000..8f13950
--- /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 0000000..3f9fe36
--- /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_type_enum.sql b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql
new file mode 100644
index 0000000..8999b38
--- /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 0000000..69114c3
--- /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 0000000..813c66d
--- /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 0000000..6ab5525
--- /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 0000000..52437de
--- /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 0000000..60ac151
--- /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 0000000..f314dc2
--- /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 0000000..d289940
--- /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 0000000..5e78452
--- /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 0000000..fc0aef7
--- /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 0000000..a387cfd
--- /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 0000000..030b76f
--- /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 0000000..a0fb4c2
--- /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/opfamily.sql b/src/test/modules/test_ddl_deparse/sql/opfamily.sql
new file mode 100644
index 0000000..988936b
--- /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 0000000..4d08aaa
--- /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 0000000..093005a
--- /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 0000000..f9ba413
--- /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 0000000..09112ee
--- /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
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to