>From 098f5acabd774004dc5d9c750d55e7c9afa60238 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 24 Sep 2014 15:53:04 -0300
Subject: [PATCH 05/27] deparse: infrastructure needed for command deparsing

---
 src/backend/catalog/objectaddress.c  | 115 +++++
 src/backend/commands/event_trigger.c | 941 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/Makefile            |   2 +-
 src/backend/tcop/deparse_utility.c   | 877 ++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c           |   2 +
 src/backend/utils/adt/format_type.c  | 113 ++++-
 src/include/catalog/objectaddress.h  |   2 +
 src/include/catalog/pg_proc.h        |   4 +
 src/include/commands/event_trigger.h |   3 +
 src/include/commands/extension.h     |   2 +-
 src/include/nodes/parsenodes.h       |   2 +
 src/include/tcop/deparse_utility.h   |  60 +++
 src/include/utils/builtins.h         |   5 +
 13 files changed, 2117 insertions(+), 11 deletions(-)
 create mode 100644 src/backend/tcop/deparse_utility.c
 create mode 100644 src/include/tcop/deparse_utility.h

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b69b75b..a2445f1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -723,6 +723,121 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 }
 
 /*
+ * Return the OID of the catalog corresponding to the given object type
+ */
+Oid
+get_objtype_catalog_oid(ObjectType objtype)
+{
+	Oid		catalog_id;
+
+	switch (objtype)
+	{
+		case OBJECT_INDEX:
+		case OBJECT_COMPOSITE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_TABLE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_COLUMN:
+			catalog_id = RelationRelationId;
+			break;
+		case OBJECT_RULE:
+			catalog_id = RewriteRelationId;
+			break;
+		case OBJECT_TRIGGER:
+			catalog_id = TriggerRelationId;
+			break;
+		case OBJECT_CONSTRAINT:
+			catalog_id = ConstraintRelationId;
+			break;
+		case OBJECT_DATABASE:
+			catalog_id = DatabaseRelationId;
+			break;
+		case OBJECT_EXTENSION:
+			catalog_id = ExtensionRelationId;
+			break;
+		case OBJECT_TABLESPACE:
+			catalog_id = TableSpaceRelationId;
+			break;
+		case OBJECT_ROLE:
+			catalog_id = AuthIdRelationId;
+			break;
+		case OBJECT_SCHEMA:
+			catalog_id = NamespaceRelationId;
+			break;
+		case OBJECT_LANGUAGE:
+			catalog_id = LanguageRelationId;
+			break;
+		case OBJECT_FDW:
+			catalog_id = ForeignDataWrapperRelationId;
+			break;
+		case OBJECT_FOREIGN_SERVER:
+			catalog_id = ForeignServerRelationId;
+			break;
+		case OBJECT_USER_MAPPING:
+			catalog_id = UserMappingRelationId;
+			break;
+		case OBJECT_EVENT_TRIGGER:
+			catalog_id = EventTriggerRelationId;
+			break;
+		case OBJECT_TYPE:
+		case OBJECT_DOMAIN:
+			catalog_id = TypeRelationId;
+			break;
+		case OBJECT_ATTRIBUTE:
+			catalog_id = TypeRelationId;	/* XXX? */
+			break;
+		case OBJECT_AGGREGATE:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_FUNCTION:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_OPERATOR:
+			catalog_id = OperatorRelationId;
+			break;
+		case OBJECT_COLLATION:
+			catalog_id = CollationRelationId;
+			break;
+		case OBJECT_CONVERSION:
+			catalog_id = ConversionRelationId;
+			break;
+		case OBJECT_OPCLASS:
+			catalog_id = OperatorClassRelationId;
+			break;
+		case OBJECT_OPFAMILY:
+			catalog_id = OperatorFamilyRelationId;
+			break;
+		case OBJECT_LARGEOBJECT:
+			catalog_id = LargeObjectRelationId;
+			break;
+		case OBJECT_CAST:
+			catalog_id = CastRelationId;
+			break;
+		case OBJECT_TSPARSER:
+			catalog_id = TSParserRelationId;
+			break;
+		case OBJECT_TSDICTIONARY:
+			catalog_id = TSDictionaryRelationId;
+			break;
+		case OBJECT_TSTEMPLATE:
+			catalog_id = TSTemplateRelationId;
+			break;
+		case OBJECT_TSCONFIGURATION:
+			catalog_id = TSConfigRelationId;
+			break;
+		default:
+				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+				/* placate compiler, in case it thinks elog might return */
+				catalog_id = InvalidOid;
+	}
+
+	/* Return the object address and the relation. */
+	return catalog_id;
+}
+
+/*
  * Find an ObjectAddress for a type of object that is identified by an
  * unqualified name.
  */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 7b55d8f..2dd7fff 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -25,16 +25,19 @@
 #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/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -48,6 +51,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -66,6 +70,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},
@@ -924,6 +929,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
 		case OBJECT_COLUMN:
+		case OBJECT_COMPOSITE:
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
@@ -951,6 +957,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
+		case OBJECT_USER_MAPPING:
 		case OBJECT_VIEW:
 			return true;
 	}
@@ -1028,13 +1035,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop events are the only reason to have event trigger
-	 * state at all; so if there are none, don't install one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1044,6 +1044,7 @@ EventTriggerBeginCompleteQuery(void)
 	state->cxt = cxt;
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1300,3 +1301,929 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
+ * EventTriggerStashCommand
+ * 		Save data about a simple DDL command that was just executed
+ */
+void
+EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype,
+						 Node *parsetree)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_Simple;
+	stashed->in_extension = creating_extension;
+
+	stashed->d.simple.objectId = objectId;
+	stashed->d.simple.objtype = objtype;
+	stashed->d.simple.objectSubId = objectSubId;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+Datum
+pg_event_trigger_get_creation_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_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						"pg_event_trigger_get_creation_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->stash)
+	{
+		StashedCommand *cmd = lfirst(lc);
+		char	   *command;
+
+		/*
+		 * For IF NOT EXISTS commands that attempt to create an existing
+		 * object, the returned OID is Invalid; in those cases, return an empty
+		 * command instead of trying to soldier on.
+		 *
+		 * XXX an 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.objectId))
+			continue;
+
+		command = deparse_utility_command(cmd);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{
+			Datum		values[9];
+			bool		nulls[9];
+			ObjectAddress addr;
+			int			i = 0;
+
+			MemSet(nulls, 0, sizeof(nulls));
+
+			if (cmd->type == SCT_Simple)
+			{
+				Oid			classId;
+				Oid			objId;
+				uint32		objSubId;
+				const char *tag;
+				char	   *identity;
+				char	   *type;
+				char	   *schema = NULL;
+
+				if (cmd->type == SCT_Simple)
+				{
+					classId = get_objtype_catalog_oid(cmd->d.simple.objtype);
+					objId = cmd->d.simple.objectId;
+					objSubId = cmd->d.simple.objectSubId;
+				}
+
+				tag = CreateCommandTag(cmd->parsetree);
+				addr.classId = classId;
+				addr.objectId = objId;
+				addr.objectSubId = objSubId;
+
+				type = getObjectTypeDescription(&addr);
+				identity = getObjectIdentity(&addr);
+
+				/*
+				 * Obtain schema name, if any ("pg_temp" if a temp object)
+				 */
+				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);
+						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(tag);
+				/* 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++] = CStringGetTextDatum(command);
+			}
+
+			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+		}
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
+
+/* ************************* JSON STUFF FROM HERE ************************* *
+ *	Code below is used to decode blobs returned by deparse_utility_command	*
+ *																			*/
+
+/*
+ * Note we only support types that are valid in command representation from
+ * deparse_utility_command.
+ */
+typedef enum
+{
+	JsonTypeArray,
+	JsonTypeObject,
+	JsonTypeString
+} JsonType;
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecStringLiteral,
+	SpecIdentifier
+} convSpecifier;
+
+/*
+ * Extract the named json field, which must be of type string, from the given
+ * JSON datum, which must be of type object.  If the field doesn't exist,
+ * NULL is returned.  Otherwise the string value is returned.
+ */
+static char *
+expand_get_strval(Datum json, char *field_name)
+{
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	char	   *value_str;
+
+	InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+	fcinfo.arg[0] = json;
+	fcinfo.argnull[0] = false;
+	fcinfo.arg[1] = CStringGetTextDatum(field_name);
+	fcinfo.argnull[1] = false;
+
+	result = (*json_object_field_text) (&fcinfo);
+
+	if (fcinfo.isnull)
+		return NULL;
+
+	value_str = TextDatumGetCString(result);
+
+	pfree(DatumGetPointer(result));
+
+	return value_str;
+}
+
+/*
+ * Extract the named json field, which must be of type boolean, from the given
+ * JSON datum, which must be of type object.  If the field doesn't exist,
+ * isnull is set to TRUE and the return value should not be consulted.
+ * Otherwise the boolean value is returned.
+ */
+static bool
+expand_get_boolval(Datum json, char *field_name, bool *isnull)
+{
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	char	   *value_str;
+
+	*isnull = false;
+
+	InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+	fcinfo.arg[0] = json;
+	fcinfo.argnull[0] = false;
+	fcinfo.arg[1] = CStringGetTextDatum(field_name);
+	fcinfo.argnull[1] = false;
+
+	result = (*json_object_field_text) (&fcinfo);
+
+	if (fcinfo.isnull)
+	{
+		*isnull = true;
+		return false;
+	}
+
+	value_str = TextDatumGetCString(result);
+
+	if (strcmp(value_str, "true") == 0)
+		return true;
+
+	Assert(strcmp(value_str, "false") == 0);
+	return false;
+}
+
+/*
+ * Given a JSON value, return its type.
+ *
+ * We return both a JsonType (for easy control flow), and a string name (for
+ * error reporting).
+ */
+static JsonType
+jsonval_get_type(Datum jsonval, char **typename)
+{
+	JsonType	json_elt_type;
+	Datum		paramtype_datum;
+	char	   *paramtype;
+
+	paramtype_datum = DirectFunctionCall1(json_typeof, jsonval);
+	paramtype = TextDatumGetCString(paramtype_datum);
+
+	if (strcmp(paramtype, "array") == 0)
+		json_elt_type = JsonTypeArray;
+	else if (strcmp(paramtype, "object") == 0)
+		json_elt_type = JsonTypeObject;
+	else if (strcmp(paramtype, "string") == 0)
+		json_elt_type = JsonTypeString;
+	else
+		/* XXX improve this; need to specify array index or param name */
+		elog(ERROR, "unexpected JSON element type %s",
+			 paramtype);
+
+	if (typename)
+		*typename = pstrdup(paramtype);
+
+	return json_elt_type;
+}
+
+/*
+ * dequote_jsonval
+ *		Take a string value extracted from a JSON object, and return a copy of it
+ *		with the quoting removed.
+ *
+ * Another alternative to this would be to run the extraction routine again,
+ * using the "_text" variant which returns the value without quotes; but this
+ * is expensive, and moreover it complicates the logic a lot because not all
+ * values are extracted in the same way (some are extracted using
+ * json_object_field, others using json_array_element).  Dequoting the object
+ * already at hand is a lot easier.
+ */
+static char *
+dequote_jsonval(char *jsonval)
+{
+	char	   *result;
+	int			inputlen = strlen(jsonval);
+	int			i;
+	int			j = 0;
+
+	result = palloc(strlen(jsonval) + 1);
+
+	/* skip the start and end quotes right away */
+	for (i = 1; i < inputlen - 1; i++)
+	{
+		if (jsonval[i] == '\\')
+		{
+			i++;
+
+			/* This uses same logic as json.c */
+			switch (jsonval[i])
+			{
+				case 'b':
+					result[j++] = '\b';
+					continue;
+				case 'f':
+					result[j++] = '\f';
+					continue;
+				case 'n':
+					result[j++] = '\n';
+					continue;
+				case 'r':
+					result[j++] = '\r';
+					continue;
+				case 't':
+					result[j++] = '\t';
+					continue;
+				case '"':
+				case '\\':
+				case '/':
+					break;
+				default:
+					/* XXX: ERROR? */
+					break;
+			}
+		}
+
+		result[j++] = jsonval[i];
+	}
+	result[j] = '\0';
+
+	return result;
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, Datum jsonval)
+{
+	char	   *unquoted;
+
+	unquoted = dequote_jsonval(TextDatumGetCString(jsonval));
+	appendStringInfo(buf, "%s", quote_identifier(unquoted));
+
+	pfree(unquoted);
+}
+
+/*
+ * Expand a json value as a dotted-name.  The value must be of type object
+ * and must contain elements "schemaname" (optional), "objname" (mandatory),
+ * "attrname" (optional).
+ *
+ * XXX do we need a "catalogname" as well?
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, Datum jsonval)
+{
+	char	   *schema;
+	char	   *objname;
+	char	   *attrname;
+	const char *qschema;
+	const char *qname;
+
+	schema = expand_get_strval(jsonval, "schemaname");
+	objname = expand_get_strval(jsonval, "objname");
+	if (objname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL object name in %%D element")));
+	qname = quote_identifier(objname);
+	if (schema == NULL)
+	{
+		appendStringInfo(buf, "%s", qname);
+	}
+	else
+	{
+		qschema = quote_identifier(schema);
+		appendStringInfo(buf, "%s.%s",
+						 qschema, qname);
+		if (qschema != schema)
+			pfree((char *) qschema);
+		pfree(schema);
+	}
+
+	attrname = expand_get_strval(jsonval, "attrname");
+	if (attrname)
+	{
+		const char *qattr;
+
+		qattr = quote_identifier(attrname);
+		appendStringInfo(buf, ".%s", qattr);
+		if (qattr != attrname)
+			pfree((char *) qattr);
+		pfree(attrname);
+	}
+
+	if (qname != objname)
+		pfree((char *) qname);
+	pfree(objname);
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		array_isnull;
+	bool		is_array;
+
+	typename = expand_get_strval(jsonval, "typename");
+	if (typename == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL type name in %%T element")));
+	typmodstr = expand_get_strval(jsonval, "typmod");	/* OK if null */
+	is_array = expand_get_boolval(jsonval, "is_array", &array_isnull);
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/*
+	 * If schema is NULL, then don't schema qualify, but do quote the type
+	 * name; if the schema is empty instead, then we don't quote the type name.
+	 * This is our (admittedly quite ugly) way of dealing with type names that
+	 * might require special treatment.
+	 */
+	if (schema == NULL)
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+	else if (schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 typename,
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *operator;
+
+	operator = expand_get_strval(jsonval, "objname");
+	if (operator == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL operator name in %%O element")));
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/* schema might be NULL or empty */
+	if (schema == NULL || schema[0] == '\0')
+		appendStringInfo(buf, "%s", operator);
+	else
+		appendStringInfo(buf, "%s.%s",
+						 quote_identifier(schema),
+						 operator);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object, in which case it must contain a "fmt" element which will be
+ * recursively expanded; also, if the object contains an element "present"
+ * and it is set to false, the expansion is the empty string.
+ */
+static void
+expand_jsonval_string(StringInfo buf, Datum jsonval, JsonType json_elt_type)
+{
+	if (json_elt_type == JsonTypeString)
+	{
+		char	   *str;
+		char	   *unquoted;
+
+		str = TextDatumGetCString(jsonval);
+		unquoted = dequote_jsonval(str);
+		appendStringInfo(buf, "%s", unquoted);
+		pfree(str);
+		pfree(unquoted);
+	}
+	else if (json_elt_type == JsonTypeObject)
+	{
+		bool		present;
+		bool		isnull;
+
+		present = expand_get_boolval(jsonval, "present", &isnull);
+
+		if (isnull || present)
+		{
+			Datum		inner;
+			char	   *str;
+
+			inner = DirectFunctionCall1(pg_event_trigger_expand_command,
+										jsonval);
+			str = TextDatumGetCString(inner);
+
+			appendStringInfoString(buf, str);
+			pfree(DatumGetPointer(inner));
+			pfree(str);
+		}
+	}
+}
+
+/*
+ * Expand a json value as a string literal
+ */
+static void
+expand_jsonval_strlit(StringInfo buf, Datum jsonval)
+{
+	char   *str;
+	char   *unquoted;
+	StringInfoData dqdelim;
+	static const char dqsuffixes[] = "_XYZZYX_";
+	int         dqnextchar = 0;
+
+	/* obtain the string, and remove the JSON quotes and stuff */
+	str = TextDatumGetCString(jsonval);
+	unquoted = dequote_jsonval(str);
+
+	/* easy case: if there are no ' and no \, just use a single quote */
+	if (strchr(unquoted, '\'') == NULL &&
+		strchr(unquoted, '\\') == NULL)
+	{
+		appendStringInfo(buf, "'%s'", unquoted);
+		return;
+	}
+
+	/* Find a useful dollar-quote delimiter */
+	initStringInfo(&dqdelim);
+	appendStringInfoString(&dqdelim, "$");
+	while (strstr(unquoted, dqdelim.data) != NULL)
+	{
+		appendStringInfoChar(&dqdelim, dqsuffixes[dqnextchar++]);
+		dqnextchar %= sizeof(dqsuffixes) - 1;
+	}
+	/* add trailing $ */
+	appendStringInfoChar(&dqdelim, '$');
+
+	/* And finally produce the quoted literal into the output StringInfo */
+	appendStringInfo(buf, "%s%s%s", dqdelim.data, unquoted, dqdelim.data);
+}
+
+/*
+ * Expand one json element according to rules.
+ */
+static void
+expand_one_element(StringInfo buf, char *param,
+				   Datum jsonval, char *valtype, JsonType json_elt_type,
+				   convSpecifier specifier)
+{
+	/*
+	 * Validate the parameter type.  If dotted-name was specified, then a JSON
+	 * object element is expected; if an identifier was specified, then a JSON
+	 * string is expected.	If a string was specified, then either a JSON
+	 * object or a string is expected.
+	 */
+	if (specifier == SpecDottedName && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%D element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecTypename && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%T element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecOperatorname && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%O element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecIdentifier && json_elt_type != JsonTypeString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%I element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecStringLiteral && json_elt_type != JsonTypeString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%L element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecString &&
+		json_elt_type != JsonTypeString && json_elt_type != JsonTypeObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string or object for %%s element \"%s\", got %s",
+						param, valtype)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			expand_jsonval_identifier(buf, jsonval);
+			break;
+
+		case SpecDottedName:
+			expand_jsonval_dottedname(buf, jsonval);
+			break;
+
+		case SpecString:
+			expand_jsonval_string(buf, jsonval, json_elt_type);
+			break;
+
+		case SpecStringLiteral:
+			expand_jsonval_strlit(buf, jsonval);
+			break;
+
+		case SpecTypename:
+			expand_jsonval_typename(buf, jsonval);
+			break;
+
+		case SpecOperatorname:
+			expand_jsonval_operator(buf, jsonval);
+			break;
+	}
+}
+
+/*
+ * Expand one JSON array element according to rules.
+ */
+static void
+expand_one_array_element(StringInfo buf, Datum array, int idx, char *param,
+						 convSpecifier specifier)
+{
+	Datum		elemval;
+	JsonType	json_elt_type;
+	char	   *elemtype;
+
+	elemval = DirectFunctionCall2(json_array_element,
+								  PointerGetDatum(array),
+								  Int32GetDatum(idx));
+	json_elt_type = jsonval_get_type(elemval, &elemtype);
+
+	expand_one_element(buf, param,
+					   elemval, elemtype, json_elt_type,
+					   specifier);
+}
+
+#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
+	do { \
+		if (++(ptr) >= (end_ptr)) \
+			ereport(ERROR, \
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+					 errmsg("unterminated format specifier"))); \
+	} while (0)
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * L		expand as a string literal (quote using single quotes)
+ * s		expand as a simple string (no quoting)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon.	Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *
+ * XXX the current implementation works fine, but is likely to be slow: for
+ * each element found in the fmt string we parse the JSON object once.	It
+ * might be better to use jsonapi.h directly so that we build a hash or tree of
+ * elements and their values once before parsing the fmt string, and later scan
+ * fmt using the tree.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	char	   *fmt_str;
+	int			fmt_len;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	StringInfoData str;
+
+	fmt_str = expand_get_strval(PointerGetDatum(json), "fmt");
+	if (fmt_str == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL format string")));
+	fmt_len = strlen(fmt_str);
+
+	start_ptr = fmt_str;
+	end_ptr = start_ptr + fmt_len;
+	initStringInfo(&str);
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+		Datum		paramval;
+		char	   *paramtype;
+		JsonType	json_elt_type;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(&str, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(&str, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'L':
+				specifier = SpecStringLiteral;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.  Note we cannot use
+		 * DirectFunctionCall here, because the element might not exist.
+		 */
+		{
+			FunctionCallInfoData fcinfo;
+
+			InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+			fcinfo.arg[0] = PointerGetDatum(json);
+			fcinfo.argnull[0] = false;
+			fcinfo.arg[1] = CStringGetTextDatum(param);
+			fcinfo.argnull[1] = false;
+
+			paramval = (*json_object_field) (&fcinfo);
+
+			if (fcinfo.isnull)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("non-existant element \"%s\" in JSON formatting object",
+								param)));
+			}
+		}
+
+		/* figure out its type */
+		json_elt_type = jsonval_get_type(paramval, &paramtype);
+
+		/* Validate that we got an array if the format string specified one. */
+		if (is_array && json_elt_type != JsonTypeArray)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("expected JSON array for element \"%s\", got %s",
+							param, paramtype)));
+
+		/* And finally print out the data */
+		if (is_array)
+		{
+			int			count;
+			bool		putsep = false;
+			int			i;
+
+			count = DatumGetInt32(DirectFunctionCall1(json_array_length,
+													  paramval));
+			for (i = 0; i < count; i++)
+			{
+				if (putsep)
+					appendStringInfoString(&str, arraysep);
+				putsep = true;
+
+				expand_one_array_element(&str, paramval, i, param, specifier);
+			}
+		}
+		else
+		{
+			expand_one_element(&str, param, paramval, paramtype, json_elt_type,
+							   specifier);
+		}
+	}
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(str.data));
+}
diff --git a/src/backend/tcop/Makefile b/src/backend/tcop/Makefile
index 674302f..34acdce 100644
--- a/src/backend/tcop/Makefile
+++ b/src/backend/tcop/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/tcop
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS= dest.o fastpath.o postgres.o pquery.o utility.o
+OBJS= dest.o deparse_utility.o fastpath.o postgres.o pquery.o utility.o
 
 ifneq (,$(filter $(PORTNAME),cygwin win32))
 override CPPFLAGS += -DWIN32_STACK_RLIMIT=$(WIN32_STACK_RLIMIT)
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
new file mode 100644
index 0000000..65b7e7d
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,877 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * This is intended to provide JSON blobs representing DDL commands, which can
+ * later be re-processed into plain strings by well-defined sprintf-like
+ * expansion.  These JSON objects are intended to allow for machine-editing of
+ * the commands, by replacing certain nodes within the objects.
+ *
+ * Much of the information in the output blob actually comes from system
+ * catalogs, not from the command parse node, as it is impossible to reliably
+ * construct a fully-specified command (i.e. one not dependent on search_path
+ * etc) looking only at the parse node.
+ *
+ * IDENTIFICATION
+ *	  src/backend/tcop/deparse_utility.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_trigger.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "lib/stringinfo.h"
+#include "nodes/makefuncs.h"
+#include "nodes/parsenodes.h"
+#include "parser/analyze.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Before they are turned into JSON representation, each command is represented
+ * as an object tree, using the structs below.
+ */
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjTree
+{
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+	bool		bool_value;
+	char	   *str_value;
+	ObjTree	   *obj_value;
+	List	   *array_value;
+	slist_node	node;
+} ObjElem;
+
+static ObjElem *new_null_object(char *name);
+static ObjElem *new_bool_object(char *name, bool value);
+static ObjElem *new_string_object(char *name, char *value);
+static ObjElem *new_object_object(char *name, ObjTree *value);
+static ObjElem *new_array_object(char *name, List *array);
+static void append_null_object(ObjTree *tree, char *name);
+static void append_bool_object(ObjTree *tree, char *name, bool value);
+static void append_string_object(ObjTree *tree, char *name, char *value);
+static void append_object_object(ObjTree *tree, char *name, ObjTree *value);
+static void append_array_object(ObjTree *tree, char *name, List *array);
+static inline void append_premade_object(ObjTree *tree, ObjElem *elem);
+
+/*
+ * Allocate a new object tree to store parameter values.
+ */
+static ObjTree *
+new_objtree(void)
+{
+	ObjTree    *params;
+
+	params = palloc(sizeof(ObjTree));
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object tree to store parameter values -- varargs version.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one,
+ * a name, type and value must be supplied.  Note we don't have the luxury of
+ * sprintf-like compiler warnings for malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree();
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		ObjTree    *value;
+		ObjType		type;
+		ObjElem	   *elem;
+		char	   *name;
+		char	   *strval;
+		bool		boolval;
+		List	   *list;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/* Null params don't have a value (obviously) */
+		if (type == ObjTypeNull)
+		{
+			append_null_object(tree, name);
+			continue;
+		}
+
+		/*
+		 * For all other param types there must be a value in the varargs.
+		 * Fetch it and add the fully formed subobject into the main object.
+		 */
+		switch (type)
+		{
+			case ObjTypeBool:
+				boolval = va_arg(args, int);
+				elem = new_bool_object(name, boolval);
+				break;
+			case ObjTypeString:
+				strval = va_arg(args, char *);
+				elem = new_string_object(name, strval);
+				break;
+			case ObjTypeObject:
+				value = va_arg(args, ObjTree *);
+				elem = new_object_object(name, value);
+				break;
+			case ObjTypeArray:
+				list = va_arg(args, List *);
+				elem = new_array_object(name, list);
+				break;
+			default:
+				elog(ERROR, "invalid parameter type %d", type);
+		}
+
+		append_premade_object(tree, elem);
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/* Allocate a new parameter with a NULL value */
+static ObjElem *
+new_null_object(char *name)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeNull;
+
+	return param;
+}
+
+/* Append a NULL object to a tree */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = new_null_object(name);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new boolean parameter */
+static ObjElem *
+new_bool_object(char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeBool;
+	param->bool_value = value;
+
+	return param;
+}
+
+/* Append a boolean parameter to a tree */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = new_bool_object(name, value);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new string object */
+static ObjElem *
+new_string_object(char *name, char *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeString;
+	param->str_value = value;	/* XXX not duped */
+
+	return param;
+}
+
+/*
+ * Append a string parameter to a tree.
+ *
+ * Note: we don't pstrdup the source string.  Caller must ensure the
+ * source string lives long enough.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem	   *param;
+
+	param = new_string_object(name, value);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new object parameter */
+static ObjElem *
+new_object_object(char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeObject;
+	param->obj_value = value;	/* XXX not duped */
+
+	return param;
+}
+
+/* Append an object parameter to a tree */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = new_object_object(name, value);
+	append_premade_object(tree, param);
+}
+
+/* Allocate a new array parameter */
+static ObjElem *
+new_array_object(char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = palloc0(sizeof(ObjElem));
+
+	param->name = name ? pstrdup(name) : NULL;
+	param->objtype = ObjTypeArray;
+	param->array_value = array; /* XXX not duped */
+
+	return param;
+}
+
+/* Append an array parameter to a tree */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = new_array_object(name, array);
+	append_premade_object(tree, param);
+}
+
+/* Append a preallocated parameter to a tree */
+static inline void
+append_premade_object(ObjTree *tree, ObjElem *elem)
+{
+	slist_push_head(&tree->params, &elem->node);
+	tree->numParams++;
+}
+
+/*
+ * Create a JSON blob from our ad-hoc representation.
+ *
+ * Note this leaks some memory; caller is responsible for later clean up.
+ *
+ * XXX this implementation will fail if there are more JSON objects in the tree
+ * than the maximum number of columns in a heap tuple.  To fix we would first call
+ * construct_md_array and then json_object.
+ */
+static char *
+jsonize_objtree(ObjTree *tree)
+{
+	TupleDesc	tupdesc;
+	Datum	   *values;
+	bool	   *nulls;
+	slist_iter	iter;
+	int			i;
+	HeapTuple	htup;
+	Datum		json;
+	char	   *jsonstr;
+
+	tupdesc = CreateTemplateTupleDesc(tree->numParams, false);
+	values = palloc(sizeof(Datum) * tree->numParams);
+	nulls = palloc(sizeof(bool) * tree->numParams);
+
+	i = 1;
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		Oid			typeid;
+
+		switch (object->objtype)
+		{
+			case ObjTypeNull:
+			case ObjTypeString:
+				typeid = TEXTOID;
+				break;
+			case ObjTypeBool:
+				typeid = BOOLOID;
+				break;
+			case ObjTypeArray:
+			case ObjTypeObject:
+				typeid = JSONOID;
+				break;
+			default:
+				elog(ERROR, "unable to determine type id");
+				typeid = InvalidOid;
+		}
+
+		TupleDescInitEntry(tupdesc, i, object->name, typeid, -1, 0);
+
+		nulls[i - 1] = false;
+		switch (object->objtype)
+		{
+			case ObjTypeNull:
+				nulls[i - 1] = true;
+				break;
+			case ObjTypeBool:
+				values[i - 1] = BoolGetDatum(object->bool_value);
+				break;
+			case ObjTypeString:
+				values[i - 1] = CStringGetTextDatum(object->str_value);
+				break;
+			case ObjTypeArray:
+				{
+					ArrayType  *arrayt;
+					Datum	   *arrvals;
+					Datum		jsonary;
+					ListCell   *cell;
+					int			length = list_length(object->array_value);
+					int			j;
+
+					/*
+					 * Arrays are stored as Lists up to this point, with each
+					 * element being a ObjElem; we need to construct an
+					 * ArrayType with them to turn the whole thing into a JSON
+					 * array.
+					 */
+					j = 0;
+					arrvals = palloc(sizeof(Datum) * length);
+					foreach(cell, object->array_value)
+					{
+						ObjElem    *elem = lfirst(cell);
+
+						switch (elem->objtype)
+						{
+							case ObjTypeString:
+								arrvals[j] =
+									/*
+									 * XXX need quotes around the value.  This
+									 * needs to be handled differently because
+									 * it will fail for values of anything but
+									 * trivial complexity.
+									 */
+									CStringGetTextDatum(psprintf("\"%s\"",
+																 elem->str_value));
+								break;
+							case ObjTypeObject:
+								arrvals[j] =
+									CStringGetTextDatum(jsonize_objtree(elem->obj_value));
+								break;
+							default:
+								/* not worth supporting other cases */
+								elog(ERROR, "unsupported object type %d",
+									 elem->objtype);
+						}
+
+						j++;
+
+					}
+					arrayt = construct_array(arrvals, length,
+											 JSONOID, -1, false, 'i');
+
+					jsonary = DirectFunctionCall1(array_to_json,
+												  (PointerGetDatum(arrayt)));
+
+					values[i - 1] = jsonary;
+				}
+				break;
+			case ObjTypeObject:
+				values[i - 1] =
+					CStringGetTextDatum(jsonize_objtree(object->obj_value));
+				break;
+		}
+
+		i++;
+	}
+
+	BlessTupleDesc(tupdesc);
+	htup = heap_form_tuple(tupdesc, values, nulls);
+	json = DirectFunctionCall1(row_to_json, HeapTupleGetDatum(htup));
+
+	jsonstr = TextDatumGetCString(json);
+
+	return jsonstr;
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	Oid			typnspid;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		is_array;
+
+	format_type_detailed(typeId, typmod,
+						 &typnspid, &typename, &typmodstr, &is_array);
+
+	if (!OidIsValid(typnspid))
+		typnsp = pstrdup("");
+	else if (isAnyTempNamespace(typnspid))
+		typnsp = pstrdup("pg_temp");
+	else
+		typnsp = get_namespace_name(typnspid);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree();
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "is_array", is_array);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * Elements "schemaname" and "objname" are set.  If the namespace OID
+ * corresponds to a temp schema, that's set to "pg_temp".
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree();
+	if (isAnyTempNamespace(nspid))
+		namespace = pstrdup("pg_temp");
+	else
+		namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ *
+ * Elements "schemaname" and "objname" are set.  If the object is a temporary
+ * object, the schema name is set to "pg_temp".
+ */
+static ObjTree *
+new_objtree_for_qualname_id(Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+
+	pfree(catobj);
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Handle deparsing of simple commands.
+ *
+ * This function contains a large switch that mirrors that in
+ * ProcessUtilitySlow.  All cases covered there should also be covered here.
+ */
+static ObjTree *
+deparse_simple_command(StashedCommand *cmd)
+{
+	Oid			objectId;
+	Node	   *parsetree;
+	ObjTree	   *command;
+
+	Assert(cmd->type == SCT_Simple);
+
+	parsetree = cmd->parsetree;
+	objectId = cmd->d.simple.objectId;
+
+	/* This switch needs to handle everything that ProcessUtilitySlow does */
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = NULL;
+			break;
+
+		case T_CreateStmt:
+			command = NULL;
+			break;
+
+		case T_CreateForeignTableStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTableStmt:
+		case T_AlterTableMoveAllStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterDomainStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+			/* other local objects */
+		case T_DefineStmt:
+			command = NULL;
+			break;
+
+		case T_IndexStmt:
+			command = NULL;
+			break;
+
+		case T_CreateExtensionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterExtensionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterExtensionContentsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterFdwStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterForeignServerStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterUserMappingStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropUserMappingStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_ImportForeignSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+			command = NULL;
+			break;
+
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+			command = NULL;
+			break;
+
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			command = NULL;
+			break;
+
+		case T_AlterEnumStmt:
+			command = NULL;
+			break;
+
+		case T_ViewStmt:		/* CREATE VIEW */
+			command = NULL;
+			break;
+
+		case T_CreateFunctionStmt:
+			command = NULL;
+			break;
+
+		case T_AlterFunctionStmt:
+			command = NULL;
+			break;
+
+		case T_RuleStmt:
+			command = NULL;
+			break;
+
+		case T_CreateSeqStmt:
+			command = NULL;
+			break;
+
+		case T_AlterSeqStmt:
+			command = NULL;
+			break;
+
+		case T_CreateTableAsStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_RefreshMatViewStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateTrigStmt:
+			command = NULL;
+			break;
+
+		case T_CreatePLangStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateDomainStmt:
+			command = NULL;
+			break;
+
+		case T_CreateConversionStmt:
+			command = NULL;
+			break;
+
+		case T_CreateCastStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpClassStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreateOpFamilyStmt:
+			command = NULL;
+			break;
+
+		case T_AlterOpFamilyStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSDictionaryStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterTSConfigurationStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_RenameStmt:
+			command = NULL;
+			break;
+
+		case T_AlterObjectSchemaStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterOwnerStmt:
+			command = NULL;
+			break;
+
+		case T_CommentStmt:
+			command = NULL;
+			break;
+
+		case T_GrantStmt:
+			/* handled elsewhere */
+			elog(ERROR, "unexpected command type %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_DropOwnedStmt:
+			/* goes through performDeletion; no action needed here */
+			command = NULL;
+			break;
+
+		case T_AlterDefaultPrivilegesStmt:
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_CreatePolicyStmt:	/* CREATE POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		case T_AlterPolicyStmt:		/* ALTER POLICY */
+			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
+
+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ */
+char *
+deparse_utility_command(StashedCommand *cmd)
+{
+	OverrideSearchPath *overridePath;
+	MemoryContext	oldcxt;
+	MemoryContext	tmpcxt;
+	ObjTree		   *tree;
+	char		   *command;
+
+	/*
+	 * Allocate everything done by the deparsing routines into a temp context,
+	 * to avoid having to sprinkle them with memory handling code
+	 */
+	tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "deparse ctx",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(tmpcxt);
+
+	/*
+	 * Many routines underlying this one will invoke ruleutils.c functionality
+	 * in order to obtain deparsed versions of expressions.  In such results,
+	 * we want all object names to be qualified, so that results are "portable"
+	 * to environments with different search_path settings.  Rather than inject
+	 * what would be repetitive calls to override search path all over the
+	 * place, we do it centrally here.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	overridePath->addCatalog = false;
+	overridePath->addTemp = false;
+	PushOverrideSearchPath(overridePath);
+
+	switch (cmd->type)
+	{
+		case SCT_Simple:
+			tree = deparse_simple_command(cmd);
+			break;
+		default:
+			elog(ERROR, "unexpected deparse node type %d", cmd->type);
+	}
+
+	PopOverrideSearchPath();
+
+	MemoryContextSwitchTo(oldcxt);
+	command = tree ? jsonize_objtree(tree) : NULL;
+
+	MemoryContextDelete(tmpcxt);
+
+	return command;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 63a9485..c61367a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -850,6 +850,8 @@ standard_ProcessUtility(Node *parsetree,
  * The "Slow" variant of ProcessUtility should only receive statements
  * supported by the event triggers facility.  Therefore, we always
  * perform the trigger support calls if the context allows it.
+ *
+ * See deparse_utility_command, which must be kept in sync with this.
  */
 static void
 ProcessUtilitySlow(Node *parsetree,
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index e1763a3..d9958d6 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,6 +326,106 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID.  For certain SQL-standard types which have weird
+ *   typmod rules, we return InvalidOid; caller is expected to not schema-
+ *   qualify the name nor add quotes to the type name.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - is_array indicates whether []s must be added
+ *
+ * Also, we don't try to decode type names to their standard-mandated names,
+ * except in the cases of unusual typmod rules, as specified above.
+ *
+ * XXX there is a lot of code duplication between this routine and
+ * format_type_internal.  (One thing that doesn't quite match is the whole
+ * allow_invalid business.)
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *is_array)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for types with strange typmod rules.
+	 */
+	if (type_oid == INTERVALOID ||
+		type_oid == TIMESTAMPOID ||
+		type_oid == TIMESTAMPTZOID)
+	{
+		switch (type_oid)
+		{
+			case INTERVALOID:
+				*typname = pstrdup("INTERVAL");
+				break;
+			case TIMESTAMPOID:
+			case TIMESTAMPTZOID:
+				/* the TZ part is added by typmod */
+				*typname = pstrdup("TIMESTAMP");
+				break;
+		}
+		*nspid = InvalidOid;
+
+		if (typemod >= 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");
+
+		*is_array = false;
+
+		ReleaseSysCache(tuple);
+		return;
+	}
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	if (array_base_type != InvalidOid &&
+		typeform->typstorage != 'p')
+	{
+		/* Switch our attention to the array element type */
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+		*is_array = true;
+	}
+	else
+		*is_array = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod >= 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +441,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 +453,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/objectaddress.h b/src/include/catalog/objectaddress.h
index 2a9431d..fd20291 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -32,6 +32,8 @@ extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
 
+extern Oid get_objtype_catalog_oid(ObjectType objtype);
+
 extern void check_object_ownership(Oid roleid,
 					   ObjectType objtype, ObjectAddress address,
 					   List *objname, List *objargs, Relation relation);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 3ce9849..0947611 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4986,6 +4986,10 @@ DESCR("peek at binary changes from replication slot");
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
 DESCR("list objects dropped by the current command");
+DATA(insert OID = 3590 (  pg_event_trigger_get_creation_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,114}" "{o,o,o,o,o,o,o,o,o}" "{classid,objid,objsubid,command_tag,object_type,schema,identity,in_extension,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list JSON-formatted commands executed by the current command");
+DATA(insert OID = 3591 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON 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_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 0233f4c..9c276f1 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -52,4 +52,7 @@ extern void EventTriggerEndCompleteQuery(void);
 extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
+extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId,
+						 ObjectType objtype, Node *parsetree);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 2cf784b..6e53320 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -23,7 +23,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 f3aa69e..18cfc40 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1207,6 +1207,7 @@ typedef enum ObjectType
 	OBJECT_ATTRIBUTE,			/* type's attribute, when distinct from column */
 	OBJECT_CAST,
 	OBJECT_COLUMN,
+	OBJECT_COMPOSITE,
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
@@ -1238,6 +1239,7 @@ typedef enum ObjectType
 	OBJECT_TSPARSER,
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
+	OBJECT_USER_MAPPING,
 	OBJECT_VIEW
 } ObjectType;
 
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..1278df9
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, 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 "nodes/nodes.h"
+
+/*
+ * Support for keeping track of a command to deparse.
+ *
+ * When a command is run, we collect some information about it for later
+ * deparsing; deparse_utility_command can later be used to obtain a usable
+ * representation of it.
+ */
+
+typedef enum StashedCommandType
+{
+	SCT_Simple,
+} StashedCommandType;
+
+/*
+ * For ALTER TABLE commands, we keep a list of the subcommands therein.
+ */
+typedef struct StashedATSubcmd
+{
+	AttrNumber		attnum;	/* affected column number */
+	Oid				oid;	/* affected constraint, default value or index */
+	Node		   *parsetree;
+} StashedATSubcmd;
+
+typedef struct StashedCommand
+{
+	StashedCommandType type;
+	bool		in_extension;
+	Node	   *parsetree;
+
+	union
+	{
+		struct SimpleCommand
+		{
+			Oid			objectId;
+			uint32		objectSubId;
+			ObjectType	objtype;
+		} simple;
+	} d;
+} StashedCommand;
+
+extern char *deparse_utility_command(StashedCommand *cmd);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index fb1b4a4..8e3e622 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1068,6 +1068,9 @@ extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern Datum oidvectortypes(PG_FUNCTION_ARGS);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
+extern void format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname,
+					 char **typemodstr, bool *is_array);
 
 /* quote.c */
 extern Datum quote_ident(PG_FUNCTION_ARGS);
@@ -1189,6 +1192,8 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 
 /* commands/event_trigger.c */
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
-- 
1.9.1

