diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index e511669..0c75ed1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -2014,6 +2014,13 @@ add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
 	addrs->numrefs++;
 }
 
+Oid
+get_class_catalog(ObjectClass oclass)
+{
+	Assert(oclass < MAX_OCLASS);
+	return object_classes[oclass];
+}
+
 /*
  * Add an entry to an ObjectAddresses array.
  *
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 73e6e20..bdc15f1 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -55,6 +55,9 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+/* the OID of the created table, for ExecCreateTableAs consumption */
+static Oid	CreateAsRelid = InvalidOid;
+
 static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
 static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
@@ -64,7 +67,7 @@ static void intorel_destroy(DestReceiver *self);
 /*
  * ExecCreateTableAs -- execute a CREATE TABLE AS command
  */
-void
+Oid
 ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag)
 {
@@ -75,6 +78,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	Oid			save_userid = InvalidOid;
 	int			save_sec_context = 0;
 	int			save_nestlevel = 0;
+	Oid			relOid;
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
@@ -98,7 +102,9 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		Assert(!is_matview);	/* excluded by syntax */
 		ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
 
-		return;
+		relOid = CreateAsRelid;
+		CreateAsRelid = InvalidOid;
+		return relOid;
 	}
 	Assert(query->commandType == CMD_SELECT);
 
@@ -190,6 +196,11 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
 	}
+
+	relOid = CreateAsRelid;
+	CreateAsRelid = InvalidOid;
+
+	return relOid;
 }
 
 /*
@@ -421,6 +432,9 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	myState->rel = intoRelationDesc;
 	myState->output_cid = GetCurrentCommandId(true);
 
+	/* and remember the new relation's OID for ExecCreateTableAs */
+	CreateAsRelid = RelationGetRelid(myState->rel);
+
 	/*
 	 * We can skip WAL-logging the insertions, unless PITR or streaming
 	 * replication is in use. We can skip the FSM in any case.
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 59f0842..9a9cd5a 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -31,10 +31,12 @@
 #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 +50,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	List	   *stash;
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -491,7 +494,7 @@ AlterEventTriggerOwner(const char *name, Oid newOwnerId)
 }
 
 /*
- * Change extension owner, by OID
+ * Change event trigger owner, by OID
  */
 void
 AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId)
@@ -1022,13 +1025,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,
@@ -1036,8 +1032,10 @@ EventTriggerBeginCompleteQuery(void)
 								ALLOCSET_DEFAULT_MAXSIZE);
 	state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));
 	state->cxt = cxt;
-	slist_init(&(state->SQLDropList));
+	if (trackDroppedObjectsNeeded())
+		slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1217,7 +1215,7 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 		 errmsg("%s can only be called in a sql_drop event trigger function",
-				"pg_event_trigger_dropped_objects()")));
+				PG_FUNCNAME_MACRO)));
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -1294,3 +1292,779 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
+ * Support for tracking of objects created during command run.
+ *
+ * When a command is run that creates some SQL objects, we collect the
+ * classid/objid of the objects being created, as well as the parsetree of the
+ * creation command; later, when event triggers are run for that command, they
+ * can use pg_event_trigger_get_creation_commands which computes and returns a
+ * usable representation of the creation commands for the objects.
+ */
+typedef struct stashedObject
+{
+	Oid			objectId;
+	Oid			classId;
+	Node	   *parsetree;
+} stashedObject;
+
+static stashedObject *
+newStashedObject(Oid objectId, ObjectClass class, Node *parsetree)
+{
+	stashedObject *stashed = palloc(sizeof(stashedObject));
+
+	stashed->objectId = objectId;
+	stashed->classId = get_class_catalog(class);
+	stashed->parsetree = copyObject(parsetree);
+
+	return stashed;
+}
+
+void
+EventTriggerStashCreatedObject(Oid objectId, ObjectClass class,
+							   Node *parsetree)
+{
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	currentEventTriggerState->stash =
+		lappend(currentEventTriggerState->stash,
+				newStashedObject(objectId, class, parsetree));
+
+	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_FUNCNAME_MACRO)));
+
+	/* 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)
+	{
+		stashedObject *obj = lfirst(lc);
+		char	   *command;
+
+		command = deparse_utility_command(obj->objectId, obj->parsetree);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{
+			Datum		values[2];
+			bool		nulls[2];
+			ObjectAddress addr;
+			char	   *identity;
+			int			i = 0;
+
+			addr.classId = obj->classId;
+			addr.objectId = obj->objectId;
+			addr.objectSubId = 0;
+			identity = getObjectIdentity(&addr);
+
+			MemSet(nulls, 0, sizeof(nulls));
+
+			/* identity */
+			values[i++] = CStringGetTextDatum(identity);
+			/* command */
+			values[i++] = CStringGetTextDatum(command);
+
+			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+			pfree(identity);
+		}
+	}
+
+	/* 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
+{
+	JsonIsArray,
+	JsonIsObject,
+	JsonIsString
+} JsonType;
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	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;
+
+	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 = JsonIsArray;
+	else if (strcmp(paramtype, "object") == 0)
+		json_elt_type = JsonIsObject;
+	else if (strcmp(paramtype, "string") == 0)
+		json_elt_type = JsonIsString;
+	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++)
+	{
+		/*
+		 * XXX this skips the \ in a \" sequence but leaves other escaped
+		 * sequences in place.	Are there other cases we need to handle
+		 * specially?
+		 */
+		if (jsonval[i] == '\\' &&
+			jsonval[i + 1] == '"')
+		{
+			i++;
+			continue;
+		}
+
+		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");
+
+	/* schema might be NULL or empty here, beware */
+	if (schema == NULL || schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(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 == JsonIsString)
+	{
+		char	   *str;
+		char	   *unquoted;
+
+		str = TextDatumGetCString(jsonval);
+		unquoted = dequote_jsonval(str);
+		appendStringInfo(buf, "%s", unquoted);
+		pfree(str);
+		pfree(unquoted);
+	}
+	else if (json_elt_type == JsonIsObject)
+	{
+		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 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 != JsonIsObject)
+		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 != JsonIsObject)
+		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 != JsonIsObject)
+		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 != JsonIsString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%I element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecString &&
+		json_elt_type != JsonIsString && json_elt_type != JsonIsObject)
+		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 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
+ * 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")));
+
+		/*
+		 * The following conversion specifiers are currently recognized: 'I'
+		 * -- expand as an identifier, adding quotes if necessary 'D' --
+		 * expand as a dotted-name, for qualified names; each element is
+		 * quoted if necessary 's' -- expand as a simple string; no quoting.
+		 */
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				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 != JsonIsArray)
+			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/commands/matview.c b/src/backend/commands/matview.c
index a331f3e..0f0ecbe 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -132,7 +132,7 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
  * The matview's "populated" state is changed based on whether the contents
  * reflect the result set of the materialized view's query.
  */
-void
+Oid
 ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag)
 {
@@ -274,6 +274,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	}
 	else
 		refresh_by_heap_swap(matviewOid, OIDNewHeap);
+
+	return matviewOid;
 }
 
 /*
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 2599e28..2859ee0 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -24,6 +24,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"
@@ -130,6 +131,15 @@ 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.
+	 */
+	EventTriggerStashCreatedObject(namespaceId, OCLASS_SCHEMA,
+								   (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/sequence.c b/src/backend/commands/sequence.c
index ed696be..564834e 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1552,7 +1552,42 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 	return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
 }
 
+/*
+ * Return sequence parameters, detailed
+ */
+Form_pg_sequence
+get_sequence_values(Oid sequenceId)
+{
+	Buffer		buf;
+	SeqTable	elm;
+	Relation	seqrel;
+	HeapTupleData seqtuple;
+	Form_pg_sequence seq;
+	Form_pg_sequence retSeq;
+
+	retSeq = palloc(sizeof(FormData_pg_sequence));
+
+	/* open and AccessShareLock sequence */
+	init_sequence(sequenceId, &elm, &seqrel);
+
+	if (pg_class_aclcheck(sequenceId, GetUserId(),
+						  ACL_SELECT | ACL_UPDATE | ACL_USAGE) != ACLCHECK_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for sequence %s",
+						RelationGetRelationName(seqrel))));
+
+	seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple);
+
+	memcpy(retSeq, seq, sizeof(FormData_pg_sequence));
+
+	UnlockReleaseBuffer(buf);
+	relation_close(seqrel, NoLock);
+
+	return retSeq;
+}
 
+/* definition elements */
 void
 seq_redo(XLogRecPtr lsn, XLogRecord *record)
 {
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 08b037e..a6d38fa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -43,6 +43,7 @@
 #include "catalog/pg_type_fn.h"
 #include "catalog/storage.h"
 #include "catalog/toasting.h"
+#include "commands/alter_table.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -112,54 +113,6 @@ typedef struct OnCommitItem
 static List *on_commits = NIL;
 
 
-/*
- * State information for ALTER TABLE
- *
- * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
- * structs, one for each table modified by the operation (the named table
- * plus any child tables that are affected).  We save lists of subcommands
- * to apply to this table (possibly modified by parse transformation steps);
- * these lists will be executed in Phase 2.  If a Phase 3 step is needed,
- * necessary information is stored in the constraints and newvals lists.
- *
- * Phase 2 is divided into multiple passes; subcommands are executed in
- * a pass determined by subcommand type.
- */
-
-#define AT_PASS_UNSET			-1		/* UNSET will cause ERROR */
-#define AT_PASS_DROP			0		/* DROP (all flavors) */
-#define AT_PASS_ALTER_TYPE		1		/* ALTER COLUMN TYPE */
-#define AT_PASS_OLD_INDEX		2		/* re-add existing indexes */
-#define AT_PASS_OLD_CONSTR		3		/* re-add existing constraints */
-#define AT_PASS_COL_ATTRS		4		/* set other column attributes */
-/* We could support a RENAME COLUMN pass here, but not currently used */
-#define AT_PASS_ADD_COL			5		/* ADD COLUMN */
-#define AT_PASS_ADD_INDEX		6		/* ADD indexes */
-#define AT_PASS_ADD_CONSTR		7		/* ADD constraints, defaults */
-#define AT_PASS_MISC			8		/* other stuff */
-#define AT_NUM_PASSES			9
-
-typedef struct AlteredTableInfo
-{
-	/* Information saved before any work commences: */
-	Oid			relid;			/* Relation to work on */
-	char		relkind;		/* Its relkind */
-	TupleDesc	oldDesc;		/* Pre-modification tuple descriptor */
-	/* Information saved by Phase 1 for Phase 2: */
-	List	   *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
-	/* Information saved by Phases 1/2 for Phase 3: */
-	List	   *constraints;	/* List of NewConstraint */
-	List	   *newvals;		/* List of NewColumnValue */
-	bool		new_notnull;	/* T if we added new NOT NULL constraints */
-	bool		rewrite;		/* T if a rewrite is forced */
-	Oid			newTableSpace;	/* new tablespace; 0 means no change */
-	/* Objects to rebuild after completing ALTER TYPE operations */
-	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
-	List	   *changedConstraintDefs;	/* string definitions of same */
-	List	   *changedIndexOids;		/* OIDs of indexes to rebuild */
-	List	   *changedIndexDefs;		/* string definitions of same */
-} AlteredTableInfo;
-
 /* Struct describing one new constraint to check in Phase 3 scan */
 /* Note: new NOT NULL constraints are handled elsewhere */
 typedef struct NewConstraint
@@ -7762,7 +7715,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				if (!list_member_oid(tab->changedConstraintOids,
 									 foundObject.objectId))
 				{
-					char	   *defstring = pg_get_constraintdef_string(foundObject.objectId);
+					char	   *defstring = pg_get_constraintdef_string(foundObject.objectId,
+																		true);
 
 					/*
 					 * Put NORMAL dependencies at the front of the list and
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..98b3711
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,1488 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_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_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 "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/syscache.h"
+
+
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+	bool		bool_value;
+	char	   *str_value;
+	struct ObjTree *obj_value;
+	List	   *array_value;
+	slist_node	node;
+} ObjElem;
+
+typedef struct ObjTree
+{
+	MemoryContext cxt;
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+
+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);
+
+/*
+ * Allocate a new object tree to store parameter values.  If parent is NULL, a
+ * new memory context is created for all allocations involving the parameters;
+ * if it's not null, then the memory context from the given object is used.
+ */
+static ObjTree *
+new_objtree(ObjTree *parent)
+{
+	MemoryContext cxt;
+	ObjTree    *params;
+
+	if (parent == NULL)
+	{
+		cxt = AllocSetContextCreate(CurrentMemoryContext,
+									"deparse parameters",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+	}
+	else
+		cxt = parent->cxt;
+
+	params = MemoryContextAlloc(cxt, sizeof(ObjTree));
+	params->cxt = cxt;
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object to store parameters.  As above, if parent is NULL,
+ * a new memory context is created; otherwise the parent is used to extract
+ * the memory context to use.
+ *
+ * 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(ObjTree *parent, char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree(parent);
+	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;
+		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);
+				append_bool_object(tree, name, boolval);
+				break;
+			case ObjTypeString:
+				strval = va_arg(args, char *);
+				append_string_object(tree, name, strval);
+				break;
+			case ObjTypeObject:
+				value = va_arg(args, ObjTree *);
+				append_object_object(tree, name, value);
+				break;
+			case ObjTypeArray:
+				list = va_arg(args, List *);
+				append_array_object(tree, name, list);
+				break;
+			default:
+				elog(ERROR, "invalid parameter type %d", type);
+		}
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/*
+ * Add a new parameter with a NULL value
+ */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeNull;
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new boolean parameter
+ */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeBool;
+	param->bool_value = value;
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new string parameter.
+ *
+ * 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 = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeString;
+	param->str_value = value;	/* XXX not duped */
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new object parameter
+ */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeObject;
+	param->obj_value = value;	/* XXX not duped */
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new array parameter
+ */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeArray;
+	param->array_value = array; /* XXX not duped */
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Create a JSON blob from our ad-hoc representation.
+ *
+ * Note this allocates memory in tree->cxt.  This is okay because we don't need
+ * a separate memory context, and the one provided by the tree object has the
+ * right lifetime already.
+ *
+ * XXX this implementation will fail if there are more JSON objects in the tree
+ * than the maximum number of columns in a heap tuple.
+ */
+static char *
+jsonize_objtree(ObjTree *tree)
+{
+	MemoryContext oldcxt;
+	TupleDesc	tupdesc;
+	Datum	   *values;
+	bool	   *nulls;
+	slist_iter	iter;
+	int			i;
+	HeapTuple	htup;
+	Datum		json;
+	char	   *jsonstr;
+
+	/*
+	 * Use the objtree's memory context, so that everything we allocate in
+	 * this routine (other than our return string) can be blown up easily
+	 * by our caller.
+	 */
+	oldcxt = MemoryContextSwitchTo(tree->cxt);
+	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)
+					{
+						ObjTree    *json = lfirst(cell);
+
+						arrvals[j++] =
+							CStringGetTextDatum(jsonize_objtree(json));
+					}
+					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));
+
+	/* switch to caller's context so that our output is allocated there */
+	MemoryContextSwitchTo(oldcxt);
+
+	jsonstr = TextDatumGetCString(json);
+
+	return jsonstr;
+}
+
+/*
+ * Release all memory used by parameters and their expansion
+ */
+static void
+free_objtree(ObjTree *tree)
+{
+	MemoryContextDelete(tree->cxt);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(ObjTree *parent, Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		is_array;
+
+	format_type_detailed(typeId, typmod,
+						 &typnsp, &typename, &typmodstr, &is_array);
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree(parent);
+	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
+ *
+ * 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(ObjTree *parent, 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(parent);
+	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
+ */
+static ObjTree *
+new_objtree_for_qualname_id(ObjTree *parent, 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(parent,
+										 DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+
+	pfree(catobj);
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Return the string representation of the given RELPERSISTENCE value
+ */
+static char *
+get_persistence_str(char persistence)
+{
+	switch (persistence)
+	{
+		case RELPERSISTENCE_TEMP:
+			return "TEMPORARY";
+		case RELPERSISTENCE_UNLOGGED:
+			return "UNLOGGED";
+		case RELPERSISTENCE_PERMANENT:
+			return "";
+		default:
+			return "???";
+	}
+}
+
+/*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	char	   *command;
+	Relation	relation;
+	OverrideSearchPath *overridePath;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA(NULL,
+					 "CREATE %{persistence}s VIEW %{identity}D AS %{query}s",
+							  1, "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(viewStmt,
+								   relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	/*
+	 * We want all names to be qualified, so set an empty search path before
+	 * calling ruleutils.c.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	PushOverrideSearchPath(overridePath);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+	PopOverrideSearchPath();
+
+	command = jsonize_objtree(viewStmt);
+	free_objtree(viewStmt);
+
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+
+/* ruleutils.c */
+extern char *relation_get_column_default(Relation rel, AttrNumber attno,
+							List *dpcontext);
+
+/*
+ * deparse_ColumnDef
+ *		Subroutine for CREATE TABLE deparsing
+ *
+ * Deparse a ColumnDef node within a regular (non typed) table creation.
+ *
+ * NOT NULL constraints in the column definition are emitted directly in the
+ * column definition by this routine; other constraints must be emitted
+ * elsewhere (the info in the parse node is incomplete anyway.)
+ */
+static ObjTree *
+deparse_ColumnDef(ObjTree *parent, Relation relation, List *dpcontext,
+				  ColumnDef *coldef)
+{
+	ObjTree    *column;
+	ObjTree    *tmp;
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+
+	/*
+	 * Inherited columns without local definitions must not be emitted. XXX --
+	 * maybe it is useful to have them with "present = false" or some such?
+	 */
+	if (!coldef->is_local)
+		return NULL;
+
+	attrTup = SearchSysCacheAttName(relid, coldef->colname);
+	if (!HeapTupleIsValid(attrTup))
+		elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+			 coldef->colname, relid);
+	attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+	get_atttypetypmodcoll(relid, attrForm->attnum,
+						  &typid, &typmod, &typcollation);
+
+	column = new_objtree_VA(parent,
+							"%{name}I %{coltype}T %{default}s %{not_null}s %{collation}s",
+							3,
+							"type", ObjTypeString, "column",
+							"name", ObjTypeString, coldef->colname,
+							"coltype", ObjTypeObject,
+							new_objtree_for_type(parent, typid, typmod));
+
+	tmp = new_objtree_VA(parent, "COLLATE %{name}I", 0);
+	if (OidIsValid(typcollation))
+	{
+		char	   *collname;
+
+		collname = get_collation_name(attrForm->attcollation);
+		append_string_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	/*
+	 * Emit a NOT NULL declaration if necessary.  Note that we cannot trust
+	 * pg_attribute.attnotnull here, because that bit is also set when primary
+	 * keys are specified; and we must not emit a NOT NULL constraint in that
+	 * case, unless explicitely specified; so we scan the list of constraints
+	 * attached to this column.  (Fortunately, NOT NULL constraints cannot be
+	 * table constraints.)
+	 */
+	saw_notnull = false;
+	foreach(cell, coldef->constraints)
+	{
+		Constraint *constr = (Constraint *) lfirst(cell);
+
+		if (constr->contype == CONSTR_NOTNULL)
+			saw_notnull = true;
+	}
+
+	if (saw_notnull)
+		append_string_object(column, "not_null", "NOT NULL");
+	else
+		append_string_object(column, "not_null", "");
+
+	tmp = new_objtree_VA(parent, "DEFAULT %{default}s", 0);
+	if (attrForm->atthasdef)
+	{
+		char *defstr;
+
+		defstr = relation_get_column_default(relation, attrForm->attnum,
+											 dpcontext);
+
+		append_string_object(tmp, "default", defstr);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "default", tmp);
+
+	ReleaseSysCache(attrTup);
+
+	return column;
+}
+
+/*
+ * deparse_ColumnDef_Typed
+ *		Subroutine for CREATE TABLE OF deparsing
+ *
+ * Deparse a ColumnDef node within a typed table creation.	This is simpler
+ * than the regular case, because we don't have to emit the type declaration,
+ * collation, or default.  Here we only return something if the column is being
+ * declared NOT NULL.
+ *
+ * As in deparse_ColumnDef, any other constraint is processed elsewhere.
+ *
+ * FIXME --- actually, what about default values?
+ */
+static ObjTree *
+deparse_ColumnDef_typed(ObjTree *parent, Relation relation, List *dpcontext,
+						ColumnDef *coldef)
+{
+	ObjTree    *column = NULL;
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+
+	attrTup = SearchSysCacheAttName(relid, coldef->colname);
+	if (!HeapTupleIsValid(attrTup))
+		elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+			 coldef->colname, relid);
+	attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+	get_atttypetypmodcoll(relid, attrForm->attnum,
+						  &typid, &typmod, &typcollation);
+
+	/*
+	 * Search for a NOT NULL declaration.  As in deparse_ColumnDef, we rely on
+	 * finding a constraint on the column rather than coldef->is_not_null.
+	 */
+	saw_notnull = false;
+	foreach(cell, coldef->constraints)
+	{
+		Constraint *constr = (Constraint *) lfirst(cell);
+
+		if (constr->contype == CONSTR_NOTNULL)
+		{
+			saw_notnull = true;
+			break;
+		}
+	}
+
+	if (saw_notnull)
+		column = new_objtree_VA(parent,
+								"%{name}I WITH OPTIONS NOT NULL", 2,
+								"type", ObjTypeString, "column_notnull",
+								"name", ObjTypeString, coldef->colname);
+
+	ReleaseSysCache(attrTup);
+	return column;
+}
+
+/*
+ * deparseTableElements
+ *		Subroutine for CREATE TABLE deparsing
+ *
+ * Deal with all the table elements (columns and constraints).
+ *
+ * Note we ignore constraints in the parse node here; they are extracted from
+ * system catalogs instead.
+ */
+static List *
+deparseTableElements(List *elements, ObjTree *parent, Relation relation,
+					 List *tableElements, List *dpcontext, bool typed)
+{
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree    *column;
+
+					column = typed ?
+						deparse_ColumnDef_typed(parent, relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(parent, relation, dpcontext,
+										  (ColumnDef *) elt);
+
+					if (column != NULL)
+						elements = lappend(elements, column);
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * obtainTableConstraints
+ *		Subroutine for CREATE TABLE deparsing
+ *
+ * Given a table OID, obtain its constraints and append them to the given
+ * elements list.  The updated list is returned.
+ *
+ * This works for both typed and regular tables.
+ */
+static List *
+obtainTableConstraints(List *elements, Oid objectId, ObjTree *parent)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+	OverrideSearchPath *overridePath;
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_constraint_conrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(objectId));
+	scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+							  true, NULL, 1, &key);
+
+	/*
+	 * We need to ensure all names in the constraint definitions are
+	 * qualified, so set an empty search_path for the duration of this loop.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	PushOverrideSearchPath(overridePath);
+
+	/*
+	 * For each constraint, add a node to the list of table elements.  In
+	 * these nodes we include not only the printable information ("fmt"), but
+	 * also separate attributes to indicate the type of constraint, for
+	 * automatic processing.
+	 */
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_constraint constrForm;
+		char	   *contype;
+
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+
+		switch (constrForm->contype)
+		{
+			case CONSTRAINT_CHECK:
+				contype = "check";
+				break;
+			case CONSTRAINT_FOREIGN:
+				contype = "foreign key";
+				break;
+			case CONSTRAINT_PRIMARY:
+				contype = "primary key";
+				break;
+			case CONSTRAINT_UNIQUE:
+				contype = "unique";
+				break;
+			case CONSTRAINT_TRIGGER:
+				contype = "trigger";
+				break;
+			case CONSTRAINT_EXCLUSION:
+				contype = "exclusion";
+				break;
+			default:
+				elog(ERROR, "unrecognized constraint type");
+		}
+
+		/*
+		 * "type" and "contype" are not part of the printable output, but are
+		 * useful to programmatically distinguish these from columns and among
+		 * different constraint types.
+		 *
+		 * XXX it might be useful to also list the column names in a PK, etc.
+		 */
+		tmp = new_objtree_VA(parent,
+							 "CONSTRAINT %{name}I %{definition}s",
+							 4,
+							 "type", ObjTypeString, "constraint",
+							 "contype", ObjTypeString, contype,
+						 "name", ObjTypeString, NameStr(constrForm->conname),
+							 "definition", ObjTypeString,
+						  pg_get_constraintdef_string(HeapTupleGetOid(tuple),
+													  false));
+		elements = lappend(elements, tmp);
+	}
+
+	PopOverrideSearchPath();
+
+	systable_endscan(scan);
+	heap_close(conRel, AccessShareLock);
+
+	return elements;
+}
+
+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)
+ *
+ * Given a table OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	char	   *command;
+	char	   *fmtstr;
+
+	/*
+	 * Typed tables use a slightly different format string: we must not put
+	 * table_elements with parents directly in the fmt string, because if
+	 * there are no options the parens must not be emitted; and also, typed
+	 * tables do not allow for inheritance.
+	 */
+	if (node->ofTypename)
+		fmtstr = "CREATE %{persistence}s TABLE %{identity}D %{if_not_exists}s "
+			"OF %{of_type}T %{table_elements}s "
+			"%{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{identity}D %{if_not_exists}s "
+			"(%{table_elements:, }s) %{inherits}s "
+			"%{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(NULL, fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(createStmt,
+								   relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createStmt, "identity", tmp);
+
+	append_string_object(createStmt, "if_not_exists",
+						 node->if_not_exists ? "IF NOT EXISTS" : "");
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+								  objectId);
+
+	if (node->ofTypename)
+	{
+		List	   *tableelts = NIL;
+
+		/*
+		 * We can't put table elements directly in the fmt string as an array
+		 * surrounded by parens here, because an empty clause would cause a
+		 * syntax error.  Therefore, we use an indirection element and set
+		 * present=false when there are no elements.
+		 */
+		append_string_object(createStmt, "table_kind", "typed");
+
+		tmp = new_objtree_for_type(createStmt, relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(NIL, createStmt, relation,
+										 node->tableElts, dpcontext, true);
+		tableelts = obtainTableConstraints(tableelts, objectId, createStmt);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA(createStmt, "", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA(createStmt, "(%{elements:, }s)", 1,
+								 "elements", ObjTypeArray, tableelts);
+		append_object_object(createStmt, "table_elements", tmp);
+	}
+	else
+	{
+		List	   *tableelts = NIL;
+
+		/*
+		 * There is no need to process LIKE clauses separately; they have
+		 * already been transformed into columns and constraints.
+		 */
+		append_string_object(createStmt, "table_kind", "plain");
+
+		/*
+		 * Process table elements: column definitions and constraints.	Only
+		 * the column definitions are obtained from the parse node itself.	To
+		 * get constraints we rely on pg_constraint, because the parse node
+		 * might be missing some things such as the name of the constraints.
+		 */
+		tableelts = deparseTableElements(NIL, createStmt, relation,
+										 node->tableElts, dpcontext, false);
+		tableelts = obtainTableConstraints(tableelts, objectId, createStmt);
+
+		append_array_object(createStmt, "table_elements", tableelts);
+
+		/*
+		 * Add inheritance specification.  We cannot simply scan the list of
+		 * parents from the parser node, because that may lack the actual
+		 * qualified names of the parent relations.  Rather than trying to
+		 * re-resolve them from the information in the parse node, it seems
+		 * more accurate and convenient to grab it from pg_inherits.
+		 */
+		tmp = new_objtree_VA(createStmt, "INHERITS (%{parents:, }D)", 0);
+		if (list_length(node->inhRelations) > 0)
+		{
+			List	   *parents = NIL;
+			Relation	inhRel;
+			SysScanDesc scan;
+			ScanKeyData key;
+			HeapTuple	tuple;
+
+			inhRel = heap_open(InheritsRelationId, RowExclusiveLock);
+
+			ScanKeyInit(&key,
+						Anum_pg_inherits_inhrelid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(objectId));
+
+			scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId,
+									  true, NULL, 1, &key);
+
+			while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+			{
+				ObjTree    *parent;
+				Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
+
+				parent = new_objtree_for_qualname_id(createStmt,
+													 RelationRelationId,
+													 formInh->inhparent);
+
+				parents = lappend(parents, parent);
+			}
+
+			systable_endscan(scan);
+			heap_close(inhRel, RowExclusiveLock);
+
+			append_array_object(tmp, "parents", parents);
+		}
+		else
+		{
+			append_null_object(tmp, "parents");
+			append_bool_object(tmp, "present", false);
+		}
+		append_object_object(createStmt, "inherits", tmp);
+	}
+
+	tmp = new_objtree_VA(createStmt, "TABLESPACE %{tablespace}I", 0);
+	if (node->tablespacename)
+		append_string_object(tmp, "tablespace", node->tablespacename);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	tmp = new_objtree_VA(createStmt, "ON COMMIT %{on_commit_value}s", 0);
+	switch (node->oncommit)
+	{
+		case ONCOMMIT_DROP:
+			append_string_object(tmp, "on_commit_value", "DROP");
+			break;
+
+		case ONCOMMIT_DELETE_ROWS:
+			append_string_object(tmp, "on_commit_value", "DELETE ROWS");
+			break;
+
+		case ONCOMMIT_PRESERVE_ROWS:
+			append_string_object(tmp, "on_commit_value", "PRESERVE ROWS");
+			break;
+
+		case ONCOMMIT_NOOP:
+			append_null_object(tmp, "on_commit_value");
+			append_bool_object(tmp, "present", false);
+			break;
+	}
+	append_object_object(createStmt, "on_commit", tmp);
+
+	command = jsonize_objtree(createStmt);
+
+	free_objtree(createStmt);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * get_sequence_ownedby
+ *		Subroutine for CREATE SEQUENCE deparsing
+ *
+ * Given a sequence OID, obtain its owning column and return it as a
+ * fully-formed OWNED BY ObjTree.  If there's no owning column, an ObjTree is
+ * still returned, but it has the "present" element set to false.
+ */
+static ObjTree *
+get_sequence_ownedby(ObjTree *parent, Oid objectId)
+{
+	ObjTree    *ownedby = NULL;
+	Relation	depRel;
+	SysScanDesc scan;
+	ScanKeyData keys[3];
+	HeapTuple	tuple;
+
+	depRel = heap_open(DependRelationId, AccessShareLock);
+	ScanKeyInit(&keys[0],
+				Anum_pg_depend_classid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationRelationId));
+	ScanKeyInit(&keys[1],
+				Anum_pg_depend_objid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(objectId));
+	ScanKeyInit(&keys[2],
+				Anum_pg_depend_objsubid,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(0));
+
+	scan = systable_beginscan(depRel, DependDependerIndexId, true,
+							  NULL, 3, keys);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Oid			ownerId;
+		Form_pg_depend depform;
+		ObjTree    *tmp;
+		char	   *colname;
+
+		depform = (Form_pg_depend) GETSTRUCT(tuple);
+
+		/* only consider AUTO dependencies on pg_class */
+		if (depform->deptype != DEPENDENCY_AUTO)
+			continue;
+		if (depform->refclassid != RelationRelationId)
+			continue;
+		if (depform->refobjsubid <= 0)
+			continue;
+
+		ownerId = depform->refobjid;
+		colname = get_attname(ownerId, depform->refobjsubid);
+		if (colname == NULL)
+			continue;
+
+		tmp = new_objtree_for_qualname_id(parent, RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA(parent,
+								 "OWNED BY %{owner}D",
+								 2,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeObject, tmp);
+	}
+
+	systable_endscan(scan);
+	relation_close(depRel, AccessShareLock);
+
+	/*
+	 * If there's no owner column, emit an empty OWNED BY element, set up so
+	 * that it won't print anything.
+	 */
+	if (!ownedby)
+		ownedby = new_objtree_VA(parent,
+								 "OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+
+	return ownedby;
+}
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeqStmt;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	char	   *command;
+	Form_pg_sequence seqdata;
+	char	   *tmpstr;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeqStmt =
+		new_objtree_VA(NULL,
+					   "CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(createSeqStmt,
+								   relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createSeqStmt, "identity", tmp);
+
+	/* definition elements */
+	tmpstr = psprintf("%lu", seqdata->increment_by);
+	tmp = new_objtree_VA(createSeqStmt, "INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	elems = lappend(elems, tmp);
+
+	tmpstr = psprintf("%lu", seqdata->min_value);
+	tmp = new_objtree_VA(createSeqStmt, "MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	elems = lappend(elems, tmp);
+
+	tmpstr = psprintf("%lu", seqdata->max_value);
+	tmp = new_objtree_VA(createSeqStmt, "MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+	elems = lappend(elems, tmp);
+
+	tmpstr = psprintf("%lu", seqdata->start_value);
+	tmp = new_objtree_VA(createSeqStmt, "START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	elems = lappend(elems, tmp);
+
+	tmpstr = psprintf("%lu", seqdata->cache_value);
+	tmp = new_objtree_VA(createSeqStmt, "CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	elems = lappend(elems, tmp);
+
+	tmp = new_objtree_VA(createSeqStmt, "%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	elems = lappend(elems, tmp);
+
+	tmp = get_sequence_ownedby(createSeqStmt, objectId);
+	if (tmp)
+		elems = lappend(elems, tmp);
+
+	append_array_object(createSeqStmt, "definition", elems);
+
+	command = jsonize_objtree(createSeqStmt);
+
+	free_objtree(createSeqStmt);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static char *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	OverrideSearchPath *overridePath;
+	char	   *command;
+	char	   *index_am;
+	char	   *definition;
+	char	   *reloptions;
+	char	   *tablespace;
+	char	   *whereClause;
+
+	if (node->primary || node->isconstraint)
+	{
+		/*
+		 * indexes for PRIMARY KEY and other constraints are output
+		 * separately; return empty here.
+		 */
+		return NULL;
+	}
+
+	idxrel = relation_open(objectId, AccessShareLock);
+	heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock);
+
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	PushOverrideSearchPath(overridePath);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+	PopOverrideSearchPath();
+
+	indexStmt =
+		new_objtree_VA(NULL,
+					   "CREATE %{unique}s INDEX %{concurrently}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   5,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(indexStmt,
+								   heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA(indexStmt, "WITH (%{opts}s)", 0);
+	if (reloptions)
+		append_string_object(tmp, "opts", reloptions);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(indexStmt, "with", tmp);
+
+	/* tablespace */
+	tmp = new_objtree_VA(indexStmt, "TABLESPACE %{tablespace}s", 0);
+	if (tablespace)
+		append_string_object(tmp, "tablespace", tablespace);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(indexStmt, "tablespace", tmp);
+
+	/* WHERE clause */
+	tmp = new_objtree_VA(indexStmt, "WHERE %{where}s", 0);
+	if (whereClause)
+		append_string_object(tmp, "where", whereClause);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(indexStmt, "where_clause", tmp);
+
+	command = jsonize_objtree(indexStmt);
+	free_objtree(indexStmt);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ *
+ * Note we don't output the schema elements given in the creation command.
+ * They must be output separately.	 (In the current implementation,
+ * CreateSchemaCommand passes them back to ProcessUtility, which will lead to
+ * this file if appropriate.)
+ */
+static char *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+	char	   *command;
+
+	createSchema =
+		new_objtree_VA(NULL,
+				"CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s",
+					   2,
+					   "name", ObjTypeString, node->schemaname,
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+
+	auth = new_objtree_VA(createSchema,
+						  "AUTHORIZATION %{authorization_role}I", 0);
+	if (node->authid)
+		append_string_object(auth, "authorization_role", node->authid);
+	else
+	{
+		append_null_object(auth, "authorization_role");
+		append_bool_object(auth, "present", false);
+	}
+	append_object_object(createSchema, "authorization", auth);
+
+	command = jsonize_objtree(createSchema);
+	free_objtree(createSchema);
+
+	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.
+ *
+ * Note we currently only support commands for which ProcessUtilitySlow saves
+ * objects to create; currently this excludes all forms of ALTER and DROP.
+ */
+char *
+deparse_utility_command(Oid objectId, Node *parsetree)
+{
+	char	   *command;
+
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
+			break;
+
+		case T_CreateStmt:
+			command = deparse_CreateStmt(objectId, parsetree);
+			break;
+
+		case T_IndexStmt:
+			command = deparse_IndexStmt(objectId, parsetree);
+			break;
+
+		case T_ViewStmt:
+			command = deparse_ViewStmt(objectId, parsetree);
+			break;
+
+		case T_CreateSeqStmt:
+			command = deparse_CreateSeqStmt(objectId, parsetree);
+			break;
+
+			/* creation of objects hanging off tables */
+		case T_RuleStmt:
+		case T_CreateTrigStmt:
+			command = NULL;
+			break;
+
+			/* FDW-related objects */
+		case T_CreateForeignTableStmt:
+		case T_CreateFdwStmt:
+		case T_CreateForeignServerStmt:
+		case T_CreateUserMappingStmt:
+
+			/* other local objects */
+		case T_DefineStmt:
+		case T_CreateExtensionStmt:
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+		case T_CreateDomainStmt:
+		case T_CreateFunctionStmt:
+		case T_CreateTableAsStmt:
+		case T_CreatePLangStmt:
+		case T_CreateConversionStmt:
+		case T_CreateCastStmt:
+		case T_CreateOpClassStmt:
+		case T_CreateOpFamilyStmt:
+			command = NULL;
+			break;
+
+			/* matviews */
+		case T_RefreshMatViewStmt:
+			command = NULL;
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f4d25bd..328f4a1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -904,6 +904,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	Oid			objectId;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -922,6 +923,10 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCreatedObject
+				 * internally, for reasons explained there.
+				 */
 				break;
 
 			case T_CreateStmt:
@@ -929,7 +934,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -946,9 +950,12 @@ ProcessUtilitySlow(Node *parsetree,
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_RELATION,
-													InvalidOid);
+							objectId = DefineRelation((CreateStmt *) stmt,
+													  RELKIND_RELATION,
+													  InvalidOid);
+							EventTriggerStashCreatedObject(objectId,
+														   OCLASS_CLASS,
+														   stmt);
 
 							/*
 							 * Let AlterTableCreateToastTable decide if this
@@ -970,20 +977,27 @@ ProcessUtilitySlow(Node *parsetree,
 												   toast_options,
 												   true);
 
-							AlterTableCreateToastTable(relOid, toast_options);
+							AlterTableCreateToastTable(objectId, toast_options);
 						}
 						else if (IsA(stmt, CreateForeignTableStmt))
 						{
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_FOREIGN_TABLE,
-													InvalidOid);
+							objectId = DefineRelation((CreateStmt *) stmt,
+													  RELKIND_FOREIGN_TABLE,
+													  InvalidOid);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
-											   relOid);
+											   objectId);
+							EventTriggerStashCreatedObject(objectId,
+														   OCLASS_CLASS,
+														   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,
@@ -1110,50 +1124,66 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_DefineStmt:
 				{
 					DefineStmt *stmt = (DefineStmt *) parsetree;
+					ObjectClass	class;
 
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							objectId =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
+							class = OCLASS_PROC;
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							objectId = DefineOperator(stmt->defnames,
+													  stmt->definition);
+							class = OCLASS_OPERATOR;
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							objectId = DefineType(stmt->defnames,
+												  stmt->definition);
+							class = OCLASS_TYPE;
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							objectId = DefineTSParser(stmt->defnames,
+													  stmt->definition);
+							class = OCLASS_TSPARSER;
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							objectId = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
+							class = OCLASS_TSDICT;
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							objectId = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
+							class = OCLASS_TSTEMPLATE;
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition);
+							objectId = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition);
+							class = OCLASS_TSCONFIG;
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							objectId = DefineCollation(stmt->defnames,
+													   stmt->definition);
+							class = OCLASS_COLLATION;
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
 								 (int) stmt->kind);
 							break;
 					}
+
+					EventTriggerStashCreatedObject(objectId, class, parsetree);
 				}
 				break;
 
@@ -1171,17 +1201,20 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					objectId = DefineIndex(stmt,
+										   InvalidOid,		/* no predefined OID */
+										   false,	/* is_alter_table */
+										   true,	/* check_rights */
+										   false,	/* skip_build */
+										   false); /* quiet */
+					EventTriggerStashCreatedObject(objectId, OCLASS_CLASS,
+												   parsetree);
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
@@ -1193,7 +1226,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_FDW, parsetree);
 				break;
 
 			case T_AlterFdwStmt:
@@ -1201,7 +1235,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_FOREIGN_SERVER,
+										parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
@@ -1209,7 +1245,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_USER_MAPPING,
+										parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
@@ -1224,16 +1262,19 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					objectId = DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				objectId = DefineEnum((CreateEnumStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				objectId = DefineRange((CreateRangeStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
@@ -1241,23 +1282,27 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				objectId = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				EventTriggerStashCreatedObject(objectId, OCLASS_PROC, parsetree);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				objectId = DefineRule((RuleStmt *) parsetree, queryString);
+				EventTriggerStashCreatedObject(objectId, OCLASS_REWRITE, parsetree);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				objectId = DefineSequence((CreateSeqStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
 				break;
 
 			case T_AlterSeqStmt:
@@ -1265,42 +1310,51 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
+				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
 								   queryString, params, completionTag);
+				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
+				objectId = CreateTrigger((CreateTrigStmt *) parsetree, queryString,
 									 InvalidOid, InvalidOid, false);
+				EventTriggerStashCreatedObject(objectId, OCLASS_TRIGGER, parsetree);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_LANGUAGE, parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				objectId = DefineDomain((CreateDomainStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_CONVERSION, parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				objectId = CreateCast((CreateCastStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_CAST, parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_OPCLASS, parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				EventTriggerStashCreatedObject(objectId, OCLASS_OPFAMILY, parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 5b75d34..f160e98 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)
 {
@@ -111,72 +114,20 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
 	return format_type_internal(type_oid, typemod, true, false, false);
 }
 
+/*
+ * Formats a system type.
+ *
+ * These special cases should all correspond to special productions in gram.y,
+ * to ensure that the type name will be taken as a system type, not a user type
+ * of the same name.
+ *
+ * Returns NULL if not a system type.
+ */
 static char *
-format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid,
-					 bool force_qualify)
+format_special_type(Oid type_oid, Form_pg_type typeform,
+					int32 typemod, bool typemod_given, bool with_typemod)
 {
-	bool		with_typemod = typemod_given && (typemod >= 0);
-	HeapTuple	tuple;
-	Form_pg_type typeform;
-	Oid			array_base_type;
-	bool		is_array;
-	char	   *buf;
-
-	if (type_oid == InvalidOid && allow_invalid)
-		return pstrdup("-");
-
-	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
-	if (!HeapTupleIsValid(tuple))
-	{
-		if (allow_invalid)
-			return pstrdup("???");
-		else
-			elog(ERROR, "cache lookup failed for type %u", type_oid);
-	}
-	typeform = (Form_pg_type) GETSTRUCT(tuple);
-
-	/*
-	 * Check if it's a regular (variable length) array type.  Fixed-length
-	 * array types such as "name" shouldn't get deconstructed.  As of Postgres
-	 * 8.1, rather than checking typlen we check the toast property, and don't
-	 * deconstruct "plain storage" array types --- this is because we don't
-	 * want to show oidvector as oid[].
-	 */
-	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))
-		{
-			if (allow_invalid)
-				return pstrdup("???[]");
-			else
-				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;
-
-	/*
-	 * See if we want to special-case the output for certain built-in types.
-	 * Note that these special cases should all correspond to special
-	 * productions in gram.y, to ensure that the type name will be taken as a
-	 * system type, not a user type of the same name.
-	 *
-	 * If we do not provide a special-case output here, the type name will be
-	 * handled the same way as a user type name --- in particular, it will be
-	 * double-quoted if it matches any lexer keyword.  This behavior is
-	 * essential for some cases, such as types "bit" and "char".
-	 */
-	buf = NULL;					/* flag for no special case */
+	char   *buf = NULL;
 
 	switch (type_oid)
 	{
@@ -285,12 +236,90 @@ format_type_internal(Oid type_oid, int32 typemod,
 
 		case VARCHAROID:
 			if (with_typemod)
-				buf = printTypmod("character varying", typemod, typeform->typmodout);
+				buf = printTypmod("character varying", typemod,
+								  typeform->typmodout);
 			else
 				buf = pstrdup("character varying");
 			break;
 	}
 
+	return buf;
+}
+
+/*
+ * Return a formatted typename.
+ *
+ * If qualify is Auto, the type name is qualified if necessary, which means
+ * qualify when not visible to search_path.  If qualify is Always, it is
+ * qualified regardless of visibility.
+ */
+static char *
+format_type_internal(Oid type_oid, int32 typemod,
+					 bool typemod_given, bool allow_invalid,
+					 bool force_qualify)
+{
+	bool		with_typemod = typemod_given && (typemod >= 0);
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+	bool		is_array;
+	char	   *buf;
+
+	if (type_oid == InvalidOid && allow_invalid)
+		return pstrdup("-");
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+	{
+		if (allow_invalid)
+			return pstrdup("???");
+		else
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+	}
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Check if it's a regular (variable length) array type.  Fixed-length
+	 * array types such as "name" shouldn't get deconstructed.  As of Postgres
+	 * 8.1, rather than checking typlen we check the toast property, and don't
+	 * deconstruct "plain storage" array types --- this is because we don't
+	 * want to show oidvector as oid[].
+	 */
+	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))
+		{
+			if (allow_invalid)
+				return pstrdup("???[]");
+			else
+				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;
+
+	/*
+	 * See if we want to special-case the output for certain built-in types.
+	 *
+	 * If we do not provide a special-case output here, the type name will be
+	 * handled the same way as a user type name --- in particular, it will be
+	 * double-quoted if it matches any lexer keyword.  This behavior is
+	 * essential for some cases, such as types "bit" and "char".
+	 */
+	buf = NULL;					/* flag for no special case */
+
+	buf = format_special_type(type_oid, typeform,
+							  typemod, typemod_given, with_typemod);
+
 	if (buf == NULL)
 	{
 		/*
@@ -323,6 +352,73 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspname is the schema name, without quotes.  This is NULL if the
+ *   type is a system type.
+ *
+ * - 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-mandate names.
+ *
+ * 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,
+					 char **nspname, 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);
+
+	/*
+	 * 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;
+
+	*nspname = get_namespace_name(typeform->typnamespace);
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod > 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");	/* XXX ?? */
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +434,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,13 +446,15 @@ 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;
 }
 
-
 /*
  * type_maximum_size --- determine maximum width of a variable-width column
  *
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 7c07554..11b1e00 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -226,13 +226,13 @@ typedef struct PopulateRecordsetState
 } PopulateRecordsetState;
 
 /*
- * SQL function json_object-keys
+ * SQL function json_object_keys
  *
  * Returns the set of keys for the object argument.
  *
  * This SRF operates in value-per-call mode. It processes the
  * object during the first call, and the keys are simply stashed
- * in an array, whise size is expanded as necessary. This is probably
+ * in an array, whose size is expanded as necessary. This is probably
  * safe enough for a list of keys of a single object, since they are
  * limited in size to NAMEDATALEN and the number of keys is unlikely to
  * be so huge that it has major memory implications.
@@ -972,7 +972,7 @@ each_worker(PG_FUNCTION_ARGS, bool as_text)
 
 	pg_parse_json(lex, sem);
 
-	MemoryContextDelete(state->tmp_cxt); 
+	MemoryContextDelete(state->tmp_cxt);
 
 	rsi->setResult = state->tuple_store;
 	rsi->setDesc = state->ret_tdesc;
@@ -1156,7 +1156,7 @@ elements_worker(PG_FUNCTION_ARGS, bool as_text)
 
 	pg_parse_json(lex, sem);
 
-	MemoryContextDelete(state->tmp_cxt); 
+	MemoryContextDelete(state->tmp_cxt);
 
 	rsi->setResult = state->tuple_store;
 	rsi->setDesc = state->ret_tdesc;
@@ -1193,7 +1193,7 @@ elements_array_element_end(void *state, bool isnull)
 	text	   *val;
 	HeapTuple	tuple;
 	Datum		values[1];
-	bool nulls[1] = {false};
+	bool		nulls[1] = {false};
 
 	/* skip over nested objects */
 	if (_state->lex->lex_level != 1)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index add5cd1..fafe3bf 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -587,6 +587,13 @@ pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
 }
 
+char *
+pg_get_viewdef_internal(Oid viewoid)
+{
+	return pg_get_viewdef_worker(viewoid, 0, WRAP_COLUMN_DEFAULT);
+}
+
+
 /*
  * Common code for by-OID and by-name variants of pg_get_viewdef
  */
@@ -966,6 +973,8 @@ pg_get_indexdef_columns(Oid indexrelid, bool pretty)
  *
  * This is now used for exclusion constraints as well: if excludeOps is not
  * NULL then it points to an array of exclusion operator OIDs.
+ *
+ * XXX if you change this function, see pg_get_indexdef_detailed too.
  */
 static char *
 pg_get_indexdef_worker(Oid indexrelid, int colno,
@@ -1144,7 +1153,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 
 			/* Add collation, if not default for column */
 			indcoll = indcollation->values[keyno];
-			if (OidIsValid(indcoll) && indcoll != keycolcollation)
+			if (OidIsValid(indcoll))
 				appendStringInfo(&buf, " COLLATE %s",
 								 generate_collation_name((indcoll)));
 
@@ -1245,6 +1254,245 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * Return an index definition, split in several pieces.
+ *
+ * There is a huge lot of code that's a dupe of pg_get_indexdef_worker, but
+ * control flow is different enough that it doesn't seem worth keeping them
+ * together.
+ */
+void
+pg_get_indexdef_detailed(Oid indexrelid,
+						 char **index_am,
+						 char **definition,
+						 char **reloptions,
+						 char **tablespace,
+						 char **whereClause)
+{
+	HeapTuple	ht_idx;
+	HeapTuple	ht_idxrel;
+	HeapTuple	ht_am;
+	Form_pg_index idxrec;
+	Form_pg_class idxrelrec;
+	Form_pg_am	amrec;
+	List	   *indexprs;
+	ListCell   *indexpr_item;
+	List	   *context;
+	Oid			indrelid;
+	int			keyno;
+	Datum		indcollDatum;
+	Datum		indclassDatum;
+	Datum		indoptionDatum;
+	bool		isnull;
+	oidvector  *indcollation;
+	oidvector  *indclass;
+	int2vector *indoption;
+	StringInfoData definitionBuf;
+	char	   *sep;
+
+	/*
+	 * Fetch the pg_index tuple by the Oid of the index
+	 */
+	ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
+	if (!HeapTupleIsValid(ht_idx))
+		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+
+	indrelid = idxrec->indrelid;
+	Assert(indexrelid == idxrec->indexrelid);
+
+	/* Must get indcollation, indclass, and indoption the hard way */
+	indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+								   Anum_pg_index_indcollation, &isnull);
+	Assert(!isnull);
+	indcollation = (oidvector *) DatumGetPointer(indcollDatum);
+
+	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									Anum_pg_index_indclass, &isnull);
+	Assert(!isnull);
+	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+
+	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									 Anum_pg_index_indoption, &isnull);
+	Assert(!isnull);
+	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+
+	/*
+	 * Fetch the pg_class tuple of the index relation
+	 */
+	ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
+	if (!HeapTupleIsValid(ht_idxrel))
+		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+
+	/*
+	 * Fetch the pg_am tuple of the index' access method
+	 */
+	ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
+	if (!HeapTupleIsValid(ht_am))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 idxrelrec->relam);
+	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+
+	/*
+	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions and predicate, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									 Anum_pg_index_indexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		indexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		indexprs = NIL;
+
+	indexpr_item = list_head(indexprs);
+
+	context = deparse_context_for(get_relation_name(indrelid), indrelid);
+
+	initStringInfo(&definitionBuf);
+
+	/* output index AM */
+	*index_am = pstrdup(quote_identifier(NameStr(amrec->amname)));
+
+	/*
+	 * Output index definition.  Note the outer parens must be supplied by
+	 * caller.
+	 */
+	sep = "";
+	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+	{
+		AttrNumber	attnum = idxrec->indkey.values[keyno];
+		int16		opt = indoption->values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			indcoll;
+
+		appendStringInfoString(&definitionBuf, sep);
+		sep = ", ";
+
+		if (attnum != 0)
+		{
+			/* Simple index column */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(indrelid, attnum);
+			appendStringInfoString(&definitionBuf, quote_identifier(attname));
+			get_atttypetypmodcoll(indrelid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* expressional index */
+			Node	   *indexkey;
+			char	   *str;
+
+			if (indexpr_item == NULL)
+				elog(ERROR, "too few entries in indexprs list");
+			indexkey = (Node *) lfirst(indexpr_item);
+			indexpr_item = lnext(indexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(indexkey, context, false, false,
+											0, 0);
+
+			/* Need parens if it's not a bare function call */
+			if (indexkey && IsA(indexkey, FuncExpr) &&
+				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+				appendStringInfoString(&definitionBuf, str);
+			else
+				appendStringInfo(&definitionBuf, "(%s)", str);
+
+			keycoltype = exprType(indexkey);
+			keycolcollation = exprCollation(indexkey);
+		}
+
+		/* Add collation, even if default */
+		indcoll = indcollation->values[keyno];
+		if (OidIsValid(indcoll))
+			appendStringInfo(&definitionBuf, " COLLATE %s",
+							 generate_collation_name((indcoll)));
+
+		/* Add the operator class name, even if default */
+		get_opclass_name(indclass->values[keyno], InvalidOid, &definitionBuf);
+
+		/* Add options if relevant */
+		if (amrec->amcanorder)
+		{
+			/* if it supports sort ordering, report DESC and NULLS opts */
+			if (opt & INDOPTION_DESC)
+			{
+				appendStringInfoString(&definitionBuf, " DESC");
+				/* NULLS FIRST is the default in this case */
+				if (!(opt & INDOPTION_NULLS_FIRST))
+					appendStringInfoString(&definitionBuf, " NULLS LAST");
+			}
+			else
+			{
+				if (opt & INDOPTION_NULLS_FIRST)
+					appendStringInfoString(&definitionBuf, " NULLS FIRST");
+			}
+		}
+
+		/* XXX excludeOps thingy was here; do we need anything? */
+	}
+	*definition = definitionBuf.data;
+
+	/* output reloptions */
+	*reloptions = flatten_reloptions(indexrelid);
+
+	/* output tablespace */
+	{
+		Oid			tblspc;
+
+		tblspc = get_rel_tablespace(indexrelid);
+		if (OidIsValid(tblspc))
+			*tablespace = pstrdup(quote_identifier(get_tablespace_name(tblspc)));
+		else
+			*tablespace = NULL;
+	}
+
+	/* report index predicate, if any */
+	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+	{
+		Node	   *node;
+		Datum		predDatum;
+		bool		isnull;
+		char	   *predString;
+
+		/* Convert text string to node tree */
+		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									Anum_pg_index_indpred, &isnull);
+		Assert(!isnull);
+		predString = TextDatumGetCString(predDatum);
+		node = (Node *) stringToNode(predString);
+		pfree(predString);
+
+		/* Deparse */
+		*whereClause =
+			deparse_expression_pretty(node, context, false, false,
+									  0, 0);
+	}
+	else
+		*whereClause = NULL;
+
+	/* Clean up */
+	ReleaseSysCache(ht_idx);
+	ReleaseSysCache(ht_idxrel);
+	ReleaseSysCache(ht_am);
+
+	/* all done */
+}
 
 /*
  * pg_get_constraintdef
@@ -1279,9 +1527,9 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
 
 /* Internal version that returns a palloc'd C string; no pretty-printing */
 char *
-pg_get_constraintdef_string(Oid constraintId)
+pg_get_constraintdef_string(Oid constraintId, bool fullCommand)
 {
-	return pg_get_constraintdef_worker(constraintId, true, 0);
+	return pg_get_constraintdef_worker(constraintId, fullCommand, 0);
 }
 
 static char *
@@ -2368,8 +2616,10 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
 
 	proc = (Form_pg_proc) GETSTRUCT(proctup);
 
-	/* Calculate index into proargdefaults: proargdefaults corresponds to the
-	 * last N input arguments, where N = pronargdefaults. */
+	/*
+	 * Calculate index into proargdefaults: proargdefaults corresponds to the
+	 * last N input arguments, where N = pronargdefaults.
+	 */
 	nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults);
 
 	if (nth_default < 0 || nth_default >= list_length(argdefaults))
@@ -4156,6 +4406,20 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
 	}
 }
 
+char *
+pg_get_viewstmt_definition(Query *viewParse)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(viewParse, &buf, NIL, NULL, 0,
+				  WRAP_COLUMN_DEFAULT, 1);
+
+	return buf.data;
+}
+
+
 /* ----------
  * get_values_def			- Parse back a VALUES list
  * ----------
@@ -9096,3 +9360,19 @@ flatten_reloptions(Oid relid)
 
 	return result;
 }
+
+extern char *relation_get_column_default(Relation rel, AttrNumber attno,
+							List *dpcontext);
+
+char *
+relation_get_column_default(Relation rel, AttrNumber attno, List *dpcontext)
+{
+	Node *defval;
+	char *defstr;
+
+	defval = build_column_default(rel, attno);
+	defstr = deparse_expression_pretty(defval, dpcontext, false, false,
+									   0, 0);
+
+	return defstr;
+}
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8948589..41660be 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -176,6 +176,8 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
+extern Oid get_class_catalog(ObjectClass oclass);
+
 extern ObjectAddresses *new_object_addresses(void);
 
 extern void add_exact_object_address(const ObjectAddress *object,
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e6713a6..66ab6f1 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4793,6 +4793,10 @@ DESCR("information about replication slots currently in use");
 /* 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 = 3567 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{25,114}" "{o,o}" "{identity,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list objects created by the current command");
+DATA(insert OID = 3568 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 1 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 message");
 
 /* 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/alter_table.h b/src/include/commands/alter_table.h
new file mode 100644
index 0000000..0974098
--- /dev/null
+++ b/src/include/commands/alter_table.h
@@ -0,0 +1,53 @@
+#ifndef ALTER_TABLE_H
+#define ALTER_TABLE_H
+
+/*
+ * State information for ALTER TABLE
+ *
+ * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
+ * structs, one for each table modified by the operation (the named table
+ * plus any child tables that are affected).  We save lists of subcommands
+ * to apply to this table (possibly modified by parse transformation steps);
+ * these lists will be executed in Phase 2.  If a Phase 3 step is needed,
+ * necessary information is stored in the constraints and newvals lists.
+ *
+ * Phase 2 is divided into multiple passes; subcommands are executed in
+ * a pass determined by subcommand type.
+ */
+
+#define AT_PASS_UNSET			-1		/* UNSET will cause ERROR */
+#define AT_PASS_DROP			0		/* DROP (all flavors) */
+#define AT_PASS_ALTER_TYPE		1		/* ALTER COLUMN TYPE */
+#define AT_PASS_OLD_INDEX		2		/* re-add existing indexes */
+#define AT_PASS_OLD_CONSTR		3		/* re-add existing constraints */
+#define AT_PASS_COL_ATTRS		4		/* set other column attributes */
+/* We could support a RENAME COLUMN pass here, but not currently used */
+#define AT_PASS_ADD_COL			5		/* ADD COLUMN */
+#define AT_PASS_ADD_INDEX		6		/* ADD indexes */
+#define AT_PASS_ADD_CONSTR		7		/* ADD constraints, defaults */
+#define AT_PASS_MISC			8		/* other stuff */
+#define AT_NUM_PASSES			9
+
+typedef struct AlteredTableInfo
+{
+	/* Information saved before any work commences: */
+	Oid			relid;			/* Relation to work on */
+	char		relkind;		/* Its relkind */
+	TupleDesc	oldDesc;		/* Pre-modification tuple descriptor */
+	/* Information saved by Phase 1 for Phase 2: */
+	List	   *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
+	/* Information saved by Phases 1/2 for Phase 3: */
+	List	   *constraints;	/* List of NewConstraint */
+	List	   *newvals;		/* List of NewColumnValue */
+	bool		new_notnull;	/* T if we added new NOT NULL constraints */
+	bool		rewrite;		/* T if a rewrite is forced */
+	Oid			newTableSpace;	/* new tablespace; 0 means no change */
+	/* Objects to rebuild after completing ALTER TYPE operations */
+	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
+	List	   *changedConstraintDefs;	/* string definitions of same */
+	List	   *changedIndexOids;		/* OIDs of indexes to rebuild */
+	List	   *changedIndexDefs;		/* string definitions of same */
+} AlteredTableInfo;
+
+
+#endif	/* ALTER_TABLE_H */
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index c17d829..477339d 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -19,7 +19,7 @@
 #include "tcop/dest.h"
 
 
-extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+extern Oid	ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 0233f4c..3505e2b 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -49,6 +49,8 @@ extern void EventTriggerSQLDrop(Node *parsetree);
 
 extern bool EventTriggerBeginCompleteQuery(void);
 extern void EventTriggerEndCompleteQuery(void);
+extern void EventTriggerStashCreatedObject(Oid objectId, Oid classId,
+							   Node *parsetree);
 extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index 476b285..2c468b2 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -22,7 +22,7 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
-extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
+extern Oid ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag);
 
 extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7d8a370..b7a5db4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -70,6 +70,7 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS);
 extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
+extern Form_pg_sequence get_sequence_values(Oid sequenceId);
 
 extern Oid	DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..7e63f52
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,17 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+extern char *deparse_utility_command(Oid objectId, Node *parsetree);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..9b6d31f 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -663,15 +663,25 @@ extern Datum pg_get_viewdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
+extern char *pg_get_viewdef_internal(Oid viewoid);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern char *pg_get_indexdef_string(Oid indexrelid);
 extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+extern char *pg_get_indexdef_internal(Oid indexrelid);
+extern void pg_get_indexdef_detailed(Oid indexrelid,
+						 char **index_am,
+						 char **definition,
+						 char **reloptions,
+						 char **tablespace,
+						 char **whereClause);
+
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_constraintdef_string(Oid constraintId);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern char *pg_get_viewstmt_definition(Query *viewParse);
 extern Datum pg_get_expr(PG_FUNCTION_ARGS);
 extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
@@ -1057,6 +1067,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,
+					 char **nspname, char **typname,
+					 char **typemodstr, bool *is_array);
 
 /* quote.c */
 extern Datum quote_ident(PG_FUNCTION_ARGS);
@@ -1177,6 +1190,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);
