*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 2014,2019 **** add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
--- 2014,2026 ----
  	addrs->numrefs++;
  }
  
+ Oid
+ get_class_catalog(ObjectClass oclass)
+ {
+ 	Assert(oclass < MAX_OCLASS);
+ 	return object_classes[oclass];
+ }
+ 
  /*
   * Add an entry to an ObjectAddresses array.
   *
*** a/src/backend/commands/createas.c
--- b/src/backend/commands/createas.c
***************
*** 55,60 **** typedef struct
--- 55,63 ----
  	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,70 **** static void intorel_destroy(DestReceiver *self);
  /*
   * ExecCreateTableAs -- execute a CREATE TABLE AS command
   */
! void
  ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  				  ParamListInfo params, char *completionTag)
  {
--- 67,73 ----
  /*
   * ExecCreateTableAs -- execute a CREATE TABLE AS command
   */
! Oid
  ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  				  ParamListInfo params, char *completionTag)
  {
***************
*** 75,80 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
--- 78,84 ----
  	Oid			save_userid = InvalidOid;
  	int			save_sec_context = 0;
  	int			save_nestlevel = 0;
+ 	Oid			relOid;
  	List	   *rewritten;
  	PlannedStmt *plan;
  	QueryDesc  *queryDesc;
***************
*** 98,104 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  		Assert(!is_matview);	/* excluded by syntax */
  		ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
  
! 		return;
  	}
  	Assert(query->commandType == CMD_SELECT);
  
--- 102,110 ----
  		Assert(!is_matview);	/* excluded by syntax */
  		ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
  
! 		relOid = CreateAsRelid;
! 		CreateAsRelid = InvalidOid;
! 		return relOid;
  	}
  	Assert(query->commandType == CMD_SELECT);
  
***************
*** 190,195 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
--- 196,206 ----
  		/* Restore userid and security context */
  		SetUserIdAndSecContext(save_userid, save_sec_context);
  	}
+ 
+ 	relOid = CreateAsRelid;
+ 	CreateAsRelid = InvalidOid;
+ 
+ 	return relOid;
  }
  
  /*
***************
*** 421,426 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
--- 432,440 ----
  	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.
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 31,40 ****
--- 31,42 ----
  #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,53 **** typedef struct EventTriggerQueryState
--- 50,56 ----
  	slist_head	SQLDropList;
  	bool		in_sql_drop;
  	MemoryContext cxt;
+ 	List	   *stash;
  	struct EventTriggerQueryState *previous;
  } EventTriggerQueryState;
  
***************
*** 491,497 **** AlterEventTriggerOwner(const char *name, Oid newOwnerId)
  }
  
  /*
!  * Change extension owner, by OID
   */
  void
  AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId)
--- 494,500 ----
  }
  
  /*
!  * Change event trigger owner, by OID
   */
  void
  AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId)
***************
*** 1022,1034 **** 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,
--- 1025,1030 ----
***************
*** 1036,1043 **** EventTriggerBeginCompleteQuery(void)
  								ALLOCSET_DEFAULT_MAXSIZE);
  	state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));
  	state->cxt = cxt;
! 	slist_init(&(state->SQLDropList));
  	state->in_sql_drop = false;
  
  	state->previous = currentEventTriggerState;
  	currentEventTriggerState = state;
--- 1032,1041 ----
  								ALLOCSET_DEFAULT_MAXSIZE);
  	state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));
  	state->cxt = cxt;
! 	if (trackDroppedObjectsNeeded())
! 		slist_init(&(state->SQLDropList));
  	state->in_sql_drop = false;
+ 	state->stash = NIL;
  
  	state->previous = currentEventTriggerState;
  	currentEventTriggerState = state;
***************
*** 1294,1296 **** pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
--- 1292,2048 ----
  
  	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_event_trigger_normalized_command")));
+ 
+ 	/* 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;
+ 
+ 		deparse_utility_command(obj->objectId, obj->parsetree,
+ 								&command);
+ 
+ 		/*
+ 		 * 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,
+ 	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, and value_len,
+  * if not NULL, is set to its length.
+  */
+ static char *
+ _expand_get_strval(Datum json, char *field_name, int *value_len)
+ {
+ 	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);
+ 	if (value_len)
+ 		*value_len = VARSIZE_ANY_EXHDR(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 "schema" (optional) and "relation" (mandatory).
+  *
+  * XXX maybe "relation" should be called something else, as this is used for
+  * qualified names of any kind, not just relations.
+  */
+ static void
+ expand_jsonval_dottedname(StringInfo buf, Datum jsonval)
+ {
+ 	char *schema;
+ 	char *relation;
+ 	const char *qschema;
+ 	const char *qrel;
+ 
+ 	schema = _expand_get_strval(jsonval, "schema", NULL);
+ 	relation = _expand_get_strval(jsonval, "relation", NULL);
+ 	if (relation == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("invalid NULL relation name in %%D element")));
+ 
+ 	qrel = quote_identifier(relation);
+ 	if (schema == NULL)
+ 	{
+ 		appendStringInfo(buf, "%s", qrel);
+ 	}
+ 	else
+ 	{
+ 		qschema = quote_identifier(schema);
+ 		appendStringInfo(buf, "%s.%s",
+ 						 qschema, qrel);
+ 		if (qschema != schema)
+ 			pfree((char *) qschema);
+ 		pfree(schema);
+ 	}
+ 
+ 	if (qrel != relation)
+ 		pfree((char *) qrel);
+ 	pfree(relation);
+ }
+ 
+ /*
+  * 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	system_isnull;
+ 	bool	is_system;
+ 	bool	is_array;
+ 
+ 	typename = _expand_get_strval(jsonval, "typename", NULL);
+ 	if (typename == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("invalid NULL type name in %%T element")));
+ 	typmodstr = _expand_get_strval(jsonval, "typmod", NULL);	/* OK if null */
+ 	is_system = _expand_get_boolval(jsonval, "is_system", &system_isnull);
+ 	if (system_isnull)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("invalid NULL is_system flag in %%T element")));
+ 
+ 	is_array = _expand_get_boolval(jsonval, "is_array", &array_isnull);
+ 
+ 	/*
+ 	 * When a system type, must not schema-qualify and must not quote the name
+ 	 * either.
+ 	 */
+ 	if (!is_system)
+ 		schema = _expand_get_strval(jsonval, "schema", NULL);
+ 
+ 	/*
+ 	 * schema might be NULL here either because it's a system type, or because
+ 	 * the data specifies no schema value.
+ 	 */
+ 	if (schema == NULL || schema[0] == '\0')
+ 	{
+ 		appendStringInfo(buf, "%s%s%s",
+ 						 typename,
+ 						 typmodstr ? typmodstr : "",
+ 						 is_array ? "[]" : "");
+ 	}
+ 	else
+ 	{
+ 		appendStringInfo(buf, "%s.%s%s%s",
+ 						 quote_identifier(schema),
+ 						 quote_identifier(typename),
+ 						 typmodstr ? typmodstr : "",
+ 						 is_array ? "[]" : "");
+ 	}
+ }
+ 
+ /*
+  * Expand a json value as 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 == 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;
+ 	}
+ }
+ 
+ /*
+  * 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
+  * 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",
+ 								 &fmt_len);
+ 
+ 	if (fmt_str == NULL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("invalid NULL format string")));
+ 
+ 	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;
+ 			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));
+ }
*** a/src/backend/commands/matview.c
--- b/src/backend/commands/matview.c
***************
*** 132,138 **** 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
  ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  				   ParamListInfo params, char *completionTag)
  {
--- 132,138 ----
   * The matview's "populated" state is changed based on whether the contents
   * reflect the result set of the materialized view's query.
   */
! Oid
  ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  				   ParamListInfo params, char *completionTag)
  {
***************
*** 274,279 **** ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
--- 274,281 ----
  	}
  	else
  		refresh_by_heap_swap(matviewOid, OIDNewHeap);
+ 
+ 	return matviewOid;
  }
  
  /*
*** a/src/backend/commands/schemacmds.c
--- b/src/backend/commands/schemacmds.c
***************
*** 24,29 ****
--- 24,30 ----
  #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,135 **** CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
--- 131,145 ----
  	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 ---
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 43,48 ****
--- 43,49 ----
  #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,165 **** 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
--- 113,118 ----
*** a/src/backend/tcop/Makefile
--- b/src/backend/tcop/Makefile
***************
*** 12,18 **** subdir = src/backend/tcop
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS= dest.o fastpath.o postgres.o pquery.o utility.o
  
  ifneq (,$(filter $(PORTNAME),cygwin win32))
  override CPPFLAGS += -DWIN32_STACK_RLIMIT=$(WIN32_STACK_RLIMIT)
--- 12,18 ----
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
! 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)
*** /dev/null
--- b/src/backend/tcop/deparse_utility.c
***************
*** 0 ****
--- 1,1030 ----
+ /*-------------------------------------------------------------------------
+  *
+  * deparse_utility.c
+  *	  Functions to convert utility commands back to command strings
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * 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_class.h"
+ #include "catalog/pg_inherits.h"
+ #include "catalog/pg_proc.h"
+ #include "catalog/pg_type.h"
+ #include "commands/defrem.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 "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
+ {
+ 	ParamTypeNull,
+ 	ParamTypeBoolean,
+ 	ParamTypeString,
+ 	ParamTypeArray,
+ 	ParamTypeObject
+ } ParamType;
+ 
+ typedef struct deparseParam
+ {
+ 	char   *name;
+ 	ParamType type;
+ 	bool	bool_value;
+ 	char   *str_value;
+ 	struct deparseParams *obj_value;
+ 	List   *array_value;
+ 	slist_node node;
+ } deparseParam;
+ 
+ typedef struct deparseParams
+ {
+ 	MemoryContext cxt;
+ 	slist_head	params;
+ 	int			numParams;
+ } deparseParams;
+ 
+ /*
+  * Allocate a new object to store parameters.  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 deparseParams *
+ setup_params(deparseParams *parent)
+ {
+ 	MemoryContext	cxt;
+ 	deparseParams  *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(deparseParams));
+ 	params->cxt = cxt;
+ 	params->numParams = 0;
+ 	slist_init(&params->params);
+ 
+ 	return params;
+ }
+ 
+ /*
+  * Add a new parameter with a NULL value
+  */
+ static void
+ append_null_param(deparseParams *params, char *name)
+ {
+ 	deparseParam	*param;
+ 
+ 	param = MemoryContextAllocZero(params->cxt, sizeof(deparseParam));
+ 
+ 	param->name = MemoryContextStrdup(params->cxt, name);
+ 	param->type = ParamTypeNull;
+ 
+ 	slist_push_head(&params->params, &param->node);
+ 	params->numParams++;
+ }
+ 
+ /*
+  * Add a new boolean parameter
+  */
+ static void
+ append_boolean_param(deparseParams *params, char *name, bool value)
+ {
+ 	deparseParam	*param;
+ 
+ 	param = MemoryContextAllocZero(params->cxt, sizeof(deparseParam));
+ 
+ 	param->name = MemoryContextStrdup(params->cxt, name);
+ 	param->type = ParamTypeBoolean;
+ 	param->bool_value = value;
+ 
+ 	slist_push_head(&params->params, &param->node);
+ 	params->numParams++;
+ }
+ 
+ /*
+  * Add a new string parameter.
+  */
+ static void
+ append_string_param(deparseParams *params, char *name, char *value)
+ {
+ 	deparseParam	*param;
+ 
+ 	param = MemoryContextAllocZero(params->cxt, sizeof(deparseParam));
+ 
+ 	param->name = MemoryContextStrdup(params->cxt, name);
+ 	param->type = ParamTypeString;
+ 	param->str_value = MemoryContextStrdup(params->cxt, value);
+ 
+ 	slist_push_head(&params->params, &param->node);
+ 	params->numParams++;
+ }
+ 
+ /*
+  * Add a new JSON parameter
+  */
+ static void
+ append_object_param(deparseParams *params, char *name, deparseParams *value)
+ {
+ 	deparseParam	*param;
+ 
+ 	param = MemoryContextAllocZero(params->cxt, sizeof(deparseParam));
+ 
+ 	param->name = MemoryContextStrdup(params->cxt, name);
+ 	param->type = ParamTypeObject;
+ 	param->obj_value = value;	/* XXX not duped */
+ 
+ 	slist_push_head(&params->params, &param->node);
+ 	params->numParams++;
+ }
+ 
+ /*
+  * add a new array parameter
+  */
+ static void
+ append_array_param(deparseParams *params, char *name, List *array)
+ {
+ 	deparseParam   *param;
+ 
+ 	param = MemoryContextAllocZero(params->cxt, sizeof(deparseParam));
+ 
+ 	param->name = MemoryContextStrdup(params->cxt, name);
+ 	param->type = ParamTypeArray;
+ 	param->array_value = array;	/* XXX not duped */
+ 
+ 	slist_push_head(&params->params, &param->node);
+ 	params->numParams++;
+ }
+ 
+ /*
+  * Release all memory used by parameters and their expansion
+  */
+ static void
+ free_params(deparseParams *params)
+ {
+ 	MemoryContextDelete(params->cxt);
+ }
+ 
+ /*
+  * Create a true JSON object from our ad-hoc representation.
+  *
+  * Note this allocates memory in params->cxt.  We don't need a separate memory
+  * context, and the one provided by the params object has the right lifetime.
+  */
+ static char *
+ finalize_params(deparseParams *params)
+ {
+ 	TupleDesc	tupdesc;
+ 	MemoryContext oldcxt;
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	HeapTuple	htup;
+ 	int			i;
+ 	Datum		json;
+ 	char	   *jsonstr;
+ 	slist_iter	iter;
+ 
+ 	oldcxt = MemoryContextSwitchTo(params->cxt);
+ 	tupdesc = CreateTemplateTupleDesc(params->numParams, false);
+ 	values = palloc(sizeof(Datum) * params->numParams);
+ 	nulls = palloc(sizeof(bool) * params->numParams);
+ 
+ 	i = 1;
+ 	slist_foreach(iter, &params->params)
+ 	{
+ 		deparseParam *param = slist_container(deparseParam, node, iter.cur);
+ 		Oid		typeid;
+ 
+ 		switch (param->type)
+ 		{
+ 			case ParamTypeNull:
+ 			case ParamTypeString:
+ 				typeid = TEXTOID;
+ 				break;
+ 			case ParamTypeBoolean:
+ 				typeid = BOOLOID;
+ 				break;
+ 			case ParamTypeArray:
+ 			case ParamTypeObject:
+ 				typeid = JSONOID;
+ 				break;
+ 			default:
+ 				elog(ERROR, "unable to determine type id");
+ 				typeid = InvalidOid;
+ 		}
+ 
+ 		TupleDescInitEntry(tupdesc, i, param->name, typeid, -1, 0);
+ 
+ 		nulls[i - 1] = false;
+ 		switch (param->type)
+ 		{
+ 			case ParamTypeNull:
+ 				nulls[i - 1] = true;
+ 				break;
+ 			case ParamTypeBoolean:
+ 				values[i - 1] = BoolGetDatum(param->bool_value);
+ 				break;
+ 			case ParamTypeString:
+ 				values[i - 1] = CStringGetTextDatum(param->str_value);
+ 				break;
+ 			case ParamTypeArray:
+ 				{
+ 					ArrayType  *arrayt;
+ 					Datum	   *arrvals;
+ 					Datum		jsonary;
+ 					ListCell   *cell;
+ 					int			length = list_length(param->array_value);
+ 					int			j;
+ 
+ 					/*
+ 					 * Arrays are stored as Lists up to this point, with each
+ 					 * element being a deparseParam; 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, param->array_value)
+ 					{
+ 						deparseParams *json = lfirst(cell);
+ 
+ 						arrvals[j++] =
+ 							CStringGetTextDatum(finalize_params(json));
+ 					}
+ 					arrayt = construct_array(arrvals, length,
+ 											 JSONOID, -1, false, 'i');
+ 
+ 					jsonary = DirectFunctionCall1(array_to_json,
+ 												  (PointerGetDatum(arrayt)));
+ 
+ 					values[i - 1] = jsonary;
+ 				}
+ 				break;
+ 			case ParamTypeObject:
+ 				values[i - 1] =
+ 					CStringGetTextDatum(finalize_params(param->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;
+ }
+ 
+ /*
+  * A helper routine to be used to setup %{}T elements.
+  */
+ static deparseParams *
+ setup_params_for_type(deparseParams *parent, Oid typeId, int32 typmod)
+ {
+ 	deparseParams *typeParam = setup_params(parent);
+ 	bool	is_system;
+ 	char   *typnsp;
+ 	char   *typename;
+ 	char   *typmodstr;
+ 	bool	is_array;
+ 
+ 	format_type_detailed(typeId, typmod,
+ 						 &is_system, &typnsp, &typename, &typmodstr, &is_array);
+ 
+ 	append_boolean_param(typeParam, "is_array", is_array);
+ 	append_boolean_param(typeParam, "is_system", is_system);
+ 
+ 	append_string_param(typeParam, "typename", typename);
+ 	if (!is_system)
+ 		append_string_param(typeParam, "schema", typnsp);
+ 	append_string_param(typeParam, "typmod", typmodstr);
+ 
+ 	return typeParam;
+ }
+ 
+ /*
+  * A helper routine to be used to setup %{}D elements.
+  */
+ static deparseParams *
+ setup_params_for_qualname(deparseParams *parent, Oid nspid, char *name)
+ {
+ 	deparseParams *qualified = setup_params(parent);
+ 	char   *namespace;
+ 
+ 	namespace = get_namespace_name(nspid);
+ 	append_string_param(qualified, "schema", namespace);
+ 	append_string_param(qualified, "relation", name);
+ 
+ 	pfree(namespace);
+ 
+ 	return qualified;
+ }
+ 
+ /*
+  * Given a raw expression used for a DEFAULT or a CHECK constraint, turn it
+  * back into source code.
+  *
+  * Note we don't apply sanity checks such as on the return type of the
+  * expression; since the expression was already run through the regular
+  * code paths, we assume it's correct.
+  */
+ static char *
+ uncookConstraintOrDefault(Node *raw_expr, ParseExprKind kind,
+ 						  RangeVar *relation, Oid relOid)
+ {
+ 	Node    *expr;
+ 	char	*src;
+ 	List	*dpcontext = NULL;
+ 	ParseState *pstate;
+ 	MemoryContext cxt;
+ 	MemoryContext oldcxt;
+ 
+ 	cxt = AllocSetContextCreate(CurrentMemoryContext,
+ 								"uncook context",
+ 								ALLOCSET_SMALL_MINSIZE,
+ 								ALLOCSET_SMALL_INITSIZE,
+ 								ALLOCSET_SMALL_MAXSIZE);
+ 	oldcxt = MemoryContextSwitchTo(cxt);
+ 
+ 	pstate = make_parsestate(NULL);
+ 	if (relation)
+ 	{
+ 		RangeTblEntry *rte;
+ 
+ 		rte = addRangeTableEntry(pstate, relation, NULL, false, true);
+ 		addRTEtoQuery(pstate, rte, true, true, true);
+ 		dpcontext = deparse_context_for(relation->relname, relOid);
+ 	}
+ 
+ 	/*
+ 	 * Transform raw parsetree to executable expression, and apply collations
+ 	 * as necessary.
+ 	 */
+ 	expr = transformExpr(pstate, raw_expr, kind);
+ 	assign_expr_collations(pstate, expr);
+ 
+ 	/*
+ 	 * Finally, produce the requested expression, making sure it's allocated
+ 	 * in the destination memory context.
+ 	 */
+ 	MemoryContextSwitchTo(oldcxt);
+ 	src = deparse_expression(expr, dpcontext, false, false);
+ 
+ 	/* and free resources */
+ 	free_parsestate(pstate);
+ 	MemoryContextDelete(cxt);
+ 
+ 	return src;
+ }
+ 
+ /*
+  * deparseColumnConstraint
+  * 		deparse a T_Constraint node as it appears in a column definition
+  */
+ static deparseParams *
+ deparseColumnConstraint(deparseParams *parent, RangeVar *rangevar, Oid relOid,
+ 						Constraint *node)
+ {
+ 	deparseParams *constraint;
+ 
+ 	constraint = setup_params(parent);
+ 	switch (node->contype)
+ 	{
+ 		case CONSTR_NOTNULL:
+ 			append_string_param(constraint, "fmt",
+ 								"%{name} NOT NULL");
+ 			break;
+ 
+ 		case CONSTR_NULL:
+ 			/* ..?? */
+ 			break;
+ 
+ 		case CONSTR_UNIQUE:
+ 			append_string_param(constraint, "fmt",
+ 								"UNIQUE");
+ 			/* _rwOptConsTableSpace(buf, c->indexspace); */
+ 			break;
+ 
+ 		case CONSTR_PRIMARY:
+ 			append_string_param(constraint, "fmt",
+ 								"%{name} PRIMARY KEY (%{columns}s)");
+ 			break;
+ 
+ 		case CONSTR_CHECK:
+ 			{
+ 				char *src;
+ 
+ 				src = uncookConstraintOrDefault(node->raw_expr,
+ 												EXPR_KIND_CHECK_CONSTRAINT,
+ 												rangevar,
+ 												relOid);
+ 				append_string_param(constraint, "fmt",
+ 									"CHECK (%{constraint})");
+ 				append_string_param(constraint, "constraint",
+ 									src);
+ 				pfree(src);
+ 			}
+ 			break;
+ 
+ 		case CONSTR_DEFAULT:
+ 			{
+ 				char	*src = NULL;
+ 
+ 				if (node->cooked_expr)
+ 				{
+ 					List   *dpcontext;
+ 					Node   *expr = (Node *) stringToNode(node->cooked_expr);
+ 
+ 					dpcontext = deparse_context_for(rangevar->relname,
+ 													relOid);
+ 					src = deparse_expression(expr, dpcontext, false, false);
+ 				}
+ 				else if (node->raw_expr)
+ 				{
+ 					/* deparse the default expression */
+ 					src = uncookConstraintOrDefault(node->raw_expr,
+ 													EXPR_KIND_COLUMN_DEFAULT,
+ 													rangevar, relOid);
+ 				}
+ 
+ 				append_string_param(constraint, "fmt",
+ 									"DEFAULT %{default}");
+ 				append_string_param(constraint, "default", src);
+ 			}
+ 
+ 			break;
+ 
+ 		case CONSTR_FOREIGN:
+ 			break;
+ 
+ 		default:
+ 			/* unexpected */
+ 			elog(WARNING, "constraint %d is not a column constraint",
+ 				 node->contype);
+ 			break;
+ 	}
+ 
+ 	return constraint;
+ }
+ 
+ /*
+  * deparse a ColumnDef node
+  *
+  * This routine doesn't process the constraint nodes in the coldef; caller must
+  * see to it.  (Constraints represented intrinsically in the ColDef node
+  * itself, such as NOT NULL, are emitted here).
+  */
+ static deparseParams *
+ deparse_ColumnDef(deparseParams *parent, Oid relid, ColumnDef *coldef)
+ {
+ 	deparseParams *column;
+ 	deparseParams *collation;
+ 	HeapTuple	attrTup;
+ 	Form_pg_attribute	attrForm;
+ 	Oid			typid;
+ 	int32		typmod;
+ 	Oid			typcollation;
+ 
+ 	/*
+ 	 * Inherited columns without local definitions, and columns coming from OF
+ 	 * TYPE clauses, must not be emitted.  XXX -- maybe it is useful to have
+ 	 * them with "present = false" or such?
+ 	 */
+ 	if (!coldef->is_local || coldef->is_from_type)
+ 		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);
+ 
+ 	column = setup_params(parent);
+ 
+ 	get_atttypetypmodcoll(relid, attrForm->attnum,
+ 						  &typid, &typmod, &typcollation);
+ 
+ 	append_string_param(column, "fmt",
+ 						"%{name}I %{type}T %{collation}s");
+ 	append_string_param(column, "name", coldef->colname);
+ 
+ 	append_object_param(column, "type",
+ 						setup_params_for_type(column, typid, typmod));
+ 
+ 	collation = setup_params(parent);
+ 	append_string_param(collation, "fmt", "COLLATE %{name}I");
+ 	if (OidIsValid(typcollation))
+ 	{
+ 		char	*collname;
+ 
+ 		collname = get_collation_name(attrForm->attcollation);
+ 		append_string_param(collation, "name", collname);
+ 		pfree(collname);
+ 	}
+ 	else
+ 		append_boolean_param(collation, "present", false);
+ 	append_object_param(column, "collation", collation);
+ 
+ 	ReleaseSysCache(attrTup);
+ 
+ 	return column;
+ }
+ 
+ /*
+  * Subroutine for deparse_CreateStmt: deal with column elements
+  */
+ static List *
+ deparseTableElements(List *elements, deparseParams *parent,
+ 					 RangeVar *rangevar,
+ 					 Oid relOid, List *tableElements)
+ {
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, tableElements)
+ 	{
+ 		Node   *elt = (Node *) lfirst(lc);
+ 
+ 		switch (nodeTag(elt))
+ 		{
+ 			case T_ColumnDef:
+ 				{
+ 					ColumnDef  *colDef = (ColumnDef *) elt;
+ 					ListCell   *lc;
+ 					deparseParams *column;
+ 
+ 					column = deparse_ColumnDef(parent, relOid, colDef);
+ 
+ 					if (column != NULL)
+ 						elements = lappend(elements, column);
+ 
+ 					/*
+ 					 * A ColumnDef might contain constraints internally;
+ 					 * process them, too.
+ 					 */
+ 					foreach(lc, colDef->constraints)
+ 					{
+ 						Constraint	*constr = lfirst(lc);
+ 						deparseParams *constraint;
+ 
+ 						constraint =
+ 							deparseColumnConstraint(parent, rangevar,
+ 													relOid,
+ 													(Constraint *) constr);
+ 						if (constraint)
+ 							elements = lappend(elements, constraint);
+ 					}
+ 				}
+ 				break;
+ 			case T_Constraint:
+ 				break;
+ 			default:
+ 				elog(ERROR, "invalid node type %d", nodeTag(elt));
+ 		}
+ 	}
+ 
+ 	return elements;
+ }
+ 
+ static void
+ append_persistence_param(deparseParams *params, char *name, char persistence)
+ {
+ 	switch (persistence)
+ 	{
+ 		case RELPERSISTENCE_TEMP:
+ 			append_string_param(params, name, "TEMPORARY");
+ 			break;
+ 		case RELPERSISTENCE_UNLOGGED:
+ 			append_string_param(params, name, "UNLOGGED");
+ 			break;
+ 		case RELPERSISTENCE_PERMANENT:
+ 			append_string_param(params, name, "");
+ 			break;
+ 	}
+ }
+ 
+ /*
+  * deparse_CreateStmt
+  * 		Process CREATE TABLE statements
+  */
+ static void
+ deparse_CreateStmt(Oid objectId, Node *parsetree, char **command)
+ {
+ 	CreateStmt *node = (CreateStmt *) parsetree;
+ 	Relation	relation = relation_open(objectId, AccessShareLock);
+ 	RangeVar   *rangevar;
+ 	deparseParams *createStmt;
+ 	deparseParams *tmp;
+ 
+ 	rangevar = makeRangeVar(get_namespace_name(relation->rd_rel->relnamespace),
+ 							RelationGetRelationName(relation),
+ 							-1);
+ 
+ 	createStmt = setup_params(NULL);
+ 	append_string_param(createStmt, "fmt",
+ 						"CREATE %{persistence}s TABLE %{identity}D "
+ 						"%{if_not_exists}s "
+ 						"%{of_type}s (%{table_elements:, }s) "
+ 						"%{inherits}s %{on_commit}s %{tablespace}s");
+ 
+ 	/*
+ 	 * missing: WITH, LIKE, table elements in the typed table case
+ 	 */
+ 
+ 	append_persistence_param(createStmt,
+ 							 "persistence",
+ 							 relation->rd_rel->relpersistence);
+ 
+ 	tmp = setup_params_for_qualname(createStmt,
+ 									relation->rd_rel->relnamespace,
+ 									RelationGetRelationName(relation));
+ 	append_object_param(createStmt, "identity", tmp);
+ 
+ 	append_string_param(createStmt, "if_not_exists",
+ 						node->if_not_exists ? "IF NOT EXISTS" : "");
+ 
+ 	tmp = setup_params(createStmt);
+ 	append_string_param(tmp, "fmt", "OF %{of_type}T");
+ 	if (node->ofTypename)
+ 	{
+ 		deparseParams *of_type_type;
+ 
+ 		append_boolean_param(tmp, "present", true);
+ 		of_type_type = setup_params_for_type(createStmt,
+ 											 relation->rd_rel->reloftype,
+ 											 -1);
+ 		append_object_param(tmp, "of_type", of_type_type);
+ 
+ 		/*
+ 		 * XXX typed tables can have "table elements" which need to be
+ 		 * processed here
+ 		 */
+ 	}
+ 	else
+ 	{
+ 		append_null_param(tmp, "of_type");
+ 		append_boolean_param(tmp, "present", false);
+ 	}
+ 	append_object_param(createStmt, "of_type", tmp);
+ 
+ 	append_array_param(createStmt, "table_elements",
+ 					   deparseTableElements(NIL,
+ 											createStmt,
+ 											rangevar,
+ 											objectId,
+ 											node->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 = setup_params(createStmt);
+ 	append_string_param(tmp, "fmt", "INHERITS (%{parents:, }D)");
+ 	if (list_length(node->inhRelations) > 0)
+ 	{
+ 		List	   *parents = NIL;
+ 		Relation	catalogRelation;
+ 		SysScanDesc scan;
+ 		ScanKeyData key;
+ 		HeapTuple	tuple;
+ 
+ 		catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+ 
+ 		ScanKeyInit(&key,
+ 					Anum_pg_inherits_inhrelid,
+ 					BTEqualStrategyNumber, F_OIDEQ,
+ 					ObjectIdGetDatum(objectId));
+ 
+ 		scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ 								  true, NULL, 1, &key);
+ 
+ 		while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ 		{
+ 			deparseParams *parent;
+ 			Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
+ 			Relation	prel;
+ 
+ 			prel = heap_open(formInh->inhparent, AccessShareLock);
+ 
+ 			parent = setup_params_for_qualname(createStmt,
+ 											   prel->rd_rel->relnamespace,
+ 											   RelationGetRelationName(prel));
+ 
+ 			heap_close(prel, AccessShareLock);
+ 
+ 			parents = lappend(parents, parent);
+ 		}
+ 
+ 		systable_endscan(scan);
+ 		heap_close(catalogRelation, RowExclusiveLock);
+ 
+ 		append_array_param(tmp, "parents", parents);
+ 		append_boolean_param(tmp, "present", true);
+ 	}
+ 	else
+ 	{
+ 		append_null_param(tmp, "parents");
+ 		append_boolean_param(tmp, "present", false);
+ 	}
+ 	append_object_param(createStmt, "inherits", tmp);
+ 
+ 	tmp = setup_params(createStmt);
+ 	append_string_param(tmp, "fmt", "TABLESPACE %{tablespace}I");
+ 	if (node->tablespacename)
+ 		append_string_param(tmp, "tablespace", node->tablespacename);
+ 	else
+ 	{
+ 		append_null_param(tmp, "tablespace");
+ 		append_boolean_param(tmp, "present", false);
+ 	}
+ 	append_object_param(createStmt, "tablespace", tmp);
+ 
+ 	tmp = setup_params(createStmt);
+ 	append_string_param(tmp, "fmt", "ON COMMIT %{on_commit_value}s");
+ 	switch (node->oncommit)
+ 	{
+ 		case ONCOMMIT_DROP:
+ 			append_string_param(tmp, "on_commit_value", "DROP");
+ 			break;
+ 
+ 		case ONCOMMIT_DELETE_ROWS:
+ 			append_string_param(tmp, "on_commit_value", "DELETE ROWS");
+ 			break;
+ 
+ 		case ONCOMMIT_PRESERVE_ROWS:
+ 			append_string_param(tmp, "on_commit_value", "PRESERVE ROWS");
+ 			break;
+ 
+ 		case ONCOMMIT_NOOP:
+ 			append_null_param(tmp, "on_commit_value");
+ 			append_boolean_param(tmp, "present", false);
+ 			break;
+ 	}
+ 	append_object_param(createStmt, "on_commit", tmp);
+ 
+ 	*command = finalize_params(createStmt);
+ 
+ 	free_params(createStmt);
+ 	relation_close(relation, AccessShareLock);
+ }
+ 
+ /*
+  * rewrite CreateSeqStmt parser production
+  */
+ static void
+ deparse_CreateSeqStmt(Oid objectId, Node *parsetree, char **command)
+ {
+ 	deparseParams *createSeqStmt;
+ 	deparseParams *sequence;
+ 	Relation relation = relation_open(objectId, AccessShareLock);
+ 
+ 	createSeqStmt = setup_params(NULL);
+ 	append_string_param(createSeqStmt, "fmt",
+ 						"CREATE %{persistence}s SEQUENCE %{identity}D");
+ 
+ 	append_persistence_param(createSeqStmt,
+ 							 "persistence",
+ 							 relation->rd_rel->relpersistence);
+ 
+ 	sequence = setup_params_for_qualname(createSeqStmt,
+ 										 relation->rd_rel->relnamespace,
+ 										 RelationGetRelationName(relation));
+ 	append_object_param(createSeqStmt, "sequence", sequence);
+ 
+ 	/* XXX Sequence options need to be processed here */
+ 
+ 	*command = finalize_params(createSeqStmt);
+ 
+ 	free_params(createSeqStmt);
+ 	relation_close(relation, AccessShareLock);
+ }
+ 
+ /*
+  * deparse IndexStmt
+  *		Process CREATE INDEX statements
+  */
+ static void
+ deparse_IndexStmt(Oid objectId, Node *parsetree, char **command)
+ {
+ 	IndexStmt	   *node  = (IndexStmt *) parsetree;
+ 	deparseParams  *indexStmt;
+ 	deparseParams  *table;
+ 	Relation		idxrel;
+ 	Relation		heaprel;
+ 
+ 	if (node->primary || node->isconstraint)
+ 	{
+ 		/*
+ 		 * indexes for PRIMARY KEY or other constraints are output separately;
+ 		 * return empty here.
+ 		 */
+ 		*command = NULL;
+ 		return;
+ 	}
+ 
+ 	idxrel = relation_open(objectId, AccessShareLock);
+ 	heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock);
+ 
+ 	indexStmt = setup_params(NULL);
+ 	append_string_param(indexStmt, "fmt",
+ 						"CREATE %{unique}s INDEX %{concurrently}s %{name}I "
+ 						"ON %{table}D");
+ 
+ 	append_string_param(indexStmt, "unique",
+ 						node->unique ? "UNIQUE" : "");
+ 
+ 	append_string_param(indexStmt, "concurrently",
+ 						node->concurrent ?  "CONCURRENTLY" : "");
+ 
+ 	append_string_param(indexStmt, "name", RelationGetRelationName(idxrel));
+ 
+ 	table = setup_params_for_qualname(indexStmt,
+ 									  heaprel->rd_rel->relnamespace,
+ 									  RelationGetRelationName(heaprel));
+ 	append_object_param(indexStmt, "table", table);
+ 
+ 	*command = finalize_params(indexStmt);
+ 	free_params(indexStmt);
+ 
+ 	heap_close(idxrel, AccessShareLock);
+ 	heap_close(heaprel, AccessShareLock);
+ }
+ 
+ /*
+  * deparse CREATE SCHEMA
+  *
+  * We don't process the schema elements here, because CreateSchemaCommand
+  * passes them back to ProcessUtility; they get output separately.
+  */
+ static void
+ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree, char **command)
+ {
+ 	CreateSchemaStmt	*node  = (CreateSchemaStmt *) parsetree;
+ 	deparseParams  *createSchema;
+ 	deparseParams  *auth;
+ 
+ 	createSchema = setup_params(NULL);
+ 	append_string_param(createSchema, "fmt",
+ 						"CREATE SCHEMA %{if_not_exists}s %{name}I "
+ 						"%{authorization}s");
+ 
+ 	append_string_param(createSchema, "name", node->schemaname);
+ 	append_string_param(createSchema, "if_not_exists",
+ 						node->if_not_exists ? "IF NOT EXISTS" : "");
+ 
+ 	auth = setup_params(createSchema);
+ 	append_string_param(auth, "fmt", "AUTHORIZATION %{authorization_role}I");
+ 	if (node->authid)
+ 	{
+ 		append_string_param(auth, "authorization_role", node->authid);
+ 		append_boolean_param(auth, "present", true);
+ 	}
+ 	else
+ 	{
+ 		append_null_param(auth, "authorization_role");
+ 		append_boolean_param(auth, "present", false);
+ 	}
+ 	append_object_param(createSchema, "authorization", auth);
+ 
+ 	*command = finalize_params(createSchema);
+ 
+ 	free_params(createSchema);
+ }
+ 
+ /*
+  * 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.
+  */
+ void
+ deparse_utility_command(Oid objectId, Node *parsetree, char **command)
+ {
+ 	switch (nodeTag(parsetree))
+ 	{
+ 		case T_CreateSchemaStmt:
+ 			deparse_CreateSchemaStmt(objectId, parsetree, command);
+ 			break;
+ 
+ 		case T_CreateStmt:
+ 			deparse_CreateStmt(objectId, parsetree, command);
+ 
+ 		case T_CreateForeignTableStmt:
+ 			break;
+ 
+ 		case T_DefineStmt:
+ 			break;
+ 
+ 		case T_IndexStmt:
+ 			deparse_IndexStmt(objectId, parsetree, command);
+ 			break;
+ 
+ 		case T_CreateExtensionStmt:
+ 			break;
+ 
+ 		case T_CreateFdwStmt:
+ 			break;
+ 
+ 		case T_CreateForeignServerStmt:
+ 			break;
+ 
+ 		case T_CreateUserMappingStmt:
+ 			break;
+ 
+ 		case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
+ 		case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
+ 		case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
+ 			break;
+ 
+ 		case T_ViewStmt:
+ 			break;
+ 
+ 		case T_CreateFunctionStmt:
+ 			break;
+ 
+ 		case T_RuleStmt:
+ 			break;
+ 
+ 		case T_CreateSeqStmt:
+ 			deparse_CreateSeqStmt(objectId, parsetree, command);
+ 			break;
+ 
+ 		case T_CreateTableAsStmt:
+ 			break;
+ 
+ 		case T_RefreshMatViewStmt:
+ 			break;
+ 
+ 		case T_CreateTrigStmt:
+ 			break;
+ 
+ 		case T_CreatePLangStmt:
+ 			break;
+ 
+ 		case T_CreateDomainStmt:
+ 			break;
+ 
+ 		case T_CreateConversionStmt:
+ 			break;
+ 
+ 		case T_CreateCastStmt:
+ 			break;
+ 
+ 		case T_CreateOpClassStmt:
+ 			break;
+ 
+ 		case T_CreateOpFamilyStmt:
+ 			break;
+ 
+ 		default:
+ 			elog(LOG, "unrecognized node type: %d",
+ 				 (int) nodeTag(parsetree));
+ 	}
+ 
+ 	return;
+ }
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 898,903 **** ProcessUtilitySlow(Node *parsetree,
--- 898,904 ----
  	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();
***************
*** 916,921 **** ProcessUtilitySlow(Node *parsetree,
--- 917,926 ----
  			case T_CreateSchemaStmt:
  				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
  									queryString);
+ 				/*
+ 				 * CreateSchemaCommand calls EventTriggerStashCreatedObject
+ 				 * internally, for reasons explained there.
+ 				 */
  				break;
  
  			case T_CreateStmt:
***************
*** 923,929 **** ProcessUtilitySlow(Node *parsetree,
  				{
  					List	   *stmts;
  					ListCell   *l;
- 					Oid			relOid;
  
  					/* Run parse analysis ... */
  					stmts = transformCreateStmt((CreateStmt *) parsetree,
--- 928,933 ----
***************
*** 940,948 **** ProcessUtilitySlow(Node *parsetree,
  							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
  
  							/* Create the table itself */
! 							relOid = DefineRelation((CreateStmt *) stmt,
! 													RELKIND_RELATION,
! 													InvalidOid);
  
  							/*
  							 * Let AlterTableCreateToastTable decide if this
--- 944,955 ----
  							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
  
  							/* Create the table itself */
! 							objectId = DefineRelation((CreateStmt *) stmt,
! 													  RELKIND_RELATION,
! 													  InvalidOid);
! 							EventTriggerStashCreatedObject(objectId,
! 														   OCLASS_CLASS,
! 														   stmt);
  
  							/*
  							 * Let AlterTableCreateToastTable decide if this
***************
*** 964,983 **** ProcessUtilitySlow(Node *parsetree,
  												   toast_options,
  												   true);
  
! 							AlterTableCreateToastTable(relOid, toast_options);
  						}
  						else if (IsA(stmt, CreateForeignTableStmt))
  						{
  							/* Create the table itself */
! 							relOid = DefineRelation((CreateStmt *) stmt,
! 													RELKIND_FOREIGN_TABLE,
! 													InvalidOid);
  							CreateForeignTable((CreateForeignTableStmt *) stmt,
! 											   relOid);
  						}
  						else
  						{
! 							/* Recurse for anything else */
  							ProcessUtility(stmt,
  										   queryString,
  										   PROCESS_UTILITY_SUBCOMMAND,
--- 971,997 ----
  												   toast_options,
  												   true);
  
! 							AlterTableCreateToastTable(objectId, toast_options);
  						}
  						else if (IsA(stmt, CreateForeignTableStmt))
  						{
  							/* Create the table itself */
! 							objectId = DefineRelation((CreateStmt *) stmt,
! 													  RELKIND_FOREIGN_TABLE,
! 													  InvalidOid);
  							CreateForeignTable((CreateForeignTableStmt *) stmt,
! 											   objectId);
! 							EventTriggerStashCreatedObject(objectId,
! 														   OCLASS_CLASS,
! 														   stmt);
  						}
  						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,
***************
*** 1104,1153 **** ProcessUtilitySlow(Node *parsetree,
  			case T_DefineStmt:
  				{
  					DefineStmt *stmt = (DefineStmt *) parsetree;
  
  					switch (stmt->kind)
  					{
  						case OBJECT_AGGREGATE:
! 							DefineAggregate(stmt->defnames, stmt->args,
! 											stmt->oldstyle, stmt->definition,
! 											queryString);
  							break;
  						case OBJECT_OPERATOR:
  							Assert(stmt->args == NIL);
! 							DefineOperator(stmt->defnames, stmt->definition);
  							break;
  						case OBJECT_TYPE:
  							Assert(stmt->args == NIL);
! 							DefineType(stmt->defnames, stmt->definition);
  							break;
  						case OBJECT_TSPARSER:
  							Assert(stmt->args == NIL);
! 							DefineTSParser(stmt->defnames, stmt->definition);
  							break;
  						case OBJECT_TSDICTIONARY:
  							Assert(stmt->args == NIL);
! 							DefineTSDictionary(stmt->defnames,
! 											   stmt->definition);
  							break;
  						case OBJECT_TSTEMPLATE:
  							Assert(stmt->args == NIL);
! 							DefineTSTemplate(stmt->defnames,
! 											 stmt->definition);
  							break;
  						case OBJECT_TSCONFIGURATION:
  							Assert(stmt->args == NIL);
! 							DefineTSConfiguration(stmt->defnames,
! 												  stmt->definition);
  							break;
  						case OBJECT_COLLATION:
  							Assert(stmt->args == NIL);
! 							DefineCollation(stmt->defnames, stmt->definition);
  							break;
  						default:
  							elog(ERROR, "unrecognized define stmt type: %d",
  								 (int) stmt->kind);
  							break;
  					}
  				}
  				break;
  
--- 1118,1183 ----
  			case T_DefineStmt:
  				{
  					DefineStmt *stmt = (DefineStmt *) parsetree;
+ 					ObjectClass	class;
  
  					switch (stmt->kind)
  					{
  						case OBJECT_AGGREGATE:
! 							objectId =
! 								DefineAggregate(stmt->defnames, stmt->args,
! 												stmt->oldstyle,
! 												stmt->definition, queryString);
! 							class = OCLASS_PROC;
  							break;
  						case OBJECT_OPERATOR:
  							Assert(stmt->args == NIL);
! 							objectId = DefineOperator(stmt->defnames,
! 													  stmt->definition);
! 							class = OCLASS_OPERATOR;
  							break;
  						case OBJECT_TYPE:
  							Assert(stmt->args == NIL);
! 							objectId = DefineType(stmt->defnames,
! 												  stmt->definition);
! 							class = OCLASS_TYPE;
  							break;
  						case OBJECT_TSPARSER:
  							Assert(stmt->args == NIL);
! 							objectId = DefineTSParser(stmt->defnames,
! 													  stmt->definition);
! 							class = OCLASS_TSPARSER;
  							break;
  						case OBJECT_TSDICTIONARY:
  							Assert(stmt->args == NIL);
! 							objectId = DefineTSDictionary(stmt->defnames,
! 														  stmt->definition);
! 							class = OCLASS_TSDICT;
  							break;
  						case OBJECT_TSTEMPLATE:
  							Assert(stmt->args == NIL);
! 							objectId = DefineTSTemplate(stmt->defnames,
! 														stmt->definition);
! 							class = OCLASS_TSTEMPLATE;
  							break;
  						case OBJECT_TSCONFIGURATION:
  							Assert(stmt->args == NIL);
! 							objectId = DefineTSConfiguration(stmt->defnames,
! 															 stmt->definition);
! 							class = OCLASS_TSCONFIG;
  							break;
  						case OBJECT_COLLATION:
  							Assert(stmt->args == NIL);
! 							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;
  
***************
*** 1165,1181 **** 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 */
  				}
  				break;
  
  			case T_CreateExtensionStmt:
! 				CreateExtension((CreateExtensionStmt *) parsetree);
  				break;
  
  			case T_AlterExtensionStmt:
--- 1195,1214 ----
  					stmt = transformIndexStmt(stmt, queryString);
  
  					/* ... and do it */
! 					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:
! 				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_EXTENSION, parsetree);
  				break;
  
  			case T_AlterExtensionStmt:
***************
*** 1187,1193 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_CreateFdwStmt:
! 				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
  				break;
  
  			case T_AlterFdwStmt:
--- 1220,1227 ----
  				break;
  
  			case T_CreateFdwStmt:
! 				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_FDW, parsetree);
  				break;
  
  			case T_AlterFdwStmt:
***************
*** 1195,1201 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_CreateForeignServerStmt:
! 				CreateForeignServer((CreateForeignServerStmt *) parsetree);
  				break;
  
  			case T_AlterForeignServerStmt:
--- 1229,1237 ----
  				break;
  
  			case T_CreateForeignServerStmt:
! 				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_FOREIGN_SERVER,
! 										parsetree);
  				break;
  
  			case T_AlterForeignServerStmt:
***************
*** 1203,1209 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_CreateUserMappingStmt:
! 				CreateUserMapping((CreateUserMappingStmt *) parsetree);
  				break;
  
  			case T_AlterUserMappingStmt:
--- 1239,1247 ----
  				break;
  
  			case T_CreateUserMappingStmt:
! 				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_USER_MAPPING,
! 										parsetree);
  				break;
  
  			case T_AlterUserMappingStmt:
***************
*** 1218,1233 **** ProcessUtilitySlow(Node *parsetree,
  				{
  					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
  
! 					DefineCompositeType(stmt->typevar, stmt->coldeflist);
  				}
  				break;
  
  			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
! 				DefineEnum((CreateEnumStmt *) parsetree);
  				break;
  
  			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
! 				DefineRange((CreateRangeStmt *) parsetree);
  				break;
  
  			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
--- 1256,1274 ----
  				{
  					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
  
! 					objectId = DefineCompositeType(stmt->typevar, stmt->coldeflist);
! 					EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
  				}
  				break;
  
  			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
! 				objectId = DefineEnum((CreateEnumStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
  				break;
  
  			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
! 				objectId = DefineRange((CreateRangeStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
  				break;
  
  			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
***************
*** 1235,1257 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_ViewStmt:	/* CREATE VIEW */
! 				DefineView((ViewStmt *) parsetree, queryString);
  				break;
  
  			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
! 				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
  				break;
  
  			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
! 				AlterFunction((AlterFunctionStmt *) parsetree);
  				break;
  
  			case T_RuleStmt:	/* CREATE RULE */
! 				DefineRule((RuleStmt *) parsetree, queryString);
  				break;
  
  			case T_CreateSeqStmt:
! 				DefineSequence((CreateSeqStmt *) parsetree);
  				break;
  
  			case T_AlterSeqStmt:
--- 1276,1302 ----
  				break;
  
  			case T_ViewStmt:	/* CREATE VIEW */
! 				objectId = DefineView((ViewStmt *) parsetree, queryString);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
  				break;
  
  			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
! 				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_PROC, parsetree);
  				break;
  
  			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
! 				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
  				break;
  
  			case T_RuleStmt:	/* CREATE RULE */
! 				objectId = DefineRule((RuleStmt *) parsetree, queryString);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_REWRITE, parsetree);
  				break;
  
  			case T_CreateSeqStmt:
! 				objectId = DefineSequence((CreateSeqStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
  				break;
  
  			case T_AlterSeqStmt:
***************
*** 1259,1300 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_CreateTableAsStmt:
! 				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
  								  queryString, params, completionTag);
  				break;
  
  			case T_RefreshMatViewStmt:
! 				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
  								   queryString, params, completionTag);
  				break;
  
  			case T_CreateTrigStmt:
! 				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
  									 InvalidOid, InvalidOid, false);
  				break;
  
  			case T_CreatePLangStmt:
! 				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
  				break;
  
  			case T_CreateDomainStmt:
! 				DefineDomain((CreateDomainStmt *) parsetree);
  				break;
  
  			case T_CreateConversionStmt:
! 				CreateConversionCommand((CreateConversionStmt *) parsetree);
  				break;
  
  			case T_CreateCastStmt:
! 				CreateCast((CreateCastStmt *) parsetree);
  				break;
  
  			case T_CreateOpClassStmt:
! 				DefineOpClass((CreateOpClassStmt *) parsetree);
  				break;
  
  			case T_CreateOpFamilyStmt:
! 				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
  				break;
  
  			case T_AlterOpFamilyStmt:
--- 1304,1354 ----
  				break;
  
  			case T_CreateTableAsStmt:
! 				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
  								  queryString, params, completionTag);
+ 				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
  				break;
  
  			case T_RefreshMatViewStmt:
! 				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
  								   queryString, params, completionTag);
+ 				EventTriggerStashCreatedObject(objectId, OCLASS_CLASS, parsetree);
  				break;
  
  			case T_CreateTrigStmt:
! 				objectId = CreateTrigger((CreateTrigStmt *) parsetree, queryString,
  									 InvalidOid, InvalidOid, false);
+ 				EventTriggerStashCreatedObject(objectId, OCLASS_TRIGGER, parsetree);
  				break;
  
  			case T_CreatePLangStmt:
! 				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_LANGUAGE, parsetree);
  				break;
  
  			case T_CreateDomainStmt:
! 				objectId = DefineDomain((CreateDomainStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_TYPE, parsetree);
  				break;
  
  			case T_CreateConversionStmt:
! 				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_CONVERSION, parsetree);
  				break;
  
  			case T_CreateCastStmt:
! 				objectId = CreateCast((CreateCastStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_CAST, parsetree);
  				break;
  
  			case T_CreateOpClassStmt:
! 				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_OPCLASS, parsetree);
  				break;
  
  			case T_CreateOpFamilyStmt:
! 				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_OPFAMILY, parsetree);
  				break;
  
  			case T_AlterOpFamilyStmt:
*** a/src/backend/utils/adt/format_type.c
--- b/src/backend/utils/adt/format_type.c
***************
*** 31,37 ****
  static char *format_type_internal(Oid type_oid, int32 typemod,
  					 bool typemod_given, bool allow_invalid,
  					 bool force_qualify);
! static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
  
  
  /*
--- 31,38 ----
  static char *format_type_internal(Oid type_oid, int32 typemod,
  					 bool typemod_given, bool allow_invalid,
  					 bool force_qualify);
! static char *printTypmod(const char *typname, int32 typmod, Oid typmodout,
! 						 bool typmodOnly);
  
  
  /*
***************
*** 96,101 **** format_type_be(Oid type_oid)
--- 97,105 ----
  	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,188 **** format_type_with_typemod(Oid type_oid, int32 typemod)
  	return format_type_internal(type_oid, typemod, true, false, false);
  }
  
  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.
! 	 * 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 */
  
  	switch (type_oid)
  	{
  		case BITOID:
  			if (with_typemod)
! 				buf = printTypmod("bit", typemod, typeform->typmodout);
  			else if (typemod_given)
  			{
  				/*
--- 115,140 ----
  	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_special_type(Oid type_oid, Form_pg_type typeform,
! 					int32 typemod, bool typemod_given, bool with_typemod)
  {
! 	char   *buf = NULL;
  
  	switch (type_oid)
  	{
  		case BITOID:
  			if (with_typemod)
! 				buf = printTypmod("bit", typemod, typeform->typmodout, false);
  			else if (typemod_given)
  			{
  				/*
***************
*** 201,207 **** format_type_internal(Oid type_oid, int32 typemod,
  
  		case BPCHAROID:
  			if (with_typemod)
! 				buf = printTypmod("character", typemod, typeform->typmodout);
  			else if (typemod_given)
  			{
  				/*
--- 153,160 ----
  
  		case BPCHAROID:
  			if (with_typemod)
! 				buf = printTypmod("character", typemod, typeform->typmodout,
! 								  false);
  			else if (typemod_given)
  			{
  				/*
***************
*** 236,296 **** format_type_internal(Oid type_oid, int32 typemod,
  
  		case NUMERICOID:
  			if (with_typemod)
! 				buf = printTypmod("numeric", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("numeric");
  			break;
  
  		case INTERVALOID:
  			if (with_typemod)
! 				buf = printTypmod("interval", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("interval");
  			break;
  
  		case TIMEOID:
  			if (with_typemod)
! 				buf = printTypmod("time", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("time without time zone");
  			break;
  
  		case TIMETZOID:
  			if (with_typemod)
! 				buf = printTypmod("time", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("time with time zone");
  			break;
  
  		case TIMESTAMPOID:
  			if (with_typemod)
! 				buf = printTypmod("timestamp", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("timestamp without time zone");
  			break;
  
  		case TIMESTAMPTZOID:
  			if (with_typemod)
! 				buf = printTypmod("timestamp", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("timestamp with time zone");
  			break;
  
  		case VARBITOID:
  			if (with_typemod)
! 				buf = printTypmod("bit varying", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("bit varying");
  			break;
  
  		case VARCHAROID:
  			if (with_typemod)
! 				buf = printTypmod("character varying", typemod, typeform->typmodout);
  			else
  				buf = pstrdup("character varying");
  			break;
  	}
  
  	if (buf == NULL)
  	{
  		/*
--- 189,334 ----
  
  		case NUMERICOID:
  			if (with_typemod)
! 				buf = printTypmod("numeric", typemod, typeform->typmodout,
! 								  false);
  			else
  				buf = pstrdup("numeric");
  			break;
  
  		case INTERVALOID:
  			if (with_typemod)
! 				buf = printTypmod("interval", typemod, typeform->typmodout,
! 								  false);
  			else
  				buf = pstrdup("interval");
  			break;
  
  		case TIMEOID:
  			if (with_typemod)
! 				buf = printTypmod("time", typemod, typeform->typmodout,
! 								  false);
  			else
  				buf = pstrdup("time without time zone");
  			break;
  
  		case TIMETZOID:
  			if (with_typemod)
! 				buf = printTypmod("time", typemod, typeform->typmodout,
! 								  false);
  			else
  				buf = pstrdup("time with time zone");
  			break;
  
  		case TIMESTAMPOID:
  			if (with_typemod)
! 				buf = printTypmod("timestamp", typemod, typeform->typmodout,
! 								  false);
  			else
  				buf = pstrdup("timestamp without time zone");
  			break;
  
  		case TIMESTAMPTZOID:
  			if (with_typemod)
! 				buf = printTypmod("timestamp", typemod, typeform->typmodout,
! 								  false);
  			else
  				buf = pstrdup("timestamp with time zone");
  			break;
  
  		case VARBITOID:
  			if (with_typemod)
! 				buf = printTypmod("bit varying", typemod, typeform->typmodout,
! 								  false);
  			else
  				buf = pstrdup("bit varying");
  			break;
  
  		case VARCHAROID:
  			if (with_typemod)
! 				buf = printTypmod("character varying", typemod,
! 								  typeform->typmodout, false);
  			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)
  	{
  		/*
***************
*** 312,318 **** format_type_internal(Oid type_oid, int32 typemod,
  		buf = quote_qualified_identifier(nspname, typname);
  
  		if (with_typemod)
! 			buf = printTypmod(buf, typemod, typeform->typmodout);
  	}
  
  	if (is_array)
--- 350,356 ----
  		buf = quote_qualified_identifier(nspname, typname);
  
  		if (with_typemod)
! 			buf = printTypmod(buf, typemod, typeform->typmodout, false);
  	}
  
  	if (is_array)
***************
*** 323,334 **** format_type_internal(Oid type_oid, int32 typemod,
  	return buf;
  }
  
  
  /*
   * Add typmod decoration to the basic type name
   */
  static char *
! printTypmod(const char *typname, int32 typmod, Oid typmodout)
  {
  	char	   *res;
  
--- 361,473 ----
  	return buf;
  }
  
+ /*
+  * Similar to format_type_internal, except we return each bit of information
+  * separately:
+  *
+  * - is_system is set if the type corresponds to a special case in gram.y
+  *   (and therefore does not need any quoting or schema-qualification)
+  *
+  * - 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
+  *
+  * 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,
+ 					 bool *is_system,
+ 					 char **nspname, char **typname, char **typemodstr,
+ 					 bool *is_array)
+ {
+ 	HeapTuple	tuple;
+ 	Form_pg_type typeform;
+ 	Oid			array_base_type;
+ 	char	   *buf;
+ 
+ 	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.  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))
+ 			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 must be
+ 	 * handled the same way as a user type name --- in particular, it must be
+ 	 * double-quoted if it matches any lexer keyword.  This behavior is
+ 	 * essential for some cases, such as types "bit" and "char".
+ 	 */
+ 	buf = format_special_type(type_oid, typeform,
+ 							  typemod, typemod >= 0, false);
+ 
+ 	if (buf != NULL)
+ 	{
+ 		*is_system = true;
+ 		*typname = buf;
+ 		*nspname = NULL;
+ 
+ 		if (typemod > 0)
+ 			*typemodstr = printTypmod(buf, typemod, typeform->typmodout, true);
+ 		else
+ 			*typemodstr = pstrdup("");	/* XXX ?? */
+ 	}
+ 	else
+ 	{
+ 		/* Default handling: report the name as it appears in the catalog. */
+ 		*is_system = false;
+ 
+ 		*nspname = get_namespace_name(typeform->typnamespace);
+ 		*typname = pstrdup(NameStr(typeform->typname));
+ 
+ 		if (typemod > 0)
+ 			*typemodstr = printTypmod(buf, typemod, typeform->typmodout, true);
+ 		else
+ 			*typemodstr = pstrdup("");	/* XXX ?? */
+ 	}
+ 
+ 	ReleaseSysCache(tuple);
+ }
+ 
  
  /*
   * Add typmod decoration to the basic type name
   */
  static char *
! printTypmod(const char *typname, int32 typmod, Oid typmodout, bool typmodOnly)
  {
  	char	   *res;
  
***************
*** 338,344 **** 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);
  	}
  	else
  	{
--- 477,486 ----
  	if (typmodout == InvalidOid)
  	{
  		/* Default behavior: just print the integer typmod with parens */
! 		if (typmodOnly)
! 			res = psprintf("(%d)", (int) typmod);
! 		else
! 			res = psprintf("%s(%d)", typname, (int) typmod);
  	}
  	else
  	{
***************
*** 347,359 **** printTypmod(const char *typname, int32 typmod, Oid typmodout)
  
  		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
  												 Int32GetDatum(typmod)));
! 		res = psprintf("%s%s", typname, tmstr);
  	}
  
  	return res;
  }
  
- 
  /*
   * type_maximum_size --- determine maximum width of a variable-width column
   *
--- 489,503 ----
  
  		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
  												 Int32GetDatum(typmod)));
! 		if (typmodOnly)
! 			res = psprintf("%s", tmstr);
! 		else
! 			res = psprintf("%s%s", typname, tmstr);
  	}
  
  	return res;
  }
  
  /*
   * type_maximum_size --- determine maximum width of a variable-width column
   *
*** a/src/backend/utils/adt/jsonfuncs.c
--- b/src/backend/utils/adt/jsonfuncs.c
***************
*** 212,224 **** typedef struct PopulateRecordsetState
  }	PopulateRecordsetState;
  
  /*
!  * 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
   * 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.
--- 212,224 ----
  }	PopulateRecordsetState;
  
  /*
!  * 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, 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.
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 4156,4161 **** get_query_def(Query *query, StringInfo buf, List *parentnamespace,
--- 4156,4175 ----
  	}
  }
  
+ 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
   * ----------
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 176,181 **** extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
--- 176,183 ----
  
  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,
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4759,4764 **** DESCR("SP-GiST support for quad tree over range");
--- 4759,4768 ----
  /* 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,25}" "{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_ ));
*** /dev/null
--- b/src/include/commands/alter_table.h
***************
*** 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 */
*** a/src/include/commands/createas.h
--- b/src/include/commands/createas.h
***************
*** 19,25 ****
  #include "tcop/dest.h"
  
  
! extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  				  ParamListInfo params, char *completionTag);
  
  extern int	GetIntoRelEFlags(IntoClause *intoClause);
--- 19,25 ----
  #include "tcop/dest.h"
  
  
! extern Oid	ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  				  ParamListInfo params, char *completionTag);
  
  extern int	GetIntoRelEFlags(IntoClause *intoClause);
*** a/src/include/commands/event_trigger.h
--- b/src/include/commands/event_trigger.h
***************
*** 49,54 **** extern void EventTriggerSQLDrop(Node *parsetree);
--- 49,56 ----
  
  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);
  
*** a/src/include/commands/matview.h
--- b/src/include/commands/matview.h
***************
*** 22,28 ****
  
  extern void SetMatViewPopulatedState(Relation relation, bool newstate);
  
! extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  				   ParamListInfo params, char *completionTag);
  
  extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
--- 22,28 ----
  
  extern void SetMatViewPopulatedState(Relation relation, bool newstate);
  
! extern Oid ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  				   ParamListInfo params, char *completionTag);
  
  extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
*** /dev/null
--- b/src/include/tcop/deparse_utility.h
***************
*** 0 ****
--- 1,18 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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 void deparse_utility_command(Oid objectId, Node *parsetree,
+ 						char **command);
+ 
+ #endif	/* DEPARSE_UTILITY_H */
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 672,677 **** extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
--- 672,678 ----
  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_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,1062 **** extern char *format_type_be_qualified(Oid type_oid);
--- 1058,1066 ----
  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,
+ 					 bool *is_system, char **nspname, char **typname,
+ 					 char **typemodstr, bool *is_array);
  
  /* quote.c */
  extern Datum quote_ident(PG_FUNCTION_ARGS);
***************
*** 1177,1182 **** extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
--- 1181,1188 ----
  
  /* 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);
