Add CREATE support to event triggers

Started by Alvaro Herreraabout 12 years ago57 messages
#1Alvaro Herrera
alvherre@2ndquadrant.com
1 attachment(s)

Hello,

Attached you can find a very-much-WIP patch to add CREATE info support
for event triggers (normalized commands). This patch builds mainly on
two things:

1. Dimitri's "DDL rewrite" patch he submitted way back, in
/messages/by-id/m2zk1j9c44.fsf@2ndQuadrant.fr

I borrowed the whole ddl_rewrite.c code, and tweaked it a bit. There
are several things still wrong with it and which will need to be fixed
before a final patch can even be contemplated; but there are some
questions that require a consensus answer before I go and fix it all,
because what it will look like will depend on said answers.

2. The ideas we used to build DROP support. Mainly, the interesting
thing here is the fact that we use a SRF to report, at
ddl_command_end, all the objects that were created during execution
of that command. We do this by collecting them in a list in some raw
form somewhere during ProcessUtility, and then spitting them out if
the SRF is called. I think the general idea is sound, although of
course I admit there might be bugs in the implementation.

Note this patch doesn't try to add any kind of ALTER support. I think
this is fine in principle, because we agreed that we would attack each
kind of command separately (divide to conquer and all that); but there
is a slight problem for some kind of objects that are represented partly
as ALTER state during creation; for example creating a table with a
sequence uses ALTER SEQ/OWNED BY internally at some point. There might
be other cases I'm missing, also. (The REFRESH command is nominally
also supported.)

Now about the questions I mentioned above:

a) It doesn't work to reverse-parse the statement nodes in all cases;
there are several unfixable bugs if we only do that. In order to create
always-correct statements, we need access to the catalogs for the
created objects. But if we are doing catalog access, then it seems to
me that we can do away with the statement parse nodes completely and
just reconstruct the objects from catalog information. Shall we go that
route?

b) What's the best design of the SRF output? This patch proposes two
columns, object identity and create statement. Is there use for
anything else? Class/object OIDs perhaps, schema OIDs for objects types
that have it? I don't see any immediate need to that info, but perhaps
someone does.

c) The current patch stashes all objects in a list, whenever there's an
event trigger function. But perhaps some users want to have event
triggers and not have any use for the CREATE statements. So one idea is
to require users that want the objects reported to call a special
function in a ddl_command_start event trigger which enables collection;
if it's not called, objects are not reported. This eliminates
performance impact for people not using it, but then maybe it will be
surprising for people that call the SRF and find that it always returns
empty.

d) There's a new routine uncookConstraintOrDefault. This takes a raw
expression, runs transformExpr() on it, and then deparses it (possibly
setting up a deparse context based on some relation). This is a
somewhat funny thing to be doing, so if there are other ideas on how to
handle this, I'm all ears.

This patch doesn't include doc changes or regression tests. Those will
be added in a later version. For now, you can see this code in action
by running installing an event trigger like this:

CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger LANGUAGE plpgsql AS $$
DECLARE
r RECORD;
BEGIN
FOR r IN SELECT * FROM pg_event_trigger_get_normalized_commands()
LOOP
RAISE NOTICE 'object created: id %, statement %',
r.identity, r.command;
END LOOP;
END;
$$;
CREATE EVENT TRIGGER snitch ON ddl_command_end EXECUTE PROCEDURE snitch();

And then running the DDL of your preference. Be warned that there are
many rough edges, so some objects might be incompletely reported (or
bogusly so, in particular with regards to objects in search_path but not
in the first position of it); but if you see any crashes, please let me
know. Also, many commands are not supported yet by the ddl_rewrite.c
code and they return "unsupported FOOBAR". I didn't want to go to the
effort of writing code for them that might end up being ripped out if we
wanted to go the route of using only syscache to build CREATE
statements. That part is pretty mechanical, and not much code anyway.
I just left whatever Dimitri already had in his patch.

I would have continued working some more on this patch before
submitting, except that I'm going on vacations for a few days starting
tomorrow and we have a rule that no complex patches can go in at CF4
without it having been discussed in previous commitfests. My intention
is that the patch that arrives at CF4 is free of design surprises and
ready for a final review before commit, so if there's any disagreement
with the direction this is going, please speak up now.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

event-trigger-create.patchtext/x-diff; charset=us-asciiDownload
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 2006,2011 **** add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
--- 2006,2018 ----
  	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;
  }
  
  /*
***************
*** 420,425 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
--- 431,439 ----
  	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
***************
*** 33,38 ****
--- 33,39 ----
  #include "miscadmin.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
+ #include "utils/ddl_rewrite.h"
  #include "utils/evtcache.h"
  #include "utils/fmgroids.h"
  #include "utils/lsyscache.h"
***************
*** 48,53 **** typedef struct EventTriggerQueryState
--- 49,55 ----
  	slist_head	SQLDropList;
  	bool		in_sql_drop;
  	MemoryContext cxt;
+ 	List	   *stash;
  	struct EventTriggerQueryState *previous;
  } EventTriggerQueryState;
  
***************
*** 488,494 **** AlterEventTriggerOwner(const char *name, Oid newOwnerId)
  }
  
  /*
!  * Change extension owner, by OID
   */
  void
  AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId)
--- 490,496 ----
  }
  
  /*
!  * Change event trigger owner, by OID
   */
  void
  AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId)
***************
*** 1019,1031 **** 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,
--- 1021,1026 ----
***************
*** 1033,1040 **** 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;
--- 1028,1037 ----
  								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;
***************
*** 1066,1071 **** EventTriggerEndCompleteQuery(void)
--- 1063,1102 ----
  	currentEventTriggerState = prevstate;
  }
  
+ 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);
+ }
+ 
  /*
   * Do we need to keep close track of objects being dropped?
   *
***************
*** 1291,1293 **** pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
--- 1322,1403 ----
  
  	return (Datum) 0;
  }
+ 
+ Datum
+ pg_event_trigger_get_normalized_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);
+ 		const char *identity;
+ 		const char *command;
+ 		Datum		values[2];
+ 		bool		nulls[2];
+ 		ObjectAddress	addr;
+ 		int			i = 0;
+ 
+ 		addr.classId = obj->classId;
+ 		addr.objectId = obj->objectId;
+ 		addr.objectSubId = 0;
+ 		identity = getObjectIdentity(&addr);
+ 
+ 		command = rewrite_utility_command(obj->objectId,
+ 										  identity,
+ 										  obj->parsetree);
+ 
+ 		MemSet(nulls, 0, sizeof(nulls));
+ 
+ 		/* identity */
+ 		values[i++] = CStringGetTextDatum(identity);
+ 		/* command */
+ 		values[i++] = CStringGetTextDatum(command);
+ 
+ 		tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ 	}
+ 
+ 	/* clean up and return the tuplestore */
+ 	tuplestore_donestoring(tupstore);
+ 
+ 	PG_RETURN_VOID();
+ }
*** a/src/backend/commands/matview.c
--- b/src/backend/commands/matview.c
***************
*** 131,137 **** 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)
  {
--- 131,137 ----
   * 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)
  {
***************
*** 268,273 **** ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
--- 268,275 ----
  	}
  	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/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 893,898 **** ProcessUtilitySlow(Node *parsetree,
--- 893,899 ----
  	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();
***************
*** 911,916 **** ProcessUtilitySlow(Node *parsetree,
--- 912,921 ----
  			case T_CreateSchemaStmt:
  				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
  									queryString);
+ 				/*
+ 				 * CreateSchemaCommand calls EventTriggerStashCreatedObject
+ 				 * internally, for reasons explained there.
+ 				 */
  				break;
  
  			case T_CreateStmt:
***************
*** 918,924 **** ProcessUtilitySlow(Node *parsetree,
  				{
  					List	   *stmts;
  					ListCell   *l;
- 					Oid			relOid;
  
  					/* Run parse analysis ... */
  					stmts = transformCreateStmt((CreateStmt *) parsetree,
--- 923,928 ----
***************
*** 935,943 **** 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
--- 939,950 ----
  							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
***************
*** 959,978 **** 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,
--- 966,992 ----
  												   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,
***************
*** 1099,1148 **** 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;
  
--- 1113,1178 ----
  			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;
  
***************
*** 1160,1176 **** 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:
--- 1190,1209 ----
  					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:
***************
*** 1182,1188 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_CreateFdwStmt:
! 				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
  				break;
  
  			case T_AlterFdwStmt:
--- 1215,1222 ----
  				break;
  
  			case T_CreateFdwStmt:
! 				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_FDW, parsetree);
  				break;
  
  			case T_AlterFdwStmt:
***************
*** 1190,1196 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_CreateForeignServerStmt:
! 				CreateForeignServer((CreateForeignServerStmt *) parsetree);
  				break;
  
  			case T_AlterForeignServerStmt:
--- 1224,1232 ----
  				break;
  
  			case T_CreateForeignServerStmt:
! 				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_FOREIGN_SERVER,
! 										parsetree);
  				break;
  
  			case T_AlterForeignServerStmt:
***************
*** 1198,1204 **** ProcessUtilitySlow(Node *parsetree,
  				break;
  
  			case T_CreateUserMappingStmt:
! 				CreateUserMapping((CreateUserMappingStmt *) parsetree);
  				break;
  
  			case T_AlterUserMappingStmt:
--- 1234,1242 ----
  				break;
  
  			case T_CreateUserMappingStmt:
! 				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
! 				EventTriggerStashCreatedObject(objectId, OCLASS_USER_MAPPING,
! 										parsetree);
  				break;
  
  			case T_AlterUserMappingStmt:
***************
*** 1213,1228 **** 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) */
--- 1251,1269 ----
  				{
  					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) */
***************
*** 1230,1252 **** 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:
--- 1271,1297 ----
  				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:
***************
*** 1254,1295 **** 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:
--- 1299,1349 ----
  				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/Makefile
--- b/src/backend/utils/adt/Makefile
***************
*** 17,23 **** endif
  
  OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	array_userfuncs.o arrayutils.o bool.o \
! 	cash.o char.o date.o datetime.o datum.o domains.o \
  	enum.o float.o format_type.o \
  	geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
--- 17,23 ----
  
  OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
  	array_userfuncs.o arrayutils.o bool.o \
! 	cash.o char.o date.o datetime.o datum.o domains.o ddl_rewrite.o \
  	enum.o float.o format_type.o \
  	geo_ops.o geo_selfuncs.o int.o int8.o json.o jsonfuncs.o like.o \
  	lockfuncs.o misc.o nabstime.o name.o numeric.o numutils.o \
*** /dev/null
--- b/src/backend/utils/adt/ddl_rewrite.c
***************
*** 0 ****
--- 1,1800 ----
+ /*-------------------------------------------------------------------------
+  *
+  * ddl_rewrite.c
+  *	  Functions to convert a utility command parsetree back to a command string
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/backend/utils/adt/ddl_rewrite.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ 
+ #include "access/htup_details.h"
+ #include "catalog/index.h"
+ #include "catalog/namespace.h"
+ #include "catalog/pg_class.h"
+ #include "catalog/pg_proc.h"
+ #include "catalog/heap.h"
+ #include "commands/defrem.h"
+ #include "lib/stringinfo.h"
+ #include "nodes/parsenodes.h"
+ #include "parser/parse_collate.h"
+ #include "parser/parse_expr.h"
+ #include "parser/parse_relation.h"
+ #include "parser/parse_type.h"
+ #include "parser/analyze.h"
+ #include "utils/builtins.h"
+ #include "utils/ddl_rewrite.h"
+ #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
+ #include "utils/syscache.h"
+ 
+ /*
+  * Given a RangeVar, return the namespace name to use as schemaname. When
+  * r->schemaname is NULL, returns the first schema name of the current
+  * search_path.
+  *
+  * It can be so that the resulting object (schema.name) does not exists, that
+  * check didn't happen yet when at the "ddl_command_start" event. All we ca do
+  * is play by the system's rule. The other option would be to capture and
+  * expose the search_path to the event trigger functions. Then any trigger
+  * function would have to duplicate the code here to extract the first schema
+  * in the search_path anyway.
+  */
+ static char *
+ RangeVarGetNamespace(RangeVar *r)
+ {
+ 	char       *schemaname;
+ 	List	   *search_path;
+ 
+ 	if (r->schemaname)
+ 		return r->schemaname;
+ 
+ 	search_path = fetch_search_path(false);
+ 	if (search_path == NIL) /* probably can't happen */
+ 		schemaname = NULL;
+ 	else
+ 		schemaname = get_namespace_name(linitial_oid(search_path));
+ 
+ 	list_free(search_path);
+ 
+ 	return schemaname;
+ }
+ 
+ static char *
+ RangeVarToString(RangeVar *r)
+ {
+ 	char       *schemaname = RangeVarGetNamespace(r);
+ 	StringInfoData string;
+ 	initStringInfo(&string);
+ 
+ 	if (r->catalogname != NULL)
+ 	{
+ 		appendStringInfoString(&string, quote_identifier(r->catalogname));
+ 		appendStringInfoChar(&string, '.');
+ 	}
+ 	if (schemaname != NULL)
+ 	{
+ 		appendStringInfoString(&string, quote_identifier(schemaname));
+ 		appendStringInfoChar(&string, '.');
+ 	}
+ 	appendStringInfoString(&string, quote_identifier(r->relname));
+ 
+ 	return string.data;
+ }
+ 
+ static const char *
+ objTypeToString(ObjectType objtype)
+ {
+ 	switch (objtype)
+ 	{
+ 		case OBJECT_AGGREGATE:
+ 			return "AGGREGATE";
+ 		case OBJECT_ATTRIBUTE:
+ 			return "ATTRIBUTE";
+ 		case OBJECT_CAST:
+ 			return "CAST";
+ 		case OBJECT_COLUMN:
+ 			return "COLUMN";
+ 		case OBJECT_CONSTRAINT:
+ 			return "CONSTRAINT";
+ 		case OBJECT_COLLATION:
+ 			return "COLLATION";
+ 		case OBJECT_CONVERSION:
+ 			return "CONVERSION";
+ 		case OBJECT_DATABASE:
+ 			return "DATABASE";
+ 		case OBJECT_DOMAIN:
+ 			return "DOMAIN";
+ 		case OBJECT_EVENT_TRIGGER:
+ 			return "EVENT TRIGGER";
+ 		case OBJECT_EXTENSION:
+ 			return "EXTENSION";
+ 		case OBJECT_FDW:
+ 			return "FDW";
+ 		case OBJECT_FOREIGN_SERVER:
+ 			return "FOREIGN SERVER";
+ 		case OBJECT_FOREIGN_TABLE:
+ 			return "FOREIGN TABLE";
+ 		case OBJECT_FUNCTION:
+ 			return "FUNCTION";
+ 		case OBJECT_INDEX:
+ 			return "INDEX";
+ 		case OBJECT_LANGUAGE:
+ 			return "LANGUAGE";
+ 		case OBJECT_LARGEOBJECT:
+ 			return "LARGE OBJECT";
+ 		case OBJECT_MATVIEW:
+ 			return "MATERIALIZED VIEW";
+ 		case OBJECT_OPCLASS:
+ 			return "OPERATOR CLASS";
+ 		case OBJECT_OPERATOR:
+ 			return "OPERATOR";
+ 		case OBJECT_OPFAMILY:
+ 			return "OPERATOR FAMILY";
+ 		case OBJECT_ROLE:
+ 			return "ROLE";
+ 		case OBJECT_RULE:
+ 			return "RULE";
+ 		case OBJECT_SCHEMA:
+ 			return "SCHEMA";
+ 		case OBJECT_SEQUENCE:
+ 			return "SEQUENCE";
+ 		case OBJECT_TABLE:
+ 			return "TABLE";
+ 		case OBJECT_TABLESPACE:
+ 			return "TABLESPACE";
+ 		case OBJECT_TRIGGER:
+ 			return "TRIGGER";
+ 		case OBJECT_TSCONFIGURATION:
+ 			return "TEXT SEARCH CONFIGURATION";
+ 		case OBJECT_TSDICTIONARY:
+ 			return "TEXT SEARCH DICTIONARY";
+ 		case OBJECT_TSPARSER:
+ 			return "TEXT SEARCH PARSER";
+ 		case OBJECT_TSTEMPLATE:
+ 			return "TEXT SEARCH TEMPLATE";
+ 		case OBJECT_TYPE:
+ 			return "TYPE";
+ 		case OBJECT_VIEW:
+ 			return "VIEW";
+ 		default:
+ 			elog(ERROR, "unrecognized object type: %d", objtype);
+ 			return NULL;		/* silence compiler */
+ 	}
+ }
+ 
+ static void
+ maybeAddSeparator(StringInfo buf, const char *sep, bool *first)
+ {
+ 	if (*first)
+ 		*first = false;
+ 	else        appendStringInfoString(buf, sep);
+ }
+ 
+ /*
+  * 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 the return type of the expression.
+  */
+ 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.
+ 	 */
+ 	expr = transformExpr(pstate, raw_expr, kind);
+ 
+ 	/*
+ 	 * Take care of collations.
+ 	 */
+ 	assign_expr_collations(pstate, expr);
+ 
+ 	src = deparse_expression(expr, dpcontext, false, false);
+ 
+ 	free_parsestate(pstate);
+ 
+ 	MemoryContextSwitchTo(oldcxt);
+ 
+ 	/* copy to destination context before destroying our own */
+ 	/* XXX maybe we should switch just before deparse_expression? */
+ 	src = pstrdup(src);
+ 	MemoryContextDelete(cxt);
+ 
+ 	return src;
+ }
+ 
+ /*
+  * rewrite any_name parser production
+  */
+ static void
+ _rwAnyName(StringInfo buf, List *name)
+ {
+ 	bool first = true;
+ 	ListCell *lc;
+ 
+ 	foreach(lc, name)
+ 	{
+ 		Value *member = (Value *) lfirst(lc);
+ 
+ 		maybeAddSeparator(buf, ".", &first);
+ 		appendStringInfo(buf, "%s", member->val.str);
+ 	}
+ }
+ 
+ static char *
+ _rwCreateExtensionStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+ 	StringInfoData buf;
+ 	ListCell   *lc;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfo(&buf, "CREATE EXTENSION%s %s",
+ 					 node->if_not_exists ? " IF NOT EXISTS" : "",
+ 					 identity);
+ 
+ 	foreach(lc, node->options)
+ 	{
+ 		DefElem    *defel = (DefElem *) lfirst(lc);
+ 
+ 		if (strcmp(defel->defname, "schema") == 0)
+ 			appendStringInfo(&buf, " SCHEMA %s", strVal(defel->arg));
+ 
+ 		else if (strcmp(defel->defname, "new_version") == 0)
+ 			appendStringInfo(&buf, " VERSION %s", strVal(defel->arg));
+ 
+ 		else if (strcmp(defel->defname, "old_version") == 0)
+ 			appendStringInfo(&buf, " FROM %s", strVal(defel->arg));
+ 	}
+ 
+ 	return buf.data;
+ }
+ 
+ static char *
+ _rwViewStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	ViewStmt *node = (ViewStmt *) parsetree;
+ 	StringInfoData buf;
+ 	Query	   *viewParse;
+ 	char	   *viewsrc;
+ 
+ 	initStringInfo(&buf);
+ 	viewParse = parse_analyze(node->query,
+ 							  "(unavailable source text)", NULL, 0);
+ 
+ 	appendStringInfo(&buf, "CREATE %sVIEW %s AS ",
+ 					 node->replace? "OR REPLACE": "",
+ 					 identity);
+ 
+ 	viewsrc = pg_get_viewstmt_definition(viewParse);
+ 	appendStringInfoString(&buf, viewsrc);
+ 	pfree(viewsrc);
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * Rewrite a OptTableSpace: grammar production
+  */
+ static void
+ _rwOptTableSpace(StringInfo buf, const char *name)
+ {
+ 	if (name != NULL)
+ 		appendStringInfo(buf, " TABLESPACE %s", name);
+ }
+ 
+ /*
+  * Rewrite a OptConsTableSpace: grammar production
+  */
+ static void
+ _rwOptConsTableSpace(StringInfo buf, const char *name)
+ {
+ 	if (name != NULL)
+ 		appendStringInfo(buf, " USING INDEX TABLESPACE %s", name);
+ }
+ 
+ /*
+  * Rewrite a generic def_arg: grammar production
+  */
+ static void
+ _rwDefArg(StringInfo buf, Node *arg)
+ {
+ 	appendStringInfoChar(buf, '?');
+ }
+ 
+ /*
+  * Rewrite a generic definition grammar production
+  */
+ static void
+ _rwDefinition(StringInfo buf, List *definitions)
+ {
+ 	/* FIXME: needs an option to print () when empty? */
+ 	if (definitions != NULL)
+ 	{
+ 		ListCell *k;
+ 		bool first = true;
+ 
+ 		appendStringInfoChar(buf, '(');
+ 		foreach(k, definitions)
+ 		{
+ 			DefElem *def = (DefElem *) lfirst(k);
+ 
+ 			elog(LOG, "dumping definition element: %s",
+ 				 nodeToString(def));
+ 
+ 			maybeAddSeparator(buf, ", ", &first);
+ 
+ 			/* ColLabel */
+ 			appendStringInfo(buf, "%s", def->defname);
+ 			/* [ '=' def_arg ] */
+ 			if (def->arg)
+ 			{
+ 				appendStringInfoChar(buf, '=');
+ 				_rwDefArg(buf, def->arg);
+ 			}
+ 
+ 		}
+ 		appendStringInfoChar(buf, ')');
+ 	}
+ }
+ 
+ /*
+  * Rewrite the opt_column_list: grammar production
+  */
+ static void
+ _rwOptColumnList(StringInfo buf, List *clist)
+ {
+ 	if (clist != NIL)
+ 	{
+ 		ListCell *c;
+ 		bool first = true;
+ 
+ 		appendStringInfoChar(buf, '(');
+ 		foreach(c, clist)
+ 		{
+ 			maybeAddSeparator(buf, ",", &first);
+ 			appendStringInfo(buf, "%s", strVal(lfirst(c)));
+ 		}
+ 		appendStringInfoChar(buf, ')');
+ 	}
+ }
+ 
+ /*
+  * Rewrite the opt_column_list: grammar production
+  */
+ static void
+ _rwColumnList(StringInfo buf, List *clist)
+ {
+ 	if (clist == NIL)
+ 		appendStringInfo(buf, "()");
+ 	else
+ 		_rwOptColumnList(buf, clist);
+ }
+ 
+ /*
+  * Rewrite the key_match: grammar production
+  */
+ static void
+ _rwKeyMatch(StringInfo buf, int matchtype)
+ {
+ 	switch (matchtype)
+ 	{
+ 		case FKCONSTR_MATCH_FULL:
+ 			appendStringInfo(buf, " MATCH FULL");
+ 			break;
+ 
+ 		case FKCONSTR_MATCH_PARTIAL:
+ 			/* should not happen, not yet implemented */
+ 			appendStringInfo(buf, " MATCH PARTIAL");
+ 			break;
+ 
+ 		case FKCONSTR_MATCH_SIMPLE:
+ 		default:
+ 			appendStringInfo(buf, " MATCH SIMPLE");
+ 			break;
+ 	}
+ }
+ 
+ /*
+  * Rewrite the key_action: grammar production
+  */
+ static void
+ _rwKeyAction(StringInfo buf, int action)
+ {
+ 	switch (action)
+ 	{
+ 		case FKCONSTR_ACTION_NOACTION:
+ 			appendStringInfo(buf, " NO ACTION");
+ 			break;
+ 
+ 		case FKCONSTR_ACTION_RESTRICT:
+ 			appendStringInfo(buf, " RESTRICT");
+ 			break;
+ 
+ 		case FKCONSTR_ACTION_CASCADE:
+ 			appendStringInfo(buf, " CASCADE");
+ 			break;
+ 
+ 		case FKCONSTR_ACTION_SETNULL:
+ 			appendStringInfo(buf, " SET NULL");
+ 			break;
+ 
+ 		case FKCONSTR_ACTION_SETDEFAULT:
+ 			appendStringInfo(buf, " SET DEFAULT");
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unexpected foreign key action: %d", action);
+ 			break;
+ 	}
+ }
+ 
+ static void
+ _rwKeyActions(StringInfo buf, int upd_action, int del_action)
+ {
+ 	appendStringInfo(buf, " ON UPDATE");
+ 	_rwKeyAction(buf, upd_action);
+ 
+ 	appendStringInfo(buf, " ON DELETE");
+ 	_rwKeyAction(buf, del_action);
+ }
+ 
+ /*
+  * Rewrite the ConstraintAttributeSpec parser production
+  */
+ static void
+ _rwConstraintAttributeSpec(StringInfo buf,
+ 						   bool deferrable, bool initdeferred)
+ {
+ 	if (deferrable)
+ 		appendStringInfo(buf, " DEFERRABLE");
+ 	else
+ 		appendStringInfo(buf, " NOT DEFERRABLE");
+ 
+ 	if (initdeferred)
+ 		appendStringInfo(buf, " INITIALLY DEFERRED");
+ 	else
+ 		appendStringInfo(buf, " INITIALLY IMMEDIATE");
+ }
+ 
+ static void
+ _rwRelPersistence(StringInfo buf, int relpersistence)
+ {
+ 	switch (relpersistence)
+ 	{
+ 		case RELPERSISTENCE_TEMP:
+ 			appendStringInfo(buf, " TEMPORARY");
+ 			break;
+ 
+ 		case RELPERSISTENCE_UNLOGGED:
+ 			appendStringInfo(buf, " UNLOGGED");
+ 			break;
+ 
+ 		case RELPERSISTENCE_PERMANENT:
+ 		default:
+ 			break;
+ 	}
+ }
+ 
+ /*
+  * rewrite the ColConstraintElem grammar production
+  *
+  * Not all constraint types can be expected here, and some of them can be found
+  * with other grammars as table level constraint attributes.
+  */
+ static void
+ _rwColConstraintElem(StringInfo buf, Oid objectId, List *constraints,
+ 					 RangeVar *relation)
+ {
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, constraints)
+ 	{
+ 		Constraint *c = (Constraint *) lfirst(lc);
+ 		Assert(IsA(c, Constraint));
+ 
+ 		if (c->conname != NULL)
+ 			appendStringInfo(buf, " CONSTRAINT %s", c->conname);
+ 
+ 		switch (c->contype)
+ 		{
+ 			case CONSTR_NOTNULL:
+ 				appendStringInfo(buf, " NOT NULL");
+ 				break;
+ 
+ 			case CONSTR_NULL:
+ 				appendStringInfo(buf, " NULL");
+ 				break;
+ 
+ 			case CONSTR_UNIQUE:
+ 				appendStringInfo(buf, " UNIQUE");
+ 				_rwOptConsTableSpace(buf, c->indexspace);
+ 				break;
+ 
+ 			case CONSTR_PRIMARY:
+ 				appendStringInfo(buf, " PRIMARY KEY");
+ 				_rwDefinition(buf, c->options);
+ 				_rwOptConsTableSpace(buf, c->indexspace);
+ 				break;
+ 
+ 			case CONSTR_CHECK:
+ 				{
+ 					char *src;
+ 
+ 					src = uncookConstraintOrDefault(c->raw_expr,
+ 											  EXPR_KIND_CHECK_CONSTRAINT,
+ 											  relation,
+ 											  objectId);
+ 					appendStringInfo(buf, " CHECK (%s)", src);
+ 					pfree(src);
+ 					break;
+ 				}
+ 
+ 			case CONSTR_DEFAULT:
+ 			{
+ 				if (c->cooked_expr)
+ 				{
+ 					List		*dpcontext;
+ 					Node		*expr = (Node *) stringToNode(c->cooked_expr);
+ 					char		*consrc;
+ 
+ 					dpcontext = deparse_context_for(relation->relname,
+ 													objectId);
+ 					consrc = deparse_expression(expr, dpcontext, false, false);
+ 
+ 					appendStringInfo(buf, " DEFAULT %s", consrc);
+ 				}
+ 				else if (c->raw_expr)
+ 				{
+ 					char   *src;
+ 
+ 					/* deparse the default expression */
+ 					src = uncookConstraintOrDefault(c->raw_expr,
+ 													EXPR_KIND_COLUMN_DEFAULT,
+ 													relation, objectId);
+ 
+ 					appendStringInfo(buf, " DEFAULT %s", src);
+ 				}
+ 				break;
+ 			}
+ 
+ 			case CONSTR_FOREIGN:
+ 			{
+ 				appendStringInfo(buf, " REFERENCES %s ",
+ 								 RangeVarToString(c->pktable));
+ 				_rwOptColumnList(buf, c->pk_attrs);
+ 				_rwKeyMatch(buf, c->fk_matchtype);
+ 				_rwKeyActions(buf, c->fk_upd_action, c->fk_del_action);
+ 				break;
+ 			}
+ 
+ 			default:
+ 				/* unexpected case, WARNING? */
+ 				elog(WARNING, "constraint %d is not a column constraint",
+ 					 c->contype);
+ 				break;
+ 		}
+ 	}
+ }
+ 
+ /*
+  * rewrite a list of TableConstraint: grammar production
+  */
+ static void
+ _rwTableConstraint(StringInfo buf, Oid objectId, List *constraints,
+ 				   RangeVar *relation)
+ {
+ 	ListCell   *lc;
+ 	List       *context = NIL;
+ 
+ 	foreach(lc, constraints)
+ 	{
+ 		Constraint *c = (Constraint *) lfirst(lc);
+ 		Assert(IsA(c, Constraint));
+ 
+ 		if (c->conname != NULL)
+ 			appendStringInfo(buf, " CONSTRAINT %s", c->conname);
+ 
+ 		switch (c->contype)
+ 		{
+ 			case CONSTR_CHECK:
+ 				{
+ 					char *src;
+ 
+ 					src = uncookConstraintOrDefault(c->raw_expr,
+ 													EXPR_KIND_CHECK_CONSTRAINT,
+ 													relation,
+ 											  objectId);
+ 					appendStringInfo(buf, " CHECK (%s)", src);
+ 					pfree(src);
+ 				}
+ 				break;
+ 
+ 			case CONSTR_UNIQUE:
+ 				appendStringInfo(buf, " UNIQUE");
+ 
+ 				if (c->keys)
+ 				{
+ 					/* unique (column, list) */
+ 					_rwColumnList(buf, c->keys);
+ 					_rwDefinition(buf, c->options);
+ 					_rwConstraintAttributeSpec(buf,
+ 											   c->deferrable, c->initdeferred);
+ 					_rwOptConsTableSpace(buf, c->indexspace);
+ 				}
+ 				else
+ 				{
+ 					/* unique using index */
+ 					appendStringInfo(buf, " USING INDEX %s", c->indexname);
+ 					_rwConstraintAttributeSpec(buf,
+ 											   c->deferrable, c->initdeferred);
+ 				}
+ 				break;
+ 
+ 			case CONSTR_PRIMARY:
+ 				appendStringInfo(buf, " PRIMARY KEY");
+ 
+ 				if (c->keys)
+ 				{
+ 					/* primary key (column, list) */
+ 					_rwColumnList(buf, c->keys);
+ 					_rwDefinition(buf, c->options);
+ 					_rwOptConsTableSpace(buf, c->indexspace);
+ 					_rwConstraintAttributeSpec(buf,
+ 											   c->deferrable, c->initdeferred);
+ 				}
+ 				else
+ 				{
+ 					/* primary key using index */
+ 					appendStringInfo(buf, " USING INDEX %s", c->indexname);
+ 					_rwConstraintAttributeSpec(buf,
+ 											   c->deferrable, c->initdeferred);
+ 				}
+ 				break;
+ 
+ 			case CONSTR_EXCLUSION:
+ 				appendStringInfo(buf, " EXCLUDE %s ", c->access_method);
+ 
+ 				if (c->exclusions == NULL)
+ 					appendStringInfo(buf, "()");
+ 				else
+ 				{
+ 					/* ExclusionConstraintList */
+ 					ListCell *e;
+ 					bool first = true;
+ 
+ 					appendStringInfoChar(buf, '(');
+ 					foreach(e, c->exclusions)
+ 					{
+ 						List *ec = (List *)lfirst(e);
+ 
+ 						maybeAddSeparator(buf, ",", &first);
+ 
+ 						/* ExclustionConstraintElem */
+ 						appendStringInfo(buf, "%s WITH OPERATOR(%s)",
+ 										 strVal(linitial(ec)),
+ 										 strVal(lsecond(ec)));
+ 					}
+ 					appendStringInfoChar(buf, ')');
+ 				}
+ 				_rwDefinition(buf, c->options);
+ 				_rwOptConsTableSpace(buf, c->indexspace);
+ 
+ 				/* ExclusionWhereClause: */
+ 				if (c->where_clause)
+ 				{
+ 					char *str;
+ 
+ 					if (context == NIL)
+ 						context = deparse_context_for(relation->relname,
+ 													  objectId);
+ 
+ 					str = deparse_expression(c->where_clause, context,
+ 											 false, false);
+ 
+ 					appendStringInfo(buf, " WHERE (%s)", str);
+ 				}
+ 
+ 				_rwConstraintAttributeSpec(buf,
+ 										   c->deferrable, c->initdeferred);
+ 				break;
+ 
+ 			case CONSTR_FOREIGN:
+ 				appendStringInfoString(buf, " FOREIGN KEY");
+ 
+ 				_rwColumnList(buf, c->fk_attrs);
+ 
+ 				appendStringInfo(buf, " REFERENCES %s",
+ 								 RangeVarToString(c->pktable));
+ 
+ 				_rwOptColumnList(buf, c->pk_attrs);
+ 
+ 				_rwKeyMatch(buf, c->fk_matchtype);
+ 				_rwKeyActions(buf, c->fk_upd_action, c->fk_del_action);
+ 
+ 				_rwConstraintAttributeSpec(buf,
+ 										   c->deferrable, c->initdeferred);
+ 
+ 				if (c->skip_validation)
+ 					appendStringInfoString(buf, " NOT VALID");
+ 				break;
+ 
+ 			default:
+ 				/* unexpected case, WARNING? */
+ 				elog(WARNING, "constraint %d is not a column constraint",
+ 					 c->contype);
+ 				break;
+ 		}
+ 	}
+ }
+ 
+ /*
+  * rewrite TableLikeOptionList parser production
+  */
+ static void
+ _rwTableLikeOptionList(StringInfo buf, bits32 options)
+ {
+ 	if (options == CREATE_TABLE_LIKE_ALL)
+ 		appendStringInfo(buf, " INCLUDING ALL");
+ 	else
+ 	{
+ 		if (options & CREATE_TABLE_LIKE_DEFAULTS)
+ 			appendStringInfo(buf, " INCLUDING DEFAULTS");
+ 
+ 		if (options & CREATE_TABLE_LIKE_CONSTRAINTS)
+ 			appendStringInfo(buf, " INCLUDING CONSTRAINTS");
+ 
+ 		if (options & CREATE_TABLE_LIKE_INDEXES)
+ 			appendStringInfo(buf, " INCLUDING INDEXES");
+ 
+ 		if (options & CREATE_TABLE_LIKE_STORAGE)
+ 			appendStringInfo(buf, " INCLUDING STORAGE");
+ 
+ 		if (options & CREATE_TABLE_LIKE_COMMENTS)
+ 			appendStringInfo(buf, " INCLUDING COMMENTS");
+ 	}
+ }
+ 
+ /*
+  * rewrite OptTableElementList parser production
+  */
+ static void
+ _rwOptTableElementList(StringInfo buf, Oid objectId, List *tableElts,
+ 					   RangeVar *relation)
+ {
+ 	bool        first = true;
+ 	ListCell   *e;
+ 
+ 	appendStringInfoChar(buf, '(');
+ 
+ 	foreach(e, tableElts)
+ 	{
+ 		Node *elt = (Node *) lfirst(e);
+ 
+ 		maybeAddSeparator(buf, ", ", &first);
+ 
+ 		switch (nodeTag(elt))
+ 		{
+ 			case T_ColumnDef:
+ 			{
+ 				ColumnDef  *c = (ColumnDef *) elt;
+ 
+ 				appendStringInfo(buf, "%s %s",
+ 								 c->colname,
+ 								 TypeNameToString(c->typeName));
+ 				/* XXX this might need conditional supression in some cases */
+ 				_rwColConstraintElem(buf, objectId, c->constraints, relation);
+ 				break;
+ 			}
+ 			case T_TableLikeClause:
+ 			{
+ 				TableLikeClause *like = (TableLikeClause *) elt;
+ 				appendStringInfo(buf, "LIKE %s",
+ 								 RangeVarToString(like->relation));
+ 				_rwTableLikeOptionList(buf, like->options);
+ 				break;
+ 			}
+ 			case T_Constraint:
+ 			{
+ 				Constraint  *c = (Constraint *) elt;
+ 				_rwTableConstraint(buf, objectId, list_make1(c), relation);
+ 				break;
+ 			}
+ 			default:
+ 				break;
+ 		}
+ 	}
+ 	appendStringInfoChar(buf, ')');
+ }
+ 
+ /*
+  * rewrite OptTypedTableElementList parser production
+  */
+ static void
+ _rwOptTypedTableElementList(StringInfo buf, Oid objectId, List *tableElts,
+ 							RangeVar *relation)
+ {
+ 	bool        first = true,
+ 				parens = false;
+ 	ListCell   *e;
+ 
+ 	foreach(e, tableElts)
+ 	{
+ 		Node *elt = (Node *) lfirst(e);
+ 
+ 		switch (nodeTag(elt))
+ 		{
+ 			case T_ColumnDef:
+ 				{
+ 					ColumnDef  *c = (ColumnDef *) elt;
+ 
+ 					if (c->constraints)
+ 					{
+ 						maybeAddSeparator(buf, ",", &first);
+ 
+ 						/* only add parens if we have columns with options */
+ 						if (!parens)
+ 						{
+ 							appendStringInfoChar(buf, '(');
+ 							parens = true;
+ 						}
+ 						appendStringInfo(buf, " %s WITH OPTIONS", c->colname);
+ 						_rwColConstraintElem(buf, objectId, c->constraints,
+ 											 relation);
+ 					}
+ 					break;
+ 				}
+ 			case T_Constraint:
+ 				{
+ 					Constraint  *c = (Constraint *) elt;
+ 
+ 					_rwTableConstraint(buf, objectId, list_make1(c), relation);
+ 					break;
+ 				}
+ 			default:
+ 				break;
+ 		}
+ 	}
+ 
+ 	if (parens)
+ 		appendStringInfoChar(buf, ')');
+ }
+ 
+ /*
+  * rewrite OptInherit parser production
+  */
+ static void
+ _rwOptInherit(StringInfo buf, List *inhRelations)
+ {
+ 	if (inhRelations)
+ 	{
+ 		ListCell *lc;
+ 		bool first = true;
+ 
+ 		appendStringInfo(buf, " INHERITS (");
+ 		foreach(lc, inhRelations)
+ 		{
+ 			RangeVar   *inh = (RangeVar *) lfirst(lc);
+ 
+ 			maybeAddSeparator(buf, ",", &first);
+ 			appendStringInfo(buf, "%s", RangeVarToString(inh));
+ 		}
+ 		appendStringInfoChar(buf, ')');
+ 	}
+ }
+ 
+ /*
+  * rewrite reloptions parser production
+  */
+ static void
+ _rwRelOptions(StringInfo buf, List *options, bool null_is_true)
+ {
+ 	bool        first = true;
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, options)
+ 	{
+ 		DefElem    *def = (DefElem *) lfirst(lc);
+ 		const char *value;
+ 
+ 		maybeAddSeparator(buf, ", ", &first);
+ 
+ 		if (def->arg != NULL)
+ 		{
+ 			/* ... defname = value */
+ 			value = defGetString(def);
+ 			appendStringInfo(buf, "%s=%s", def->defname, value);
+ 		}
+ 		else
+ 		{
+ 			if (null_is_true)
+ 			{
+ 				/* ... defname = true */
+ 				appendStringInfo(buf, "%s=true", def->defname);
+ 			}
+ 			else
+ 			{
+ 				/* ... defname */
+ 				appendStringInfo(buf, "%s", def->defname);
+ 			}
+ 		}
+ 	}
+ }
+ 
+ /*
+  * rewrite OptWith parser production
+  */
+ static void
+ _rwOptWith(StringInfo buf, List *options)
+ {
+ 	if (options)
+ 	{
+ 		appendStringInfoString(buf, " WITH (");
+ 		_rwRelOptions(buf, options, true);
+ 		appendStringInfoChar(buf, ')');
+ 	}
+ }
+ 
+ /*
+  * rewrite OptCommitOption parser production
+  */
+ static void
+ _rwOnCommitOption(StringInfo buf, int oncommit)
+ {
+ 	switch (oncommit)
+ 	{
+ 		case ONCOMMIT_DROP:
+ 			appendStringInfo(buf, " ON COMMIT DROP");
+ 			break;
+ 
+ 		case ONCOMMIT_DELETE_ROWS:
+ 			appendStringInfo(buf, " ON COMMIT DELETE ROWS");
+ 			break;
+ 
+ 		case ONCOMMIT_PRESERVE_ROWS:
+ 			appendStringInfo(buf, " ON COMMIT PRESERVE ROWS");
+ 			break;
+ 
+ 		case ONCOMMIT_NOOP:
+ 			/* EMPTY */
+ 			break;
+ 
+ 	}
+ }
+ 
+ /*
+  * rewrite CreateStmt parser production
+  */
+ static char *
+ _rwCreateStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	CreateStmt *node = (CreateStmt *) parsetree;
+ 	StringInfoData buf;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfoString(&buf, "CREATE");
+ 	_rwRelPersistence(&buf, node->relation->relpersistence);
+ 	appendStringInfo(&buf, " TABLE %s %s", identity,
+ 					 node->if_not_exists ? " IF NOT EXISTS" : "");
+ 
+ 	if (node->ofTypename)
+ 	{
+ 		appendStringInfo(&buf, "OF %s",
+ 						 TypeNameToString(node->ofTypename));
+ 		_rwOptTypedTableElementList(&buf, objectId, node->tableElts,
+ 									node->relation);
+ 	}
+ 	else
+ 	{
+ 		List *elts = list_concat(node->tableElts, node->constraints);
+ 
+ 		_rwOptTableElementList(&buf, objectId, elts, node->relation);
+ 		_rwOptInherit(&buf, node->inhRelations);
+ 	}
+ 
+ 	_rwOptWith(&buf, node->options);
+ 	_rwOnCommitOption(&buf, node->oncommit);
+ 	_rwOptTableSpace(&buf, node->tablespacename);
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * rewrite OptSeqOptList parser production
+  */
+ static void
+ _rwOptSeqOptList(StringInfo buf, List *options)
+ {
+ 	ListCell *opt;
+ 
+ 	foreach(opt, options)
+ 	{
+ 		DefElem    *defel = (DefElem *) lfirst(opt);
+ 
+ 		if (strcmp(defel->defname, "cache") == 0)
+ 		{
+ 			char		num[100];
+ 
+ 			snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel));
+ 			appendStringInfo(buf, " CACHE %s", num);
+ 		}
+ 		else if (strcmp(defel->defname, "cycle") == 0)
+ 		{
+ 			if (intVal(defel))
+ 				appendStringInfo(buf, " CYCLE");
+ 			else
+ 				appendStringInfo(buf, " NO CYCLE");
+ 		}
+ 		else if (strcmp(defel->defname, "increment") == 0)
+ 		{
+ 			char		num[100];
+ 
+ 			snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel));
+ 			appendStringInfo(buf, " INCREMENT BY %s", num);
+ 		}
+ 		else if (strcmp(defel->defname, "maxvalue") == 0)
+ 		{
+ 			if (defel->arg)
+ 			{
+ 				char		num[100];
+ 
+ 				snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel));
+ 				appendStringInfo(buf, " MAXVALUE %s", num);
+ 			}
+ 			else
+ 				appendStringInfo(buf, " NO MAXVALUE");
+ 		}
+ 		else if (strcmp(defel->defname, "minvalue") == 0)
+ 		{
+ 			if (defel->arg)
+ 			{
+ 				char		num[100];
+ 
+ 				snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel));
+ 				appendStringInfo(buf, " MINVALUE %s", num);
+ 			}
+ 			else
+ 				appendStringInfo(buf, " NO MINVALUE");
+ 		}
+ 		else if (strcmp(defel->defname, "owned_by") == 0)
+ 		{
+ 			List       *owned_by = defGetQualifiedName(defel);
+ 			int         nnames = list_length(owned_by);
+ 			List	   *relname;
+ 			char	   *attrname;
+ 			RangeVar   *rel;
+ 
+ 			relname = list_truncate(list_copy(owned_by), nnames - 1);
+ 			attrname = strVal(lfirst(list_tail(owned_by)));
+ 			rel = makeRangeVarFromNameList(relname);
+ 
+ 			appendStringInfo(buf, " OWNED BY %s.%s",
+ 							 RangeVarToString(rel), attrname);
+ 		}
+ 		else if (strcmp(defel->defname, "start") == 0)
+ 		{
+ 			char		num[100];
+ 
+ 			snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel));
+ 			appendStringInfo(buf, " START WITH %s", num);
+ 		}
+ 		else if (strcmp(defel->defname, "restart") == 0)
+ 		{
+ 			if (defel->arg)
+ 			{
+ 				char		num[100];
+ 
+ 				snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel));
+ 				appendStringInfo(buf, " RESTART WITH %s", num);
+ 			}
+ 			else
+ 				appendStringInfo(buf, " RESTART");
+ 		}
+ 	}
+ }
+ 
+ /*
+  * rewrite CreateSeqStmt parser production
+  */
+ static char *
+ _rwCreateSeqStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	CreateSeqStmt *node = (CreateSeqStmt *) parsetree;
+ 	StringInfoData buf;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfo(&buf, "CREATE");
+ 	_rwRelPersistence(&buf, node->sequence->relpersistence);
+ 	appendStringInfo(&buf, " SEQUENCE %s ", identity);
+ 	_rwOptSeqOptList(&buf, node->options);
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * rewrite index_elem parser production
+  */
+ static void
+ _rwIndexElem(StringInfo buf, IndexElem *e, List *context)
+ {
+ 	if (e->name)
+ 		appendStringInfo(buf, "%s", e->name);
+ 	else
+ 	{
+ 		char *str;
+ 
+ 		str = deparse_expression(e->expr, context, false, false);
+ 
+ 		/* Need parens if it's not a bare function call */
+ 		if (IsA(e->expr, FuncExpr) &&
+ 			((FuncExpr *) e->expr)->funcformat == COERCE_EXPLICIT_CALL)
+ 			appendStringInfo(buf, "%s", str);
+ 		else
+ 			appendStringInfo(buf, "(%s)", str);
+ 	}
+ 
+ 	if (e->collation)
+ 	{
+ 		appendStringInfo(buf, " COLLATE");
+ 		_rwAnyName(buf, e->collation);
+ 	}
+ 
+ 	if (e->opclass)
+ 	{
+ 		appendStringInfo(buf, " USING ");
+ 		_rwAnyName(buf, e->opclass);
+ 	}
+ 
+ 	/* defensive coding, so that the compiler hints us into updating those bits
+ 	 * if needs be */
+ 	switch (e->ordering)
+ 	{
+ 		/* using unexpected in create index */
+ 		case SORTBY_DEFAULT:
+ 		case SORTBY_USING:
+ 			break;
+ 
+ 		case SORTBY_ASC:
+ 			appendStringInfo(buf, " ASC");
+ 			break;
+ 
+ 		case SORTBY_DESC:
+ 			appendStringInfo(buf, " DESC");
+ 			break;
+ 	}
+ 	switch (e->nulls_ordering)
+ 	{
+ 		case SORTBY_NULLS_DEFAULT:
+ 			break;
+ 
+ 		case SORTBY_NULLS_FIRST:
+ 			appendStringInfo(buf, " NULLS FIRST");
+ 			break;
+ 
+ 		case SORTBY_NULLS_LAST:
+ 			appendStringInfo(buf, " NULLS LAST");
+ 			break;
+ 	}
+ }
+ 
+ /*
+  * rewrite IndexStmt parser production
+  */
+ static char *
+ _rwCreateIndexStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	IndexStmt			*node  = (IndexStmt *) parsetree;
+ 	StringInfoData		 buf;
+ 	Oid					 relId;
+ 	bool				 first = true;
+ 	ListCell			*lc;
+ 	List				*context;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfo(&buf, "CREATE%s INDEX", node->unique ? " UNIQUE" : "");
+ 
+ 	if (node->concurrent)
+ 		appendStringInfo(&buf, " CONCURRENTLY");
+ 
+ 	appendStringInfo(&buf, " %s ON %s USING %s (",
+ 					 identity,
+ 					 RangeVarToString(node->relation),
+ 					 node->accessMethod);
+ 
+ 	relId = IndexGetRelation(objectId, false);
+ 	context = deparse_context_for("", relId);	/* alias is not important */
+ 
+ 	foreach(lc, node->indexParams)
+ 	{
+ 		IndexElem *e = (IndexElem *) lfirst(lc);
+ 
+ 		maybeAddSeparator(&buf, ", ", &first);
+ 		_rwIndexElem(&buf, e, context);
+ 	}
+ 	appendStringInfoChar(&buf, ')');
+ 
+ 	_rwOptWith(&buf, node->options);
+ 	_rwOptConsTableSpace(&buf, node->tableSpace);
+ 
+ 	if (node->whereClause)
+ 	{
+ 		Node *transformed;
+ 		ParseState *pstate;
+ 		char *str;
+ 		RangeTblEntry *rte;
+ 
+ 		pstate = make_parsestate(NULL);
+ 		rte = addRangeTableEntry(pstate, node->relation,
+ 								 NULL, false, true);
+ 		addRTEtoQuery(pstate, rte, true, true, true);
+ 
+ 		transformed = transformExpr(pstate, node->whereClause,
+ 									EXPR_KIND_INDEX_PREDICATE);
+ 
+ 		str = deparse_expression(transformed, context, false, false);
+ 		/* deparse_expression adds outer parens */
+ 		appendStringInfo(&buf, " WHERE %s", str);
+ 	}
+ 
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * rewrite func_arg parser production
+  */
+ static void
+ _rwFuncArg(StringInfo buf, FunctionParameter *fp)
+ {
+ 	/* Parameter's mode */
+ 	switch (fp->mode)
+ 	{
+ 		case FUNC_PARAM_INOUT:
+ 			appendStringInfoString(buf, "IN OUT");
+ 			break;
+ 
+ 		case FUNC_PARAM_IN:
+ 			appendStringInfoString(buf, "IN");
+ 			break;
+ 
+ 		case FUNC_PARAM_OUT:
+ 			appendStringInfoString(buf, "OUT");
+ 			break;
+ 
+ 		case FUNC_PARAM_VARIADIC:
+ 			appendStringInfoString(buf, "VARIADIC");
+ 			break;
+ 
+ 		case FUNC_PARAM_TABLE:
+ 			elog(ERROR, "FUNC_PARAM_TABLE not expected in argument list");
+ 			break;
+ 	}
+ 
+ 	/* Parameter's name is optional */
+ 	if (fp->name)
+ 		appendStringInfo(buf, " %s", fp->name);
+ 
+ 	/* Parameter's type name is not */
+ 	appendStringInfo(buf, " %s", TypeNameToString(fp->argType));
+ 
+ 	if (fp->defexpr)
+ 	{
+ 		char *src;
+ 
+ 		src = uncookConstraintOrDefault(fp->defexpr,
+ 										EXPR_KIND_FUNCTION_DEFAULT,
+ 										NULL, InvalidOid);
+ 
+ 		appendStringInfo(buf, " DEFAULT %s", src);
+ 		pfree(src);
+ 	}
+ }
+ 
+ /*
+  * rewrite CreateFunctionStmt parser production
+  */
+ static char *
+ _rwCreateFunctionStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	CreateFunctionStmt *node  = (CreateFunctionStmt *) parsetree;
+ 	StringInfoData	buf;
+ 	HeapTuple		funcTup;
+ 	Form_pg_proc	func;
+ 	char		   *schema;
+ 	char		   *qualifiedName;
+ 
+ #if 0
+ 	char				*fname, *nspname;
+ 	Oid					 namespaceId, languageOid;
+ 	ListCell			*x;
+ 	Oid					 prorettype;
+ 	bool				 returnsSet;
+ 	List				*as_clause;
+ 	char				*language;
+ 	bool				 isWindowFunc, isStrict, security, isLeakProof;
+ 	char				 volatility = PROVOLATILE_VOLATILE;
+ 	ArrayType			*proconfig;
+ 	float4				 procost = -1;
+ 	float4				 prorows = -1;
+ 	char                *procost_str, *prorows_str;
+ 	char				*probin_str;
+ 	char				*prosrc_str;
+ 	HeapTuple			 languageTuple;
+ #endif
+ 
+ 	initStringInfo(&buf);
+ 
+ 	/*
+ 	 * We cannot use the passed identity here: it includes only IN parameters,
+ 	 * but that's not sufficient to write a CREATE FUNCTION command.  So
+ 	 * fetch the name on our own.
+ 	 */
+ 	funcTup = SearchSysCache1(PROCOID, objectId);
+ 	func = (Form_pg_proc) GETSTRUCT(funcTup);
+ 
+ 	schema = get_namespace_name(func->pronamespace);
+ 	qualifiedName = quote_qualified_identifier(schema,
+ 											   NameStr(func->proname));
+ 	appendStringInfo(&buf, "CREATE FUNCTION %s ",
+ 					 qualifiedName);
+ 	pfree(schema);
+ 	pfree(qualifiedName);
+ 
+ 	/* Emit complete parameter list */
+ 	if (node->parameters)
+ 	{
+ 		ListCell   *lc;
+ 		bool		first = true;
+ 
+ 		appendStringInfoChar(&buf, '(');
+ 
+ 		foreach(lc, node->parameters)
+ 		{
+ 			FunctionParameter *fp = (FunctionParameter *) lfirst(lc);
+ 
+ 			maybeAddSeparator(&buf, ", ", &first);
+ 			/* direction, name, type, default */
+ 			_rwFuncArg(&buf, fp);
+ 		}
+ 		appendStringInfoChar(&buf, ')');
+ 	}
+ 
+ #if 0
+ 
+ 	/* return type */
+ 	compute_return_type(node->returnType, languageOid, &prorettype, &returnsSet);
+ 	appendStringInfo(&buf, " RETURNS %s", TypeNameToString(node->returnType));
+ 
+ 	/* get options and attributes */
+ 	compute_attributes_sql_style(node->options,
+ 								 &as_clause, &language,
+ 								 &isWindowFunc, &volatility,
+ 								 &isStrict, &security, &isLeakProof,
+ 								 &proconfig, &procost, &prorows);
+ 
+ 	languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
+ 	languageOid =  HeapTupleGetOid(languageTuple);
+ 	ReleaseSysCache(languageTuple);
+ 
+ 	/* also get the function's body */
+ 	interpret_AS_clause(languageOid, language, fname, as_clause,
+ 						&prosrc_str, &probin_str,
+ 						returnsSet, &procost, &prorows);
+ 
+ 	/* language */
+ 	appendStringInfo(&buf, " LANGUAGE %s", language);
+ 
+ 	/* options */
+ 	if (isWindowFunc)
+ 		appendStringInfo(&buf, " WINDOW");
+ 
+ 	switch (volatility)
+ 	{
+ 		case PROVOLATILE_IMMUTABLE:
+ 			appendStringInfo(&buf, " IMMUTABLE");
+ 			break;
+ 		case PROVOLATILE_STABLE:
+ 			appendStringInfo(&buf, " STABLE");
+ 			break;
+ 		case PROVOLATILE_VOLATILE:
+ 			appendStringInfo(&buf, " VOLATILE");
+ 			break;
+ 	}
+ 	appendStringInfo(&buf, " %sLEAKPROOF", isLeakProof ? "" : "NOT ");
+ 
+ 	if (isStrict)
+ 		appendStringInfo(&buf, " RETURNS NULL ON NULL INPUT");
+ 	else
+ 		appendStringInfo(&buf, " CALLED ON NULL INPUT");
+ 
+ 	/* friendly output for cost and rows */
+ 	procost_str = DatumGetCString(
+ 		DirectFunctionCall1(float4out, Float4GetDatum(procost)));
+ 	prorows_str = DatumGetCString(
+ 		DirectFunctionCall1(float4out, Float4GetDatum(prorows)));
+ 
+ 	appendStringInfo(&buf, " COST %s", procost_str);
+ 	appendStringInfo(&buf, " ROWS %s", prorows_str);
+ 
+ 	/* body */
+ 	appendStringInfo(&buf, " AS $%s$ %s $%s$;", fname, prosrc_str, fname);
+ #endif
+ 
+ 	ReleaseSysCache(funcTup);
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * rewrite CreateSchemaStmt parser production
+  *
+  * We don't bother with OptSchemaEltList, those will get back each separately
+  * as new ProcessUtility queries with a SUBCOMMAND context.
+  */
+ static char *
+ _rwCreateSchemaStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	CreateSchemaStmt	*node  = (CreateSchemaStmt *) parsetree;
+ 	StringInfoData		 buf;
+ 
+ 	initStringInfo(&buf);
+ 	appendStringInfo(&buf, "CREATE SCHEMA%s %s",
+ 					 node->if_not_exists ? " IF NOT EXISTS" : "",
+ 					 identity);
+ 
+ 	if (node->authid)
+ 		appendStringInfo(&buf, " AUTHORIZATION %s", node->authid);
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * rewrite CreateConversionStmt parser production
+  */
+ static char *
+ _rwCreateConversionStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	CreateConversionStmt *node  = (CreateConversionStmt *) parsetree;
+ 	StringInfoData		  buf;
+ 	StringInfoData		fnamebuf;
+ 
+ 	initStringInfo(&buf);
+ 	initStringInfo(&fnamebuf);
+ 
+ 	_rwAnyName(&fnamebuf, node->func_name);
+ 
+ 	appendStringInfo(&buf, "CREATE%s CONVERSION %s FOR %s TO %s FROM %s;",
+ 					 node->def ? " DEFAULT" : "",
+ 					 identity,
+ 					 node->for_encoding_name,
+ 					 node->to_encoding_name,
+ 					 fnamebuf.data);
+ 	pfree(fnamebuf.data);
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * rewrite DefineStmt parser productions
+  */
+ static char *
+ _rwDefineStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	DefineStmt			*node = (DefineStmt *) parsetree;
+ 	StringInfoData		 buf;
+ 	ListCell			*opt;
+ 	bool                first = true;
+ 
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "CREATE %s %s ", objTypeToString(node->kind),
+ 					 identity);
+ 
+ 	if (node->definition)
+ 	{
+ 		appendStringInfoChar(&buf, '(');
+ 
+ 		/* definition: grammar production */
+ 		foreach(opt, node->definition)
+ 		{
+ 			DefElem    *defel = (DefElem *) lfirst(opt);
+ 
+ 			maybeAddSeparator(&buf, ", ", &first);
+ 
+ 			if (defel->arg != NULL)
+ 			{
+ 				Node *arg = (Node *)defel->arg;
+ 
+ 				appendStringInfo(&buf, "%s=", defel->defname);
+ 
+ 				/* def_arg: grammar production */
+ 				switch (nodeTag(arg))
+ 				{
+ 					/* func_type */
+ 					case T_TypeName:
+ 						appendStringInfoString(&buf,
+ 											   TypeNameToString((TypeName *)arg));
+ 						break;
+ 
+ 					/* reserved_keyword or Sconst */
+ 					case T_String:
+ 						appendStringInfo(&buf, "'%s'", defGetString(defel));
+ 						break;
+ 
+ 					/* qual_all_Op */
+ 					case T_List:
+ 						appendStringInfo(&buf, "OPERATOR (%s)",
+ 										 strVal((Value *)linitial((List *)arg)));
+ 						break;
+ 
+ 					/* NumericOnly */
+ 					case T_Float:
+ 						appendStringInfo(&buf, "%g", defGetNumeric(defel));
+ 						break;
+ 
+ 					/* NumericOnly */
+ 					case T_Integer:
+ 					{
+ 						char		num[100];
+ 
+ 						snprintf(num, sizeof(num), INT64_FORMAT, defGetInt64(defel));
+ 						appendStringInfo(&buf, "%s", num);
+ 						break;
+ 					}
+ 
+ 					default:
+ 						elog(DEBUG1, "unrecognized node type: %d",
+ 							 (int) nodeTag(arg));
+ 				}
+ 			}
+ 			else
+ 			{
+ 				appendStringInfo(&buf, "%s", defel->defname);
+ 			}
+ 		}
+ 		appendStringInfoChar(&buf, ')');
+ 	}
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * rewrite ColQualList parser production
+  *
+  * FIXME this code assumes the ColQualList comes from a domain, but is this
+  * always the case?
+  */
+ static void
+ _rwColQualList(StringInfo buf, List *constraints,
+ 			   const char *domainName, TypeName *typeName)
+ {
+ 	ListCell			*lc;
+ 	HeapTuple			 typeTup = NULL;
+ 	Oid					 basetypeoid = InvalidOid;
+ 	int32				 basetypeMod;
+ 
+ 	foreach(lc, constraints)
+ 	{
+ 		Constraint *c = (Constraint *) lfirst(lc);
+ 		Assert(IsA(c, Constraint));
+ 
+ 		if (c->conname != NULL)
+ 			appendStringInfo(buf, " CONSTRAINT %s", c->conname);
+ 
+ 		switch (c->contype)
+ 		{
+ 			case CONSTR_NOTNULL:
+ 				appendStringInfo(buf, " NOT NULL");
+ 				break;
+ 
+ 			case CONSTR_NULL:
+ 				appendStringInfo(buf, " NULL");
+ 				break;
+ 
+ 			case CONSTR_DEFAULT:
+ 			{
+ 				char			*src;
+ 
+ 				src = uncookConstraintOrDefault(c->raw_expr,
+ 									   EXPR_KIND_COLUMN_DEFAULT,
+ 									   NULL, InvalidOid);
+ 
+ 				appendStringInfo(buf, " DEFAULT %s", src);
+ 				break;
+ 			}
+ 
+ 			case CONSTR_CHECK:
+ 			{
+ 				Node					*expr;
+ 				char					*ccsrc;
+ 				ParseState				*pstate;
+ 				CoerceToDomainValue		*domVal;
+ 
+ 				if (typeTup == NULL)
+ 				{
+ 					typeTup = typenameType(NULL, typeName, &basetypeMod);
+ 					basetypeoid = HeapTupleGetOid(typeTup);
+ 				}
+ 
+ 				domVal = makeNode(CoerceToDomainValue);
+ 				domVal->typeId = basetypeoid;
+ 				domVal->typeMod = basetypeMod;
+ 				domVal->collation = get_typcollation(basetypeoid);
+ 				domVal->location = -1;
+ 
+ 				pstate = make_parsestate(NULL);
+ 				pstate->p_value_substitute = (Node *) domVal;
+ 
+ 				expr = transformExpr(pstate,
+ 									 c->raw_expr,
+ 									 EXPR_KIND_DOMAIN_CHECK);
+ 
+ 				assign_expr_collations(pstate, expr);
+ 
+ 				ccsrc = deparse_expression(expr, NIL, false, false);
+ 
+ 				appendStringInfo(buf, " CHECK (%s)", ccsrc);
+ 				break;
+ 			}
+ 
+ 			default:
+ 				/* not a domain constraint */
+ 				break;
+ 		}
+ 	}
+ 
+ 	if (typeTup)
+ 		ReleaseSysCache(typeTup);
+ }
+ 
+ /*
+  * rewrite CreateDomainStmt parser production
+  */
+ static char *
+ _rwCreateDomainStmt(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 	CreateDomainStmt *node  = (CreateDomainStmt *) parsetree;
+ 	StringInfoData	buf;
+ 
+ 	initStringInfo(&buf);
+ 
+ 	appendStringInfo(&buf, "CREATE DOMAIN %s AS %s ",
+ 					 identity,
+ 					 TypeNameToString(node->typeName));
+ 
+ 	if (node->collClause)
+ 	{
+ 		Oid		 collNspId;
+ 		char	*collname;
+ 
+ 		collNspId =
+ 			QualifiedNameGetCreationNamespace(node->collClause->collname,
+ 											  &collname);
+ 
+ 		appendStringInfo(&buf, "COLLATE %s.%s ",
+ 						 get_namespace_name(collNspId), collname);
+ 	}
+ 	_rwColQualList(&buf, node->constraints, identity, node->typeName);
+ 
+ 	return buf.data;
+ }
+ 
+ /*
+  * Given a utility command parsetree and the OID of the corresponding object,
+  * return a textual representation of the command.
+  *
+  * The command is expanded fully, so that there are no ambiguities even in the
+  * face of search_path changes.
+  *
+  * Note we currently only support commands for which ProcessUtilitySlow saves
+  * objects to create; currently this excludes all forms of ALTER and DROP.
+  */
+ char *
+ rewrite_utility_command(Oid objectId, const char *identity, Node *parsetree)
+ {
+ 
+ 	switch (nodeTag(parsetree))
+ 	{
+ 		case T_CreateSchemaStmt:
+ 			return _rwCreateSchemaStmt(objectId, identity, parsetree);
+ 
+ 		case T_CreateStmt:
+ 		case T_CreateForeignTableStmt:
+ 			return _rwCreateStmt(objectId, identity, parsetree);
+ 
+ 		case T_DefineStmt:
+ 			return _rwDefineStmt(objectId, identity, parsetree);
+ 
+ 		case T_IndexStmt:
+ 			return _rwCreateIndexStmt(objectId, identity, parsetree);
+ 
+ 		case T_CreateExtensionStmt:
+ 			return _rwCreateExtensionStmt(objectId, identity, parsetree);
+ 
+ 		case T_CreateFdwStmt:
+ 			/* XXX these "unsupported" cases need to be filled in for
+ 			 * final submission */
+ 			return "unsupported CREATE FDW";
+ 		case T_CreateForeignServerStmt:
+ 			return "unsupported CREATE FOREIGN SERVER";
+ 
+ 		case T_CreateUserMappingStmt:
+ 			return "unsupported CREATE USER MAPPING";
+ 		case T_CompositeTypeStmt:	/* CREATE TYPE (composite) */
+ 		case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
+ 		case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
+ 			return "unsupported CREATE TYPE";
+ 
+ 		case T_ViewStmt:
+ 			return _rwViewStmt(objectId, identity, parsetree);
+ 
+ 		case T_CreateFunctionStmt:
+ 			return _rwCreateFunctionStmt(objectId, identity, parsetree);
+ 
+ 		case T_RuleStmt:
+ 			return "unsupported CREATE RULE";
+ 
+ 		case T_CreateSeqStmt:
+ 			return _rwCreateSeqStmt(objectId, identity, parsetree);
+ 
+ 		case T_CreateTableAsStmt:
+ 			return "unsupported CREATE TABLE AS";
+ 		case T_RefreshMatViewStmt:
+ 			return "unsupported REFRESH MATERIALIZED VIEW";
+ 		case T_CreateTrigStmt:
+ 			return "unsupported CREATE TRIGGER";
+ 		case T_CreatePLangStmt:
+ 			return "unsupported CREATE PROCEDURAL LANGUAGE";
+ 
+ 		case T_CreateDomainStmt:
+ 			return _rwCreateDomainStmt(objectId, identity, parsetree);
+ 
+ 		case T_CreateConversionStmt:
+ 			return _rwCreateConversionStmt(objectId, identity, parsetree);
+ 
+ 		case T_CreateCastStmt:
+ 			return "unsupported CREATE CAST";
+ 		case T_CreateOpClassStmt:
+ 			return "unsupported CREATE OPERATOR CLASS";
+ 		case T_CreateOpFamilyStmt:
+ 			return "unsupported CREATE OPERATOR FAMILY";
+ 
+ 		default:
+ 			elog(LOG, "unrecognized node type: %d",
+ 				 (int) nodeTag(parsetree));
+ 	}
+ 
+ 	return NULL;
+ }
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 4036,4041 **** get_query_def(Query *query, StringInfo buf, List *parentnamespace,
--- 4036,4055 ----
  	}
  }
  
+ 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
***************
*** 4750,4755 **** DESCR("SP-GiST support for quad tree over range");
--- 4750,4758 ----
  /* 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_normalized_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_normalized_commands _null_ _null_ _null_ ));
+ 
+ 
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
*** /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);
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 657,662 **** extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
--- 657,663 ----
  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);
***************
*** 1160,1165 **** extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
--- 1161,1167 ----
  
  /* commands/event_trigger.c */
  extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+ extern Datum pg_event_trigger_get_normalized_commands(PG_FUNCTION_ARGS);
  
  /* commands/extension.c */
  extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
*** /dev/null
--- b/src/include/utils/ddl_rewrite.h
***************
*** 0 ****
--- 1,18 ----
+ /*-------------------------------------------------------------------------
+  *
+  * ddl_rewrite.h
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/utils/ddl_rewrite.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef DDL_REWRITE_H
+ #define DDL_REWRITE_H
+ 
+ extern char *rewrite_utility_command(Oid objectId, const char *identity,
+ 						Node *parsetree);
+ 
+ #endif	/* DDL_REWRITE_H */
#2Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#1)
Re: Add CREATE support to event triggers

On Fri, Nov 8, 2013 at 10:33 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Attached you can find a very-much-WIP patch to add CREATE info support
for event triggers (normalized commands). This patch builds mainly on
two things:

1. Dimitri's "DDL rewrite" patch he submitted way back, in
/messages/by-id/m2zk1j9c44.fsf@2ndQuadrant.fr

I borrowed the whole ddl_rewrite.c code, and tweaked it a bit. There
are several things still wrong with it and which will need to be fixed
before a final patch can even be contemplated; but there are some
questions that require a consensus answer before I go and fix it all,
because what it will look like will depend on said answers.

I'm still unhappy with this whole concept. It adds a significant
maintenance burden that must be carried by everyone who adds new DDL
syntax in the future, and it's easy to imagine this area of the code
ending up very poorly tested and rife with bugs. After all, event
triggers, as nifty as they are, are only going to be used by a small
minority of users. And new DDL features are typically going to be
things that are fairly obscure, so again they will only be used by a
small minority of users. I think we need to avoid the situation where
we have bugs that can only get found when those minorities happen to
intersect. If we're going to have DDL rewrite code, then we need a
way of making sure that it gets tested in a comprehensive way on a
regular basis.

Here's one idea: create a contrib module that (somehow, via APIs to be
invented) runs every DDL command that gets executed through the
deparsing code, and then parses the result and executes *that* instead
of the original command. Then, add a build target that runs the
regression test suite in that mode, and get the buildfarm configured
to run that build target regularly on at least some machines. That
way, adding syntax to the regular regression test suite also serves to
test that the deparsing logic for that syntax is working. If we do
this, there's still some maintenance burden associated with having DDL
deparsing code, but at least our chances of noticing when we've failed
to maintain it should be pretty good.

The other thing that bothers me here is that, while a normalized
command string sounds great in theory, as soon as you want to allow
(for example) mapping schema A on node 1 to schema B on node 2, the
wheels come off: you'll have to deparse that normalized command string
so you can change out the schema name and then reassemble it back into
a command string again. So we're going to parse the user input, then
deparse it, hand over the results to the application code, which will
then parse it, modify that, and deparse it again. At every step of
that process, any mistake will lead to subtle bugs in the resulting
system. Larry Wall once wrote (approximately) that a good programming
language makes simple things simple and hard things possible; I think
this design fails the second prong of that test.

Now, I guess I can live with that if it's what everyone else wants,
but I don't have a great feeling about the long-term utility of it.
Exposing the data from the DDL statement in some structured way - like
what we've done with drops, or a JSON blob, or something like that,
feels much more robust and useful than a normalized command string to
me. If the normalized command string can easily be built up from that
data, then you don't really need to expose the command string
separately. If it can't, then you're not doing a good job exposing
the data in a usable form. Saying "well, people can always parse the
normalized command string" is a cop-out. Parsing SQL is *hard*; the
only external project I know of that parses our SQL syntax well is
pgpool, and that's because they copy our parser wholesale, surely not
the sort of solution we want to foist off on event trigger authors.

Finally, I'm very skeptical of the word "normalized". To me, that
sounds like an alias for "modifying the command string in unspecified
ways that big brother thinks will be useful to event trigger authors".
Color me skeptical. What if somebody doesn't want their command
string normalized? What if they want it normalized in a way that's
different from the way that we've chosen to normalize it? I fear that
this whole direction amounts to "we don't know how to design a real
API so let's just do surgery on the command string and call whatever
pops out the API". Maybe that's harsh, but if it is I don't know why.
The normalization steps we build into this process constitute
assumptions about how the feature will be used, and they back the user
into using that feature in just that way and no other.

2. The ideas we used to build DROP support. Mainly, the interesting
thing here is the fact that we use a SRF to report, at
ddl_command_end, all the objects that were created during execution
of that command. We do this by collecting them in a list in some raw
form somewhere during ProcessUtility, and then spitting them out if
the SRF is called. I think the general idea is sound, although of
course I admit there might be bugs in the implementation.

Agreed.

Note this patch doesn't try to add any kind of ALTER support. I think
this is fine in principle, because we agreed that we would attack each
kind of command separately (divide to conquer and all that);

Also agreed.

but there
is a slight problem for some kind of objects that are represented partly
as ALTER state during creation; for example creating a table with a
sequence uses ALTER SEQ/OWNED BY internally at some point. There might
be other cases I'm missing, also. (The REFRESH command is nominally
also supported.)

There are lots of places in the DDL code where we pass around
constructed parse trees as a substitute for real argument lists. I
expect that many of those places will eventually get refactored away,
so it's important that this feature does not end up relying on
accidents of the current code structure. For example, an
AlterTableStmt can actually do a whole bunch of different things in a
single statement: SOME of those are handled by a loop in
ProcessUtilitySlow() and OTHERS are handled internally by AlterTable.
I'm pretty well convinced that that division of labor is a bad design,
and I think it's important that this feature doesn't make that dubious
design decision into documented behavior.

Now about the questions I mentioned above:

a) It doesn't work to reverse-parse the statement nodes in all cases;
there are several unfixable bugs if we only do that. In order to create
always-correct statements, we need access to the catalogs for the
created objects. But if we are doing catalog access, then it seems to
me that we can do away with the statement parse nodes completely and
just reconstruct the objects from catalog information. Shall we go that
route?

That works well for CREATE and is definitely appealing in some ways;
it probably means needing a whole lot of what's in pg_dump in the
backend also. Of course, converting the pg_dump code to a library
that can be linked into either a client or the server would make a lot
of people happy. Making pg_dump much dumber (at least as regards
future versions) and relying on new backend code to serve the same
purpose would perhaps be reasonable as well, although I know Tom is
against it. But having two copies of that code doesn't sound very
good; and we'd need some way of thoroughly testing the one living in
the backend.

c) The current patch stashes all objects in a list, whenever there's an
event trigger function. But perhaps some users want to have event
triggers and not have any use for the CREATE statements. So one idea is
to require users that want the objects reported to call a special
function in a ddl_command_start event trigger which enables collection;
if it's not called, objects are not reported. This eliminates
performance impact for people not using it, but then maybe it will be
surprising for people that call the SRF and find that it always returns
empty.

This seems like premature optimization to me, but I think I lost the
last iteration of this argument.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Christopher Browne
cbbrowne@gmail.com
In reply to: Alvaro Herrera (#1)
Re: Add CREATE support to event triggers

On Fri, Nov 8, 2013 at 10:33 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Hello,

Attached you can find a very-much-WIP patch to add CREATE info support
for event triggers (normalized commands). This patch builds mainly on
two things:

1. Dimitri's "DDL rewrite" patch he submitted way back, in
/messages/by-id/m2zk1j9c44.fsf@2ndQuadrant.fr

I borrowed the whole ddl_rewrite.c code, and tweaked it a bit. There
are several things still wrong with it and which will need to be fixed
before a final patch can even be contemplated; but there are some
questions that require a consensus answer before I go and fix it all,
because what it will look like will depend on said answers.

I have tried this out; the patch applies fine.

Note that it induces (modulo my environment) a failure in "make check".

The "opr_sanity" test fails.

postgres@cbbrowne ~/p/s/t/regress> diff expected/opr_sanity.out
results/opr_sanity.out
348,350c348,351
< oid | proname
< -----+---------
< (0 rows)
---

oid | proname
------+------------------------------------------
3567 | pg_event_trigger_get_normalized_commands
(1 row)

That's a minor problem; the trouble there is that the new function is not
yet documented. Not a concern at this stage.

2. The ideas we used to build DROP support. Mainly, the interesting
thing here is the fact that we use a SRF to report, at
ddl_command_end, all the objects that were created during execution
of that command. We do this by collecting them in a list in some raw
form somewhere during ProcessUtility, and then spitting them out if
the SRF is called. I think the general idea is sound, although of
course I admit there might be bugs in the implementation.

Note this patch doesn't try to add any kind of ALTER support. I think
this is fine in principle, because we agreed that we would attack each
kind of command separately (divide to conquer and all that); but there
is a slight problem for some kind of objects that are represented partly
as ALTER state during creation; for example creating a table with a
sequence uses ALTER SEQ/OWNED BY internally at some point. There might
be other cases I'm missing, also. (The REFRESH command is nominally
also supported.)

I imagine that the things we create in earlier stages may help with later
stages, so it's worth *some* planning so we can hope not to build bits
now that push later enhancements into corners that they can't get out of.

But I'm not disagreeing at all.

Now about the questions I mentioned above:

a) It doesn't work to reverse-parse the statement nodes in all cases;
there are several unfixable bugs if we only do that. In order to create
always-correct statements, we need access to the catalogs for the
created objects. But if we are doing catalog access, then it seems to
me that we can do away with the statement parse nodes completely and
just reconstruct the objects from catalog information. Shall we go that
route?

Here's a case where it doesn't work.

testevent@localhost-> create schema foo;
CREATE SCHEMA
testevent@localhost-> create domain foo.bar integer;
CREATE DOMAIN
testevent@localhost-> CREATE OR REPLACE FUNCTION snitch() RETURNS
event_trigger LANGUAGE plpgsql AS $$
testevent$# DECLARE
testevent$# r RECORD;
testevent$# BEGIN
testevent$# FOR r IN SELECT * FROM
pg_event_trigger_get_normalized_commands()
testevent$# LOOP
testevent$# RAISE NOTICE 'object created: id %, statement %',
testevent$# r.identity, r.command;
testevent$# END LOOP;
testevent$# END;
testevent$# $$;
CREATE FUNCTION
testevent@localhost-> CREATE EVENT TRIGGER snitch ON ddl_command_end
EXECUTE PROCEDURE snitch();
CREATE EVENT TRIGGER
testevent@localhost-> set search_path to public, foo;
SET
testevent@localhost-> create table foo.foo2 (acolumn bar);
NOTICE: object created: id foo.foo2, statement CREATE TABLE foo.foo2
(acolumn bar)
CREATE TABLE

The trouble is that you have only normalized the table name. The
domain, bar, needs its name normalized as well.

b) What's the best design of the SRF output? This patch proposes two
columns, object identity and create statement. Is there use for
anything else? Class/object OIDs perhaps, schema OIDs for objects types
that have it? I don't see any immediate need to that info, but perhaps
someone does.

Probably an object type is needed as well, to know if it's a table or
a domain or a sequence or whatever.

I suspect that what will be needed to make it all usable is some sort of
"structured" form. That is in keeping with Robert Haas' discomfort with
the normalized form.

My "minor" gripe is that you haven't normalized enough (e.g. - it should be
CREATE TABLE foo.foo2 (acolumn foo.bar), capturing the normalization of
data types that are referenced).

But Robert's quite right that users may want more than just to capture that
literally; they may want to modify it, for instance, by shifting to another
schema. And it will be no fun at all if you have to construct an SQL parser
in order to change it.

The "big change" in Slony 2.2 was essentially to solve that problem; we had
been capturing logged updates as near-raw SQL, and discovered that we
really needed to capture it in a form that allows restructuring it without
needing to reparse it. It seems to me that the same need applies here.

The answer we came up with in Slony was to change from "SQL fragment"
to "array of attribute names and attribute values." I expect that is quite
unsuitable for CREATE events, being too "flat" a representation.

c) The current patch stashes all objects in a list, whenever there's an
event trigger function. But perhaps some users want to have event
triggers and not have any use for the CREATE statements. So one idea is
to require users that want the objects reported to call a special
function in a ddl_command_start event trigger which enables collection;
if it's not called, objects are not reported. This eliminates
performance impact for people not using it, but then maybe it will be
surprising for people that call the SRF and find that it always returns
empty.

Hmm. I'm not sure I follow what the issue is here (I think Robert has
the same problem; if that's so, then it's probably worth a separate
explanation/discussion).

I think there are actually more options here than just worrying about
CREATE...

I can see caring/not caring about CREATE events. Also caring/not
caring about different sorts of objects.

Thus, an event trigger might either filter on event type
(CREATE versus DROP versus ALTER vs ...), as well as on object
type (TABLE vs VIEW vs SCHEMA vs DOMAIN vs ...).

I could see that being either part of the trigger definition or as an
attribute established within the trigger.

Thus...

create event trigger foo snitch on ddl_command_end
on create, drop, alter
for table, view
execute procedure snitch();

And having internals

create or replace function snitch() returns event_trigger language plpgsql as
$$
declare
r record;
begin
for r in select * from pg_event_trigger_get_normalized_commands loop
raise notice 'action: % object type % object name % object id %
normalized statement %',
r.type, r.identity, r.id, r.command;
end loop
end;
$$;

And I suspect that there needs to be another value returned (not
necessarily by the same function) that is some form of the parse tree.

If the parse tree is already available in memory on the C side of things, then
making it accessible by calling a function that allows "walking" the tree and
invoking methods on the elements is a perfectly reasonable idea. Ideally,
there's something that can be usefully called that is written in pl/pgsql;
mandating that we punt to C to do arbitrary magic doesn't seem nice.
(If that's not reading coherently, well, blame it on my mayor being on
crack! ;-) )

d) There's a new routine uncookConstraintOrDefault. This takes a raw
expression, runs transformExpr() on it, and then deparses it (possibly
setting up a deparse context based on some relation). This is a
somewhat funny thing to be doing, so if there are other ideas on how to
handle this, I'm all ears.

Is that perhaps some of the "magic" to address my perhaps incoherent
bit about "punt to C"? It looks a bit small to be that...
--
When confronted by a difficult problem, solve it by reducing it to the
question, "How would the Lone Ranger handle this?"

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Andrew Tipton
andrew@kiwidrew.com
In reply to: Christopher Browne (#3)
Re: Add CREATE support to event triggers

On Thu, Nov 21, 2013 at 2:36 AM, Christopher Browne <cbbrowne@gmail.com> wrote:

b) What's the best design of the SRF output? This patch proposes two
columns, object identity and create statement. Is there use for
anything else? Class/object OIDs perhaps, schema OIDs for objects types
that have it? I don't see any immediate need to that info, but perhaps
someone does.

Probably an object type is needed as well, to know if it's a table or
a domain or a sequence or whatever.

I suspect that what will be needed to make it all usable is some sort of
"structured" form. That is in keeping with Robert Haas' discomfort with
the normalized form.

My "minor" gripe is that you haven't normalized enough (e.g. - it should be
CREATE TABLE foo.foo2 (acolumn foo.bar), capturing the normalization of
data types that are referenced).

But Robert's quite right that users may want more than just to capture that
literally; they may want to modify it, for instance, by shifting to another
schema. And it will be no fun at all if you have to construct an SQL parser
in order to change it.

It's certainly much easier to transform a structured representation
into a valid SQL command string than it is to do the inverse.

You may be interested in an extension that I'm working on for a
client, which provides relation_create, relation_alter, and
relation_drop event triggers for 9.3:

https://bitbucket.org/malloclabs/pg_schema_triggers

I decided to create a composite type for each event, which can be
accessed from within the event trigger by calling a special function.
For example, the relation_alter event supplies the relation Oid and
the "old" and "new" pg_class rows. It's easy to then examine the old
vs. new rows and determine what has changed.

Kind regards,
Andrew Tipton

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#2)
Re: Add CREATE support to event triggers

Robert Haas escribi�:

The other thing that bothers me here is that, while a normalized
command string sounds great in theory, as soon as you want to allow
(for example) mapping schema A on node 1 to schema B on node 2, the
wheels come off: you'll have to deparse that normalized command string
so you can change out the schema name and then reassemble it back into
a command string again. So we're going to parse the user input, then
deparse it, hand over the results to the application code, which will
then parse it, modify that, and deparse it again.

I have considered several ideas on this front, but most of them turn out
to be useless or too cumbersome to use. What seems most adequate is to
build a command string containing certain patterns, and an array of
replacement values for such patterns; each pattern corresponds to one
element that somebody might want modified in the command. As a trivial
example, a command such as

CREATE TABLE foo (bar INTEGER);

would return a string like
CREATE TABLE ${table_schema}.${table_name} (bar INTEGER);

and the replacement array would be
{table_schema => "public", table_name => "foo"}

If we additionally provide a function to expand the replacements in the
string, we would have the base funcionality of a normalized command
string. If somebody wants to move the table to some other schema, they
can simply modify the array to suit their taste, and again expand using
the provided function; this doesn't require parsing SQL. It's likely
that there are lots of fine details that need exploring before this is a
fully workable idea -- I have just started work on it, so please bear
with me.

I think this is basically what you call "a JSON blob".

Finally, I'm very skeptical of the word "normalized". To me, that
sounds like an alias for "modifying the command string in unspecified
ways that big brother thinks will be useful to event trigger authors".
Color me skeptical. What if somebody doesn't want their command
string normalized? What if they want it normalized in a way that's
different from the way that we've chosen to normalize it? I fear that
this whole direction amounts to "we don't know how to design a real
API so let's just do surgery on the command string and call whatever
pops out the API".

You might criticize the example above by saying that I haven't
considered using a JSON array for the list of table elements; in a
sense, I would be being Big Brother and deciding that you (as the user)
don't need to mess up with the column/constraints list in a table you're
creating. I thought about it and wasn't sure if there was a need to
implement that bit in the first iteration of this implementation. One
neat thing about this string+replaceables idea is that we can later
change what replaceable elements the string has, thus providing more
functionality (thus, for example, perhaps the column list can be altered
in v2 that was a "constant" in v1), without breaking existing users of
the v1.

but there
is a slight problem for some kind of objects that are represented partly
as ALTER state during creation; for example creating a table with a
sequence uses ALTER SEQ/OWNED BY internally at some point. There might
be other cases I'm missing, also. (The REFRESH command is nominally
also supported.)

There are lots of places in the DDL code where we pass around
constructed parse trees as a substitute for real argument lists. I
expect that many of those places will eventually get refactored away,
so it's important that this feature does not end up relying on
accidents of the current code structure. For example, an
AlterTableStmt can actually do a whole bunch of different things in a
single statement: SOME of those are handled by a loop in
ProcessUtilitySlow() and OTHERS are handled internally by AlterTable.
I'm pretty well convinced that that division of labor is a bad design,
and I think it's important that this feature doesn't make that dubious
design decision into documented behavior.

Yeah, the submitted patch took care of these elements by invoking the
appropriate collection function at all the right places. Most of it
happened right in ProcessUtilitySlow, but other bits were elsewhere (for
instance, sub-objects created in a complex CREATE SCHEMA command). I
mentioned the ALTER SEQUENCE example above because that happens in a
code path that wasn't even close to the rest of the stuff.

Now about the questions I mentioned above:

a) It doesn't work to reverse-parse the statement nodes in all cases;
there are several unfixable bugs if we only do that. In order to create
always-correct statements, we need access to the catalogs for the
created objects. But if we are doing catalog access, then it seems to
me that we can do away with the statement parse nodes completely and
just reconstruct the objects from catalog information. Shall we go that
route?

That works well for CREATE and is definitely appealing in some ways;
it probably means needing a whole lot of what's in pg_dump in the
backend also. Of course, converting the pg_dump code to a library
that can be linked into either a client or the server would make a lot
of people happy. Making pg_dump much dumber (at least as regards
future versions) and relying on new backend code to serve the same
purpose would perhaps be reasonable as well, although I know Tom is
against it. But having two copies of that code doesn't sound very
good; and we'd need some way of thoroughly testing the one living in
the backend.

Agreed on requiring thorough testing.

c) The current patch stashes all objects in a list, whenever there's an
event trigger function. But perhaps some users want to have event
triggers and not have any use for the CREATE statements. So one idea is
to require users that want the objects reported to call a special
function in a ddl_command_start event trigger which enables collection;
if it's not called, objects are not reported. This eliminates
performance impact for people not using it, but then maybe it will be
surprising for people that call the SRF and find that it always returns
empty.

This seems like premature optimization to me, but I think I lost the
last iteration of this argument.

I don't think you did; we ended up using a design that both doesn't
require explicit user action, nor causes a performance hit. I wasn't
sure about this, but thought I would mention just it in case. Maybe we
will end up doing the same here and have a create_sql_objects event or
something like that --- not real sure of this part yet, as there's a lot
of infrastructure to design and code first.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#5)
Re: Add CREATE support to event triggers

On Fri, Jan 3, 2014 at 9:05 AM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

The other thing that bothers me here is that, while a normalized
command string sounds great in theory, as soon as you want to allow
(for example) mapping schema A on node 1 to schema B on node 2, the
wheels come off: you'll have to deparse that normalized command string
so you can change out the schema name and then reassemble it back into
a command string again. So we're going to parse the user input, then
deparse it, hand over the results to the application code, which will
then parse it, modify that, and deparse it again.

I have considered several ideas on this front, but most of them turn out
to be useless or too cumbersome to use. What seems most adequate is to
build a command string containing certain patterns, and an array of
replacement values for such patterns; each pattern corresponds to one
element that somebody might want modified in the command. As a trivial
example, a command such as

CREATE TABLE foo (bar INTEGER);

would return a string like
CREATE TABLE ${table_schema}.${table_name} (bar INTEGER);

and the replacement array would be
{table_schema => "public", table_name => "foo"}

If we additionally provide a function to expand the replacements in the
string, we would have the base funcionality of a normalized command
string. If somebody wants to move the table to some other schema, they
can simply modify the array to suit their taste, and again expand using
the provided function; this doesn't require parsing SQL. It's likely
that there are lots of fine details that need exploring before this is a
fully workable idea -- I have just started work on it, so please bear
with me.

I think this is basically what you call "a JSON blob".

I think this direction has some potential. I'm not sure it's right in
detail. The exact scheme you propose above won't work if you want to
leave out the schema name altogether, and more generally it's not
going to help very much with anything other than substituting in
identifiers. What if you want to add a column called satellite_id to
every table that gets created, for example? What if you want to make
the tables UNLOGGED? I don't see how that kind of things is going to
work at all cleanly.

What I can imagine that might work is that you get a JSON blob for a
create table statement and then you have a function
make_a_create_table_statement(json) that will turn it back into SQL.
You can pass a modified blob (adding, removing, or changing the
schema_name or any other element) instead and it will do its thing.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#6)
Re: Add CREATE support to event triggers

Robert Haas escribi�:

I think this direction has some potential. I'm not sure it's right in
detail. The exact scheme you propose above won't work if you want to
leave out the schema name altogether, and more generally it's not
going to help very much with anything other than substituting in
identifiers. What if you want to add a column called satellite_id to
every table that gets created, for example? What if you want to make
the tables UNLOGGED? I don't see how that kind of things is going to
work at all cleanly.

Thanks for the discussion. I am building some basic infrastructure to
make this possible, and will explore ideas to cover these oversights
(not posting anything concrete yet because I expect several iterations
to crash and burn before I have something sensible to post).

What I can imagine that might work is that you get a JSON blob for a
create table statement and then you have a function
make_a_create_table_statement(json) that will turn it back into SQL.
You can pass a modified blob (adding, removing, or changing the
schema_name or any other element) instead and it will do its thing.

I agree, except that I would prefer not to have one function for each
DDL statement type; instead we would have a single function that knows
to expand arbitrary strings using arbitrary JSON parameter objects, and
let ddl_rewrite.c (or whatever we call that file) deal with all the
ugliness.

One function per statement would increase the maintenance cost more, I
think (three extra pg_proc entries every time you add a new object type?
Ugh.)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#7)
Re: Add CREATE support to event triggers

On Mon, Jan 6, 2014 at 1:17 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

I agree, except that I would prefer not to have one function for each
DDL statement type; instead we would have a single function that knows
to expand arbitrary strings using arbitrary JSON parameter objects, and
let ddl_rewrite.c (or whatever we call that file) deal with all the
ugliness.

Yeah, that might work.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#7)
Re: Add CREATE support to event triggers

Alvaro Herrera escribi�:

Robert Haas escribi�:

I think this direction has some potential. I'm not sure it's right in
detail. The exact scheme you propose above won't work if you want to
leave out the schema name altogether, and more generally it's not
going to help very much with anything other than substituting in
identifiers. What if you want to add a column called satellite_id to
every table that gets created, for example? What if you want to make
the tables UNLOGGED? I don't see how that kind of things is going to
work at all cleanly.

Thanks for the discussion. I am building some basic infrastructure to
make this possible, and will explore ideas to cover these oversights
(not posting anything concrete yet because I expect several iterations
to crash and burn before I have something sensible to post).

Here's a working example. Suppose the user runs

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy";

In an event trigger, the function pg_event_trigger_get_creation_commands()
returns the following JSON blob:

{"authorization":{"authorization_role":"some guy",
"output":"AUTHORIZATION %i{authorization_role}"},
"if_not_exists":"IF NOT EXISTS",
"name":"some schema",
"output":"CREATE SCHEMA %{if_not_exists} %i{name} %{authorization}"}

wherein I have chosen to have a JSON element with the hardcoded name of
"output" which is what needs to be expanded; for each %{} parameter
found in it, there is an equally-named element in the JSON blob. This
can be a string, a NULL, or another JSON object.

If it's a string, it expands to that value; if it's an object,
recursively an "output" element is expanded in the same way, and the
expanded string is used.

If there's a NULL element when expanding an object, the whole thing
expands to empty. For example, if no AUTHORIZATION
clause is specified, "authorization" element is still there, but the
"authorization_role" element within it is NULL, and so the whole
AUTHORIZATION clause expands to empty and the resulting command contains
no authorization clause. This is useful to support the case that
someone doesn't have an AUTHORIZATION clause in the CREATE SCHEMA
command, and the event trigger injects one simply by setting the
authorization_role to some role name.

IF NOT EXISTS is handled by defining it to either the string IF NOT
EXISTS or to empty if no such clause was specified.

The user can modify elements in the JSON to get a different version of
the command. (I reckon the "output" can also be modified, but this is
probably a bad idea in most/all cases. I don't think there's a need to
prohibit this explicitely.) Also, someone might define "if_not_exists"
to something completely unrelated, but that would be their own fault.
(Maybe we can have some cross-check that the if_not_exists element in
JSON cannot be anything other than "IF NOT EXISTS" or the empty string;
and that the "output" element remains the same at expansion time than it
was at generation time. Perhaps we should even hide the "output"
element from the user completely and only add them to the JSON at time
of expansion. Not sure it's worth the trouble.)

There is another function,
pg_event_trigger_expand_creation_command(json), which will expand the
above JSON blob and return the following text:

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy"

Note the identifiers are properly quoted (there are quotes in the JSON
blob, but they correspond to JSON's own delimiters). I have defined a
'i' modifier to have %i{} elements, which means that the element is an
identifier which might need quoting.

I have also defined a %d{} modifier that means to use the element to
expand a possibly-qualified dotted name. (There would be no "output"
element in this case.) This is to support the case where you have

CREATE TABLE public.foo
which results in
{"table_name":{"schema":"public",
"relname":"foo"}}

and you want to edit the "table_name" element in the root JSON and set
the schema to something else (perhaps NULL), so in the event trigger
after expansion you can end up with "CREATE TABLE foo" or "CREATE TABLE
private.foo" or whatever.

Most likely there are some more rules that will need to be created, but
so far this looks sensible.

I'm going to play some more with the %d{} stuff, and also with the idea
of representing table elements such as columns and constraints as an
array. In the meantime please let me know whether this makes sense.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#9)
Re: Add CREATE support to event triggers

Hello

I don't like this direction. What we can do with JSON from plpgsql? More,
JSON is not too robust format against some future changes.

Regards

Pavel
Dne 8.1.2014 21:43 "Alvaro Herrera" <alvherre@2ndquadrant.com> napsal(a):

Show quoted text

Alvaro Herrera escribió:

Robert Haas escribió:

I think this direction has some potential. I'm not sure it's right in
detail. The exact scheme you propose above won't work if you want to
leave out the schema name altogether, and more generally it's not
going to help very much with anything other than substituting in
identifiers. What if you want to add a column called satellite_id to
every table that gets created, for example? What if you want to make
the tables UNLOGGED? I don't see how that kind of things is going to
work at all cleanly.

Thanks for the discussion. I am building some basic infrastructure to
make this possible, and will explore ideas to cover these oversights
(not posting anything concrete yet because I expect several iterations
to crash and burn before I have something sensible to post).

Here's a working example. Suppose the user runs

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy";

In an event trigger, the function pg_event_trigger_get_creation_commands()
returns the following JSON blob:

{"authorization":{"authorization_role":"some guy",
"output":"AUTHORIZATION %i{authorization_role}"},
"if_not_exists":"IF NOT EXISTS",
"name":"some schema",
"output":"CREATE SCHEMA %{if_not_exists} %i{name} %{authorization}"}

wherein I have chosen to have a JSON element with the hardcoded name of
"output" which is what needs to be expanded; for each %{} parameter
found in it, there is an equally-named element in the JSON blob. This
can be a string, a NULL, or another JSON object.

If it's a string, it expands to that value; if it's an object,
recursively an "output" element is expanded in the same way, and the
expanded string is used.

If there's a NULL element when expanding an object, the whole thing
expands to empty. For example, if no AUTHORIZATION
clause is specified, "authorization" element is still there, but the
"authorization_role" element within it is NULL, and so the whole
AUTHORIZATION clause expands to empty and the resulting command contains
no authorization clause. This is useful to support the case that
someone doesn't have an AUTHORIZATION clause in the CREATE SCHEMA
command, and the event trigger injects one simply by setting the
authorization_role to some role name.

IF NOT EXISTS is handled by defining it to either the string IF NOT
EXISTS or to empty if no such clause was specified.

The user can modify elements in the JSON to get a different version of
the command. (I reckon the "output" can also be modified, but this is
probably a bad idea in most/all cases. I don't think there's a need to
prohibit this explicitely.) Also, someone might define "if_not_exists"
to something completely unrelated, but that would be their own fault.
(Maybe we can have some cross-check that the if_not_exists element in
JSON cannot be anything other than "IF NOT EXISTS" or the empty string;
and that the "output" element remains the same at expansion time than it
was at generation time. Perhaps we should even hide the "output"
element from the user completely and only add them to the JSON at time
of expansion. Not sure it's worth the trouble.)

There is another function,
pg_event_trigger_expand_creation_command(json), which will expand the
above JSON blob and return the following text:

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy"

Note the identifiers are properly quoted (there are quotes in the JSON
blob, but they correspond to JSON's own delimiters). I have defined a
'i' modifier to have %i{} elements, which means that the element is an
identifier which might need quoting.

I have also defined a %d{} modifier that means to use the element to
expand a possibly-qualified dotted name. (There would be no "output"
element in this case.) This is to support the case where you have

CREATE TABLE public.foo
which results in
{"table_name":{"schema":"public",
"relname":"foo"}}

and you want to edit the "table_name" element in the root JSON and set
the schema to something else (perhaps NULL), so in the event trigger
after expansion you can end up with "CREATE TABLE foo" or "CREATE TABLE
private.foo" or whatever.

Most likely there are some more rules that will need to be created, but
so far this looks sensible.

I'm going to play some more with the %d{} stuff, and also with the idea
of representing table elements such as columns and constraints as an
array. In the meantime please let me know whether this makes sense.

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#10)
Re: Add CREATE support to event triggers

Pavel Stehule escribi�:

Hello

I don't like this direction. What we can do with JSON from plpgsql?

We have plenty of JSON functions and operators in SQL, and more to come
soon. Is that not enough?

More, JSON is not too robust format against some future changes.

Not sure what you mean. This JSON is generated and consumed by our own
code, so we only need to concern ourselves with making sure that we can
consume (in the expansion function) what we generated in the previous
phase. There is no transmission to the exterior.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#1)
Re: Add CREATE support to event triggers

CC to hackers restored.

Pavel Stehule escribi�:

Dne 8.1.2014 23:17 "Alvaro Herrera" <alvherre@2ndquadrant.com> napsal(a):

Pavel Stehule escribi�:

Hello

I don't like this direction. What we can do with JSON from plpgsql?

We have plenty of JSON functions and operators in SQL, and more to come
soon. Is that not enough?

No, is not. Im sure. It is wrong a request to parse system internal data,
that is available in structured form. You create string that should be
parsed same time.

Few functions with OUT parameters are beter than any semistructured string.

That was shot down, for good reasons: we assume that the users of this
are going to want to modify the command before considering it final.
Maybe they want to add a column to each table being created, or they
want to change the tablespace if the table name ends with "_big", or
they want to change the schema in which it is created.

This JSON representations lets you receive the table creation data in a
well-known JSON schema; you can tweak individual elements without having
to parse the SQL command. And when you're done tweaking, there's a
function that lets you produce the SQL command that corresponds to the
original with the tweaks you just did.

(Please note that, thus far, this facility DOES NOT let you change the
table that was created, at least not directly: these event triggers are
run AFTER the creation command has completed. You can tweak the command
that would be sent to a remote server in a replication swarm, for
example. Or create a mirror table for audit purposes. Or perhaps even
generate an ALTER TABLE command for the new table.)

If by "few functions with OUT parameters" you mean that we need to have
one record type that is able to receive all possible CREATE TABLE
options, so that you can change them as a record in plpgsql, this
doesn't sound too good to me, for three reasons: a) it's going to
require too many types and functions (one per statement type); b)
cramming the stuff in pg_type.h / pg_proc.h is going to be a horrid
task; c) any change is going to require an initdb.

--
Alvaro Herrera

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Craig Ringer
craig@2ndquadrant.com
In reply to: Alvaro Herrera (#9)
Re: Add CREATE support to event triggers

On 01/09/2014 04:42 AM, Alvaro Herrera wrote:

If there's a NULL element when expanding an object, the whole thing
expands to empty. For example, if no AUTHORIZATION
clause is specified, "authorization" element is still there, but the
"authorization_role" element within it is NULL, and so the whole
AUTHORIZATION clause expands to empty and the resulting command contains
no authorization clause.

I'd like to see this applied consistently to argument-less clauses like
IF NOT EXISTS too. So the same rules apply.

IF NOT EXISTS is handled by defining it to either the string IF NOT
EXISTS or to empty if no such clause was specified.

I'm not keen on this bit. It puts clauses of syntax into value strings
other than the special "output" key. Those keys aren't easily
distinguished from user data without clause specific knowledge. I'm not
keen on that.

Instead, can't we use your already proposed subclause structure?

{"authorization":{"authorization_role":"some guy",
"output":"AUTHORIZATION %i{authorization_role}"},
"if_not_exists": {"output": "IF NOT EXISTS"},
"name":"some schema",
"output":"CREATE SCHEMA %{if_not_exists} %i{name} %{authorization}"}

i.e. "if_not_exists" becomes an object. All clauses are objects, all
non-object values are user data. (right?). If the clause is absent, the
"output" key is the empty string.

The issue with that (and with your original proposal) is that you can't
tell what these clauses are supposed to be if they're not present in the
original query. You can't *enable* "IF NOT EXISTS" without pulling
knowledge of that syntax from somewhere else.

Depending on the problem you intend to solve there, that might be fine.

If it isn't, then instead there just needs to be a key to flag such
clauses as present or not.

{"authorization":{"authorization_role":"some guy",
"output":"AUTHORIZATION %i{authorization_role}"},
"if_not_exists": {"output": "IF NOT EXISTS"
"present": true},
"name":"some schema",
"output":"CREATE SCHEMA %{if_not_exists} %i{name} %{authorization}"}

Am I just over-complicating something simple here?

My reasoning is that it'd be good to be able to easily tell the
difference between *structure* and *user data* in these query trees and
do so without possibly version-specific and certainly
syntax/clause-specific knowledge about the meaning of every key of every
clause.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Craig Ringer (#13)
Re: Add CREATE support to event triggers

Craig Ringer escribi�:

Instead, can't we use your already proposed subclause structure?

{"authorization":{"authorization_role":"some guy",
"output":"AUTHORIZATION %i{authorization_role}"},
"if_not_exists": {"output": "IF NOT EXISTS"},
"name":"some schema",
"output":"CREATE SCHEMA %{if_not_exists} %i{name} %{authorization}"}

i.e. "if_not_exists" becomes an object. All clauses are objects, all
non-object values are user data. (right?). If the clause is absent, the
"output" key is the empty string.

The issue with that (and with your original proposal) is that you can't
tell what these clauses are supposed to be if they're not present in the
original query. You can't *enable* "IF NOT EXISTS" without pulling
knowledge of that syntax from somewhere else.

Depending on the problem you intend to solve there, that might be fine.

Hmm. This seems like a reasonable thing to do, except that I would like
the "output" to always be the constant, and have some other way to
enable the clause or disable it. With your "present" boolean:
so

"if_not_exists": {"output": "IF NOT EXISTS",
"present": true/false}

In fact, I'm now wondering whether this is a better idea than not
emitting anything when some element in the output expands to NULL; so it
would apply to "authorization" as well; if the command includes the
clause, it'd be

{"authorization":{"authorization_role":"some guy",
"present": true,
"output":"AUTHORIZATION %i{authorization_role}"},

and if there wasn't anything, you'd have

{"authorization":{"authorization_role": null,
"present": false,
"output":"AUTHORIZATION %i{authorization_role}"},

so if you want to turn it on and it wasn't, you need to change both the
present boolean and also set the authorization_role element; and if you
want to turn it off when it was present, just set present to false.

Am I just over-complicating something simple here?

I think it's a fair point.

My reasoning is that it'd be good to be able to easily tell the
difference between *structure* and *user data* in these query trees and
do so without possibly version-specific and certainly
syntax/clause-specific knowledge about the meaning of every key of every
clause.

Sounds reasonable.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#14)
Re: Add CREATE support to event triggers

On Wed, Jan 8, 2014 at 10:27 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Craig Ringer escribió:

Instead, can't we use your already proposed subclause structure?

{"authorization":{"authorization_role":"some guy",
"output":"AUTHORIZATION %i{authorization_role}"},
"if_not_exists": {"output": "IF NOT EXISTS"},
"name":"some schema",
"output":"CREATE SCHEMA %{if_not_exists} %i{name} %{authorization}"}

i.e. "if_not_exists" becomes an object. All clauses are objects, all
non-object values are user data. (right?). If the clause is absent, the
"output" key is the empty string.

The issue with that (and with your original proposal) is that you can't
tell what these clauses are supposed to be if they're not present in the
original query. You can't *enable* "IF NOT EXISTS" without pulling
knowledge of that syntax from somewhere else.

Depending on the problem you intend to solve there, that might be fine.

Hmm. This seems like a reasonable thing to do, except that I would like
the "output" to always be the constant, and have some other way to
enable the clause or disable it. With your "present" boolean:
so

"if_not_exists": {"output": "IF NOT EXISTS",
"present": true/false}

Why not:

"if_not_exists": true/false

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#15)
Re: Add CREATE support to event triggers

Robert Haas escribi�:

On Wed, Jan 8, 2014 at 10:27 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Hmm. This seems like a reasonable thing to do, except that I would like
the "output" to always be the constant, and have some other way to
enable the clause or disable it. With your "present" boolean:
so

"if_not_exists": {"output": "IF NOT EXISTS",
"present": true/false}

Why not:

"if_not_exists": true/false

Yeah, that's another option. If we do this, though, the expansion
function would have to know that an "if_not_exist" element expands to IF
NOT EXISTS. Maybe that's okay. Right now, the expansion function is
pretty stupid, which is nice.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Jim Nasby
jim@nasby.net
In reply to: Alvaro Herrera (#16)
Re: Add CREATE support to event triggers

On 1/9/14, 11:58 AM, Alvaro Herrera wrote:

Robert Haas escribi�:

On Wed, Jan 8, 2014 at 10:27 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Hmm. This seems like a reasonable thing to do, except that I would like
the "output" to always be the constant, and have some other way to
enable the clause or disable it. With your "present" boolean:
so

"if_not_exists": {"output": "IF NOT EXISTS",
"present": true/false}

Why not:

"if_not_exists": true/false

Yeah, that's another option. If we do this, though, the expansion
function would have to know that an "if_not_exist" element expands to IF
NOT EXISTS. Maybe that's okay. Right now, the expansion function is
pretty stupid, which is nice.

Yeah, the source side of this will always have to understand the nuances of every command; it'd be really nice to not burden the other side with that as well. The only downside I see is a larger JSON output, but meh.

Another advantage is if you really wanted to you could modify the output formatting in the JSON doc to do something radically different if so inclined...
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Simon Riggs
simon@2ndQuadrant.com
In reply to: Alvaro Herrera (#9)
Re: Add CREATE support to event triggers

On 8 January 2014 20:42, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy";

Hmm, given in 9.3 it was OK to have only DROP event triggers, I think
it should be equally acceptable to have just CREATE, but without every
option on CREATE. CREATE SCHEMA is easily the most complex thing here
and would be the command/event to deprioritise if we had any issues
getting this done/agreeing something for 9.4.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Robert Haas
robertmhaas@gmail.com
In reply to: Jim Nasby (#17)
Re: Add CREATE support to event triggers

On Thu, Jan 9, 2014 at 5:17 PM, Jim Nasby <jim@nasby.net> wrote:

On 1/9/14, 11:58 AM, Alvaro Herrera wrote:

Robert Haas escribió:

On Wed, Jan 8, 2014 at 10:27 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Hmm. This seems like a reasonable thing to do, except that I would like
the "output" to always be the constant, and have some other way to
enable the clause or disable it. With your "present" boolean:
so

"if_not_exists": {"output": "IF NOT EXISTS",
"present": true/false}

Why not:

"if_not_exists": true/false

Yeah, that's another option. If we do this, though, the expansion
function would have to know that an "if_not_exist" element expands to IF
NOT EXISTS. Maybe that's okay. Right now, the expansion function is
pretty stupid, which is nice.

Yeah, the source side of this will always have to understand the nuances of
every command; it'd be really nice to not burden the other side with that as
well. The only downside I see is a larger JSON output, but meh.

Another advantage is if you really wanted to you could modify the output
formatting in the JSON doc to do something radically different if so
inclined...

Yeah. I wasn't necessarily objecting to the way Alvaro did it, just
asking why he did it that way.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#18)
Re: Add CREATE support to event triggers

On Fri, Jan 10, 2014 at 10:36 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 8 January 2014 20:42, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy";

Hmm, given in 9.3 it was OK to have only DROP event triggers, I think
it should be equally acceptable to have just CREATE, but without every
option on CREATE. CREATE SCHEMA is easily the most complex thing here
and would be the command/event to deprioritise if we had any issues
getting this done/agreeing something for 9.4.

I don't know that I agree with that, but I guess we can cross that
bridge when we come to it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#20)
Re: Add CREATE support to event triggers

On 10 January 2014 15:48, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jan 10, 2014 at 10:36 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 8 January 2014 20:42, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy";

Hmm, given in 9.3 it was OK to have only DROP event triggers, I think
it should be equally acceptable to have just CREATE, but without every
option on CREATE. CREATE SCHEMA is easily the most complex thing here
and would be the command/event to deprioritise if we had any issues
getting this done/agreeing something for 9.4.

I don't know that I agree with that, but I guess we can cross that
bridge when we come to it.

We've come to it...

You would prefer either everything or nothing?? On what grounds?

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#21)
Re: Add CREATE support to event triggers

On Fri, Jan 10, 2014 at 10:55 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 10 January 2014 15:48, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jan 10, 2014 at 10:36 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 8 January 2014 20:42, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy";

Hmm, given in 9.3 it was OK to have only DROP event triggers, I think
it should be equally acceptable to have just CREATE, but without every
option on CREATE. CREATE SCHEMA is easily the most complex thing here
and would be the command/event to deprioritise if we had any issues
getting this done/agreeing something for 9.4.

I don't know that I agree with that, but I guess we can cross that
bridge when we come to it.

We've come to it...

You would prefer either everything or nothing?? On what grounds?

I hardly think I need to justify that position. That's project policy
and always has been. When somebody implements 50% of a feature, or
worse yet 95% of a feature, it violates the POLA for users and doesn't
always subsequently get completed, leaving us with long-term warts
that are hard to eliminate. It's perfectly fine to implement a
feature incrementally if the pieces are individually self-consistent
and ideally even useful, but deciding to support every command except
one because the last one is hard to implement doesn't seem like a
principled approach to anything. It's not even obvious to me that
CREATE SCHEMA is all that much harder than anything else and Alvaro
has not said that that's the only thing he can't implement (or why) so
I think it's entirely premature to make the decision now about which
way to proceed - but, OK, sure, if you want to force the issue now,
then yeah, I think it's better to have everything or nothing than to
have support for only some things justified by nothing more than
implementation complexity.

Aside from the general issue, in this particular case, I have
previously and repeatedly expressed concerns about regression test
coverage and suggested a path that would guarantee thorough regression
testing but which would require that support be complete for
everything present in our regression tests. Although there may be
some other plan for guaranteeing thorough regression testing not only
now but going forward, I have not seen it proposed here.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#22)
Re: Add CREATE support to event triggers

On 10 January 2014 17:07, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jan 10, 2014 at 10:55 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 10 January 2014 15:48, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jan 10, 2014 at 10:36 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 8 January 2014 20:42, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

CREATE SCHEMA IF NOT EXISTS "some schema" AUTHORIZATION "some guy";

Hmm, given in 9.3 it was OK to have only DROP event triggers, I think
it should be equally acceptable to have just CREATE, but without every
option on CREATE. CREATE SCHEMA is easily the most complex thing here
and would be the command/event to deprioritise if we had any issues
getting this done/agreeing something for 9.4.

I don't know that I agree with that, but I guess we can cross that
bridge when we come to it.

We've come to it...

You would prefer either everything or nothing?? On what grounds?

I hardly think I need to justify that position.

Yeh, you do. Everybody does.

That's project policy
and always has been. When somebody implements 50% of a feature, or
worse yet 95% of a feature, it violates the POLA for users and doesn't
always subsequently get completed, leaving us with long-term warts
that are hard to eliminate.

So why was project policy violated when we released 9.3 with only DROP
event support? Surely that was a worse violation of POLA than my
suggestion?

It's not reasonable to do something yourself and then object when
others suggest doing the same thing.

After 3 years we need something useful. I think the "perfect being the
enemy of the good" argument applies here after this length of time.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Robert Haas
robertmhaas@gmail.com
In reply to: Simon Riggs (#23)
Re: Add CREATE support to event triggers

On Fri, Jan 10, 2014 at 12:59 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

That's project policy
and always has been. When somebody implements 50% of a feature, or
worse yet 95% of a feature, it violates the POLA for users and doesn't
always subsequently get completed, leaving us with long-term warts
that are hard to eliminate.

So why was project policy violated when we released 9.3 with only DROP
event support? Surely that was a worse violation of POLA than my
suggestion?

Well, obviously I didn't think so at the time, or I would have
objected. I felt, and still feel, that implementing one kind of event
trigger (drop) does not necessarily require implementing another kind
(create). I think that's clearly different from implementing either
one for only some object types.

"This event trigger will be called whenever an object is dropped" is a
reasonable contract with the user. "This other event trigger will be
called whenever an object is created, unless it happens to be a
schema" is much less reasonable.

At least in my opinion.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Simon Riggs
simon@2ndQuadrant.com
In reply to: Robert Haas (#24)
Re: Add CREATE support to event triggers

On 10 January 2014 18:17, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jan 10, 2014 at 12:59 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

That's project policy
and always has been. When somebody implements 50% of a feature, or
worse yet 95% of a feature, it violates the POLA for users and doesn't
always subsequently get completed, leaving us with long-term warts
that are hard to eliminate.

So why was project policy violated when we released 9.3 with only DROP
event support? Surely that was a worse violation of POLA than my
suggestion?

Well, obviously I didn't think so at the time, or I would have
objected. I felt, and still feel, that implementing one kind of event
trigger (drop) does not necessarily require implementing another kind
(create). I think that's clearly different from implementing either
one for only some object types.

"This event trigger will be called whenever an object is dropped" is a
reasonable contract with the user. "This other event trigger will be
called whenever an object is created, unless it happens to be a
schema" is much less reasonable.

At least in my opinion.

In the fullness of time, I agree that is not a restriction we should maintain.

Given that CREATE SCHEMA with multiple objects is less well used, its
a reasonable restriction to accept for one release, if the alternative
is to implement nothing at all of value. Especially since we are now
in the third year of development of this set of features, it is time
to reduce the scope to ensure delivery.

There may be other ways to ensure something of value is added, this
was just one suggestion.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#2)
Re: Add CREATE support to event triggers

Can we please stop arguing over a problem I don't have? I started with
CREATE SCHEMA because it is one of the easy cases, not because it was
the most difficult case: we only need to deparse the bits of it that
don't involve the objects within, because those are reported by the
event trigger as separate commands. Each object gets its own creation
command, which works pretty nicely. Of course, the deparsed version of
the command will not look very much like what was submitted by the user,
but that doesn't matter -- what does matter is that the objects created
by running the commands reported in the event trigger will be (or should
be) the same as those created by the original command.

I was showing CREATE SCHEMA as a way to discuss the fine details: how to
report identifiers that might need quoting, what to do with optional
clauses (AUTHORIZATION), etc. I am past that now.

On the subject of testing the triggers, Robert Haas wrote:

Here's one idea: create a contrib module that (somehow, via APIs to be
invented) runs every DDL command that gets executed through the
deparsing code, and then parses the result and executes *that* instead
of the original command. Then, add a build target that runs the
regression test suite in that mode, and get the buildfarm configured
to run that build target regularly on at least some machines. That
way, adding syntax to the regular regression test suite also serves to
test that the deparsing logic for that syntax is working. If we do
this, there's still some maintenance burden associated with having DDL
deparsing code, but at least our chances of noticing when we've failed
to maintain it should be pretty good.

I gave this some more thought and hit a snag. The problem here is that
by the time the event trigger runs, the original object has already been
created. At that point, we can't simply replace the created objects
with objects that would hypothetically be created by a command trigger.

A couple of very hand-wavy ideas:

1. in the event trigger, DROP the original object and CREATE it as
reported by the creation_commands SRF.

2. Have ddl_command_start open a savepoint, and then roll it back in
ddl_command_end, then create the object again. Not sure this is doable
because of the whole SPI nesting issue .. maybe with C-language event
trigger functions?

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Jim Nasby
jim@nasby.net
In reply to: Simon Riggs (#25)
Re: Add CREATE support to event triggers

On 1/10/14, 3:40 PM, Simon Riggs wrote:

Given that CREATE SCHEMA with multiple objects is less well used, its
a reasonable restriction to accept for one release, if the alternative
is to implement nothing at all of value. Especially since we are now
in the third year of development of this set of features, it is time
to reduce the scope to ensure delivery.

ISTM that since you can nest JSON then CREATE SCHEMA just needs to support providing an array of other JSON definitions, no? And it doesn't seem like it should be that hard to code that up.

That said, if it's a choice between getting a bunch of CREATE blah support or getting nothing because of CREATE SCHEMA with objects then as long as we have a scheme that supports the full CREATE SCHEMA feature we shouldn't hold up just because it's not coded.
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Jim Nasby
jim@nasby.net
In reply to: Alvaro Herrera (#26)
Re: Add CREATE support to event triggers

On 1/10/14, 5:22 PM, Alvaro Herrera wrote:

Here's one idea: create a contrib module that (somehow, via APIs to be

invented) runs every DDL command that gets executed through the
deparsing code, and then parses the result and executes*that* instead
of the original command. Then, add a build target that runs the
regression test suite in that mode, and get the buildfarm configured
to run that build target regularly on at least some machines. That
way, adding syntax to the regular regression test suite also serves to
test that the deparsing logic for that syntax is working. If we do
this, there's still some maintenance burden associated with having DDL
deparsing code, but at least our chances of noticing when we've failed
to maintain it should be pretty good.

I gave this some more thought and hit a snag. The problem here is that
by the time the event trigger runs, the original object has already been
created. At that point, we can't simply replace the created objects
with objects that would hypothetically be created by a command trigger.

A couple of very hand-wavy ideas:

1. in the event trigger, DROP the original object and CREATE it as
reported by the creation_commands SRF.

2. Have ddl_command_start open a savepoint, and then roll it back in
ddl_command_end, then create the object again. Not sure this is doable
because of the whole SPI nesting issue .. maybe with C-language event
trigger functions?

What if we don't try and do this all in one shot? I'm thinking let the original DDL do it's thing while capturing the re-parsed commands in order somewhere. Dump those commands into a brand new database and use that for testing.
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Simon Riggs
simon@2ndQuadrant.com
In reply to: Alvaro Herrera (#26)
Re: Add CREATE support to event triggers

On 10 January 2014 23:22, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

On the subject of testing the triggers, Robert Haas wrote:

Here's one idea: create a contrib module that (somehow, via APIs to be
invented) runs every DDL command that gets executed through the
deparsing code, and then parses the result and executes *that* instead
of the original command. Then, add a build target that runs the
regression test suite in that mode, and get the buildfarm configured
to run that build target regularly on at least some machines. That
way, adding syntax to the regular regression test suite also serves to
test that the deparsing logic for that syntax is working. If we do
this, there's still some maintenance burden associated with having DDL
deparsing code, but at least our chances of noticing when we've failed
to maintain it should be pretty good.

I gave this some more thought and hit a snag. The problem here is that
by the time the event trigger runs, the original object has already been
created. At that point, we can't simply replace the created objects
with objects that would hypothetically be created by a command trigger.

Yes, all we are doing is firing a trigger that has access to the
information about the current object.

A couple of very hand-wavy ideas:

1. in the event trigger, DROP the original object and CREATE it as
reported by the creation_commands SRF.

2. Have ddl_command_start open a savepoint, and then roll it back in
ddl_command_end, then create the object again. Not sure this is doable
because of the whole SPI nesting issue .. maybe with C-language event
trigger functions?

We need to be clear what action we are testing exactly. Once we know
that we can come up with a test mechanism.

We can come up with various tests that test coverage but that may not
be enough. Mostly we're just splitting up fields from the DDL, like
object name into its own text field, as well as augmenting it with
database name. That's pretty simple and shouldn't require much
testing. This code seems likely to be prone to missing features much
more so than actual bugs. Missing a piece of useful information in the
JSON doesn't mean there is a bug.

If all the event trigger does is generate JSON, then all we need to do
is test that the trigger fired and generated well-formed JSON with the
right information content. That should be very simple and I would say
the simpler the better.
To do that we can run code to parse the JSON and produce formatted
text output like this...
Field | Value
---------+-----------
XX afagaha
YY aamnamna
The output can be derived from visual inspection, proving that we
generated the required JSON.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#9)
Re: Add CREATE support to event triggers

Alvaro Herrera escribi�:

In an event trigger, the function pg_event_trigger_get_creation_commands()
returns the following JSON blob:

After playing with this for a while, I realized something that must have
seemed quite obvious to those paying attention: what this function is,
is just a glorified sprintf() for JSON. So I propose we take our
existing format(text) and use it to model a new format(json) function,
which will be useful to the project at hand and be of more general
applicability.

To make it a better fit, I have changed the spec slightly. The format
string is now the "fmt" element in the topmost JSON. This format string
can contain % escapes, which consist of:

* the literal % itself
* an element name, enclosed in braces { }. The name can optionally be
followed by a colon and a possibly-empty array separator.
* a format specifier, which can be I (identifier), D (dotted name), or s
(string)
* Alternatively, %% expands to a literal %, as usual.

For each such escape, the JSON object is searched using the element name
as key. For identifiers, the element is expected to be a string, and
will be quoted per identifier quoting rules. Dotted-names are used to
format possibly-qualified relation names and such; the element must be
an object with one, two or three string elements, each of which is
quoted per identifier rules, and output separated by periods.

Finally, for arrays we expand each element in the JSON array element,
and separate them with the separator specified in the {} part of the
format specifier.

For instance,
alvherre=# select format(json '{"fmt":"hello, %{who}s! This is %{name}I", "who":"world", "name":"a function"}');
format
------------------------------------------
hello, world! This is "a function"

Elements can be objects, in which case they are expanded recursively: a
"fmt" element is looked up and expanded as described above.

I don't yet see a need for %L escapes (that is, literals that can expand
to a single-quoted value or to NULL), but if I see it I will add that
too.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#30)
Re: Add CREATE support to event triggers

Hello

2014/1/13 Alvaro Herrera <alvherre@2ndquadrant.com>

Alvaro Herrera escribió:

In an event trigger, the function

pg_event_trigger_get_creation_commands()

returns the following JSON blob:

After playing with this for a while, I realized something that must have
seemed quite obvious to those paying attention: what this function is,
is just a glorified sprintf() for JSON. So I propose we take our
existing format(text) and use it to model a new format(json) function,
which will be useful to the project at hand and be of more general
applicability.

To make it a better fit, I have changed the spec slightly. The format
string is now the "fmt" element in the topmost JSON. This format string
can contain % escapes, which consist of:

* the literal % itself
* an element name, enclosed in braces { }. The name can optionally be
followed by a colon and a possibly-empty array separator.
* a format specifier, which can be I (identifier), D (dotted name), or s
(string)
* Alternatively, %% expands to a literal %, as usual.

For each such escape, the JSON object is searched using the element name
as key. For identifiers, the element is expected to be a string, and
will be quoted per identifier quoting rules. Dotted-names are used to
format possibly-qualified relation names and such; the element must be
an object with one, two or three string elements, each of which is
quoted per identifier rules, and output separated by periods.

Finally, for arrays we expand each element in the JSON array element,
and separate them with the separator specified in the {} part of the
format specifier.

For instance,
alvherre=# select format(json '{"fmt":"hello, %{who}s! This is %{name}I",
"who":"world", "name":"a function"}');
format
------------------------------------------
hello, world! This is "a function"

Elements can be objects, in which case they are expanded recursively: a
"fmt" element is looked up and expanded as described above.

I don't yet see a need for %L escapes (that is, literals that can expand
to a single-quoted value or to NULL), but if I see it I will add that
too.

I am not against to this idea, although I don't see a strong benefit. .
Just special function can be better - it has minimal relation to variadic
"function" format - and nested mixed format can be messy

Show quoted text

--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#26)
Re: Add CREATE support to event triggers

On Fri, Jan 10, 2014 at 6:22 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Here's one idea: create a contrib module that (somehow, via APIs to be
invented) runs every DDL command that gets executed through the
deparsing code, and then parses the result and executes *that* instead
of the original command. Then, add a build target that runs the
regression test suite in that mode, and get the buildfarm configured
to run that build target regularly on at least some machines. That
way, adding syntax to the regular regression test suite also serves to
test that the deparsing logic for that syntax is working. If we do
this, there's still some maintenance burden associated with having DDL
deparsing code, but at least our chances of noticing when we've failed
to maintain it should be pretty good.

I gave this some more thought and hit a snag. The problem here is that
by the time the event trigger runs, the original object has already been
created. At that point, we can't simply replace the created objects
with objects that would hypothetically be created by a command trigger.

Hmm, so these triggers are firing after the corresponding actions have
been performed. Yeah, that's tricky. I don't immediately have an
idea how to come up with a comprehensive test framework for that,
although I do think that the way you're structuring the JSON blobs
makes this a whole lot less error-prone than the approaches that have
been explored previously. The big question in my mind is still "if
somebody updates the grammar in gram.y, how do we make sure that they
notice that this stuff needs to be updated to match?". Ideally the
answer is "if they don't, the regression tests fail".

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Jim Nasby
jim@nasby.net
In reply to: Robert Haas (#32)
Re: Add CREATE support to event triggers

On 1/14/14, 2:05 PM, Robert Haas wrote:

On Fri, Jan 10, 2014 at 6:22 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Here's one idea: create a contrib module that (somehow, via APIs to be
invented) runs every DDL command that gets executed through the
deparsing code, and then parses the result and executes *that* instead
of the original command. Then, add a build target that runs the
regression test suite in that mode, and get the buildfarm configured
to run that build target regularly on at least some machines. That
way, adding syntax to the regular regression test suite also serves to
test that the deparsing logic for that syntax is working. If we do
this, there's still some maintenance burden associated with having DDL
deparsing code, but at least our chances of noticing when we've failed
to maintain it should be pretty good.

I gave this some more thought and hit a snag. The problem here is that
by the time the event trigger runs, the original object has already been
created. At that point, we can't simply replace the created objects
with objects that would hypothetically be created by a command trigger.

Hmm, so these triggers are firing after the corresponding actions have
been performed. Yeah, that's tricky. I don't immediately have an
idea how to come up with a comprehensive test framework for that,
although I do think that the way you're structuring the JSON blobs
makes this a whole lot less error-prone than the approaches that have
been explored previously. The big question in my mind is still "if
somebody updates the grammar in gram.y, how do we make sure that they
notice that this stuff needs to be updated to match?". Ideally the
answer is "if they don't, the regression tests fail".

Can't we capture the JSON, use that to create a new database, and then test against that? Unfortunately it'd be easiest to do that at a high level and not for individual commands, but it's a lot better than nothing...
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Pavel Stehule (#31)
1 attachment(s)
Re: Add CREATE support to event triggers

Pavel Stehule escribi�:

2014/1/13 Alvaro Herrera <alvherre@2ndquadrant.com>

After playing with this for a while, I realized something that must have
seemed quite obvious to those paying attention: what this function is,
is just a glorified sprintf() for JSON. So I propose we take our
existing format(text) and use it to model a new format(json) function,
which will be useful to the project at hand and be of more general
applicability.

I am not against to this idea, although I don't see a strong benefit. .
Just special function can be better - it has minimal relation to variadic
"function" format - and nested mixed format can be messy

Yeah. I eventually realized that I need some very specialized format
specifiers here; I invented on %{}T specifier, for instance, which is
used to format types. So I think this is better confined to expansion
of SQL commands rather than a generic string formatter.

So here's a patch implementing the ideas expressed in this thread.
There are two new SQL functions:

pg_event_trigger_get_creation_commands()
Can be called in a ddl_command_end event, and returns a JSON blob for
each object created in that command.

pg_event_trigger_expand_command()
Takes such a JSON blob and turns it back into an executable command.

The usefulness of this combination is that the user can edit the JSON
between those two calls, say by adding new columns or changing or
removing schema specifications, tablespaces, and so on.

One interesting bit I had to add was format_type_detailed(). This
function returns a type specification in minute detail: schema, type
name, typemod, array are all returned separately. This might seem
overkill, but if we want to let the user mess with column definitions, I
think it's necessary.

Some things are uglier than I would like -- one reason is I stayed away
from using the JSON things too directly. There are at least two patches
in that area, and some helpers might appear that help this patch.
However, at the moment I am not sure whether the end result would be
better or worse, and I don't want to make this patch depend on some
other patch which might or might not end up being applied. In any case,
the JSON stuff is pretty localized so it should be reasonably easy to
rip out and replace. The first half of deparse_utility.c is concerned
with a simple-minded mechanism to accumulate an object hierarchy until
it's time to convert it to proper JSON. Perhaps the new JSON stuff will
make it possible to rip that all out.

The JSON parsing is done in event_trigger.c. This code should probably
live elsewhere, but I again hesitate to put it in json.c or jsonfuncs.c,
at least until some discussion about its general applicability takes
place.

The second half of deparse_utility.c is concerned with actually
processing the parse nodes to construct objects. There are several
cases missing (at the moment, only CREATE SCHEMA, CREATE TABLE, CREATE
INDEX and CREATE SEQUENCE are supported, and in each of them there are
some things missing). This is code tedious to write but not difficult.

To see this stuff in action, an event trigger function such as this is
useful:

CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger LANGUAGE plpgsql AS $$
DECLARE
r RECORD;
BEGIN
FOR r IN SELECT * FROM pg_event_trigger_get_creation_commands()
LOOP
RAISE NOTICE 'JSON blob: %', r.command;
RAISE NOTICE 'expanded: %', pg_event_trigger_expand_command(r.command::json);
END LOOP;
END;
$$;

CREATE EVENT TRIGGER snitch ON ddl_command_end
when tag in ('create schema', 'create table', 'create index', 'create sequence')
EXECUTE PROCEDURE snitch();

Then execute commands to your liking.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

event-trigger-create-2.patchtext/x-diff; charset=us-asciiDownload
*** 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);
#35Andres Freund
andres@2ndquadrant.com
In reply to: Alvaro Herrera (#34)
Re: Add CREATE support to event triggers

On 2014-01-15 02:11:11 -0300, Alvaro Herrera wrote:

Then execute commands to your liking.

So, I am giving a quick look, given that I very likely will have to
write a consumer for it...

* deparse_utility_command returns payload via parameter, not return
value?
* functions beginning with an underscore formally are reserved, we
shouldn't add new places using such a convention.
* I don't think dequote_jsonval is correct as is, IIRC that won't
correctly handle unicode escapes and such. I think json.c needs to
expose functionality for this.
* I wonder if expand_jsonval_identifier shouldn't truncate as well? It
should get handled by the individual created commands, right?
* So, if I read things correctly, identifiers in json are never
downcased, is that correct? I.e. they are implicitly quoted?
* Why must we not schema qualify system types
(c.f. expand_jsonval_typename)? It seems to be perfectly sensible to
me to just use pg_catalog there.
* It looks like pg_event_trigger_expand_command will misparse nested {,
error out instead?
* I wonder if deparseColumnConstraint couldn't somehow share a bit more
code with ruleutils.c's pg_get_constraintdef_worker().
* I guess you know, but deparseColumnConstraint() doesn't handle foreign
keys yet.
* Is tcop/ the correct location for deparse_utility.c? I wonder if it
shouldn't be alongside ruleutils.c instead.
* shouldn't pg_event_trigger_get_creation_commands return "command" as
json instead of text?

* Not your department, but a builtin json pretty printer would be
really, really handy. Something like
CREATE FUNCTION json_prettify(j json)
RETURNS TEXT AS $$
import json
return json.dumps(json.loads(j), sort_keys=True, indent=4)
$$ LANGUAGE PLPYTHONU;
makes the json so much more readable.

Some minimal tests:
* CREATE SEQUENCE errors out with:
NOTICE: JSON blob: {"sequence":{"relation":"frakbar2","schema":"public"},"persistence":"","fmt":"CREATE %{persistence}s SEQUENCE %{identity}D"}
ERROR: non-existant element "identity" in JSON formatting object
*CREATE TABLE frakkbar2(id int); error out with:
postgres=# CREATE TABLE frakkbar2(id int);
NOTICE: JSON blob: {"on_commit":{"present":false,"on_commit_value":null,"fmt":"ON COMMIT %{on_commit_value}s"},"tablespace":{"present":false,"tablespace":null,"fmt":"TABLESPACE %{tablespace}I"},"inherits":{"present":false,"parents":null,"fmt":"INHERITS (%{parents:, }D)"},"table_elements":[{"collation":{"present":false,"fmt":"COLLATE %{name}I"},"type":{"typmod":"","typename":"integer","is_system":true,"is_array":false},"name":"id","fmt":"%{name}I %{type}T %{collation}s"}],"of_type":{"present":false,"of_type":null,"fmt":"OF %{of_type}T"},"if_not_exists":"","identity":{"relation":"frakkbar2","schema":"public"},"persistence":"","fmt":"CREATE %{persistence}s TABLE %{identity}D %{if_not_exists}s %{of_type}s (%{table_elements:, }s) %{inherits}s %{on_commit}s %{tablespace}s"}
ERROR: invalid NULL is_system flag in %T element
CONTEXT: PL/pgSQL function snitch() line 8 at RAISE

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Andres Freund (#35)
Re: Add CREATE support to event triggers

Andres Freund escribi�:

* Why must we not schema qualify system types
(c.f. expand_jsonval_typename)? It seems to be perfectly sensible to
me to just use pg_catalog there.

So, the reason for doing things this way is to handle cases like
"varchar(10)" being turned into "character varying"; and that name
requires that the typename NOT be schema-qualified, otherwise it fails.
But thinking about this again, I don't see a reason why this can't be
returned simply as pg_catalog.varchar(10); this should work fine on the
receiving end as well, and give the same result.

The other cases I'm worried about are types like bit(1) vs. unadorned
bit vs. double-quoted "bit", and "char", etc. I'm not sure I'm dealing
with them correctly right now. So even if by the above paragraph I
could make the is_system thingy go away, I might still need it to cover
this case.

Thanks for the review, I will post an updated version later after fixing
the other issues you mentioned plus adding support for more commands.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#36)
Re: Add CREATE support to event triggers

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

So, the reason for doing things this way is to handle cases like
"varchar(10)" being turned into "character varying"; and that name
requires that the typename NOT be schema-qualified, otherwise it fails.
But thinking about this again, I don't see a reason why this can't be
returned simply as pg_catalog.varchar(10); this should work fine on the
receiving end as well, and give the same result.

I think people would be unhappy if we changed the output of, say, pg_dump
that way. But it's presumably not a problem for strings inside event
triggers. Once upon a time, the typmods would have been an issue, but now
that we support them in generic typename syntax I think we're good.

The other cases I'm worried about are types like bit(1) vs. unadorned
bit vs. double-quoted "bit", and "char", etc. I'm not sure I'm dealing
with them correctly right now. So even if by the above paragraph I
could make the is_system thingy go away, I might still need it to cover
this case.

Yeah, there are some weird cases there. I think you can make them go away
if you always print the fully qualified type name, ie don't omit
"pg_catalog", and use pg_type.typname not any converted version (and don't
forget to double-quote anything that might be a reserved word).
But I've not looked closely at the code.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Alvaro Herrera (#34)
Re: Add CREATE support to event triggers

Hi,

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

So here's a patch implementing the ideas expressed in this thread.
There are two new SQL functions:

I spent some time reviewing the patch and tried to focus on a higher
level review, as I saw Andres already began with the lower level stuff.

The main things to keep in mind here are:

- this patch enables running Event Triggers anytime a new object is
created, in a way that the user code is run once the object already
made it through the catalogs;

- the Event Trigger code has access to the full details about every
created object, so it's not tied to a command but really the fact
that an object just was created in the catalogs;

(it's important with serial and primary key sub-commands)

- facilities are provided so that it's possible to easily build an SQL
command that if executed would create the exact same object again;

- the facilities around passing the created object details and
building a SQL command are made in such a way that it's trivially
possible to "hack" away the captured objects properties before
producing again a new SQL command.

After careful study and thinking, it appears to me that the proposed
patch addresses the whole range of features we expect here, and is both
flexible enough for the users and easy enough to maintain.

The event being fired once the objects are available in the catalogs
makes it possible for the code providing the details in the JSON format
to complete the parsetree with necessary information.

Current state of the patch is not ready for commit yet, independant of
code details some more high-level work needs to be done:

- preliminary commit

It might be a good idea to separate away some pre-requisites of this
patch and commit them separately: the OID publishing parts and
allowing an Event Trigger to get fired after CREATE but without the
extra detailed JSON formated information might be good commits
already, and later add the much needed details about what did
happen.

- document the JSON format

I vote for going with the proposed format, because it actually
allows to implement both the audit and replication features we want,
with the capability of hacking schema, data types, SQL
representation etc; and because I couldn't think of anything better
than what's proposed here ;-)

- other usual documentation

I don't suppose I have to expand on what I mean here…

- fill-in other commands

Not all commands are supported in the submitted patch. I think once
we have a clear documentation on the general JSON formating and how
to use it as a user, we need to include support for all CREATE
commands that we have.

I see nothing against extending when this work has to bo done until
after the CF, as long as it's fully done before beta. After all it's
only about filling in minutia at this point.

- review the JSON producing code

It might be possible to use more of the internal supports for JSON
now that the format is freezing.

- regression tests

The patch will need some. The simpler solution is to add a new
regression test entry and exercise all the CREATE commands in there,
in a specific schema, activating an event trigger that outputs the
JSON detailed information each time (the snitch() example).

Best would be to have some "pretty" indented output of the JSON to
help with reviewing diffs, and I have to wonder about JSON object
inner-ordering if we're going to do that.

No other ideas on this topic from me.

The JSON parsing is done in event_trigger.c. This code should probably
live elsewhere, but I again hesitate to put it in json.c or jsonfuncs.c,
at least until some discussion about its general applicability takes
place.

I see that as useful enough if it can be made to work without the
special "fmt" fields somehow, with a nice default formatting ability.

In particular, being able to build some intermediate object with
json_agg then call the formating/expanding function on top of that might
be quite useful.

That said, I don't think we have enough time to attack this problem now,
I think it would be wiser to address your immediate problem separately
in your patch and clean it later (next release) with sharing code and
infrastructure and offering a more generally useful tool. At least we
will have some feedback about the Event Trigger specific context then.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Dimitri Fontaine (#38)
1 attachment(s)
Re: Add CREATE support to event triggers

Hi,

Dimitri, thanks for the review.

Here's a new version of this patch. I focused on a small set of
commands for now, with the intention of building something that could
serve as a useful foundation for an interesting percentage of use cases.
There are enough strange things going on in this code that I thought I
should go one step further in the foundations before I go much further
in supporting the rest of the commands, which will be mostly tedious
additions of extracting info from catalogs to immediately emit as JSON
blobs.

So this version supports the following commands:
CREATE SCHEMA
CREATE TABLE
CREATE SEQUENCE
CREATE INDEX
CREATE VIEW

I have run into some issues, though:

1. certain types, particularly timestamp/timestamptz but really this
could happen for any type, have unusual typmod output behavior. For
those one cannot just use the schema-qualified catalog names and then
append the typmod at the end; because what you end up is something like
pg_catalog.timestamptz(4) with time zone
because, for whatever reason, the "with time zone" is part of typmod
output. But this doesn't work at all for input. I'm not sure how to
solve this.

2. I have been having object definitions be emitted complete; in
particular, sequences have OWNED BY clauses when they have an owning
column. But this doesn't work with a SERIAL column, because we get
output like this:

alvherre=# CREATE TABLE public.hijo (b serial);
NOTICE: expanded: CREATE SEQUENCE public.hijo_b_seq INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE OWNED BY public.hijo.b
NOTICE: expanded: CREATE TABLE public.hijo (b pg_catalog.int4 DEFAULT nextval('hijo_b_seq'::regclass) NOT NULL )

which is all nice, except that the sequence is using the column name as
owner before the column has been created in the first place. Both these
command will, of course, fail, because both depend on the other to have
been executed first. The tie is easy to break in this case: just don't
emit the OWNED BY clause .. but it makes me a bit nervous to be
hardcoding the decision of parts that might depend on others. OTOH
pg_dump already knows how to split objects in constituent parts as
necessary; maybe it's not so bad.

3. It is possible to coerce ruleutils.c to emit always-qualified names
by using PushOverrideSearchPath() facility; but actually this doesn't
always work, because some places in namespace.c believe that
PG_CATALOG_NAMESPACE is always visible and so certain objects are not
qualified. In particular, text columns using default collation will be
emitted as having collation "default" and not pg_catalog.default as I
would have initially expected. Right now it doesn't seem like this is a
problem, but it is unusual.

Dimitri Fontaine wrote:

After careful study and thinking, it appears to me that the proposed
patch addresses the whole range of features we expect here, and is both
flexible enough for the users and easy enough to maintain.

Excellent, thanks.

- preliminary commit

It might be a good idea to separate away some pre-requisites of this
patch and commit them separately: the OID publishing parts and
allowing an Event Trigger to get fired after CREATE but without the
extra detailed JSON formated information might be good commits
already, and later add the much needed details about what did
happen.

I agree that perhaps the OID publishing bits should be pushed prior to
the rest of the patch. It's two minor changes anyway that only affect
CREATE TABLE AS and REFRESH MATVIEW as far as I remember; the rest of
the commands already return the OID of the affected object.

I'm not sure about pushing the bits to have a trigger to fire before
having the deparse utility stuff. I guess it is useful because it will
provide classID/objectID/identity for all created objects, even if we
don't have the creation command. But changing the API of
pg_get_creation_commands() in a later release might be problematic.

- document the JSON format

I vote for going with the proposed format, because it actually
allows to implement both the audit and replication features we want,
with the capability of hacking schema, data types, SQL
representation etc; and because I couldn't think of anything better
than what's proposed here�;-)

That's great to hear. Since the previous patch I have added a %{}O
escape that is used to format operators. I think that makes the set of
format specifiers mostly complete.

- review the JSON producing code

It might be possible to use more of the internal supports for JSON
now that the format is freezing.

Yeah. I realized that the trick through row_to_json is likely to fail
when the objects have more than 1600 columns. Not sure if this is a
problem in practice (haven't tried creating a 1600-column table yet, but
I think that wouldn't fail because table elements are handed as arrays).
Anyway if we want to use the new json_build facilities, we'd need to
serialize from the in-memory representation I'm using to the text
format, and from there to JSON. Not real sure that that's an
improvement ...

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

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

On Tue, Feb 4, 2014 at 12:11 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I have run into some issues, though:

1. certain types, particularly timestamp/timestamptz but really this
could happen for any type, have unusual typmod output behavior. For
those one cannot just use the schema-qualified catalog names and then
append the typmod at the end; because what you end up is something like
pg_catalog.timestamptz(4) with time zone
because, for whatever reason, the "with time zone" is part of typmod
output. But this doesn't work at all for input. I'm not sure how to
solve this.

How about doing whatever pg_dump does?

2. I have been having object definitions be emitted complete; in
particular, sequences have OWNED BY clauses when they have an owning
column. But this doesn't work with a SERIAL column, because we get
output like this:

alvherre=# CREATE TABLE public.hijo (b serial);
NOTICE: expanded: CREATE SEQUENCE public.hijo_b_seq INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1 NO CYCLE OWNED BY public.hijo.b
NOTICE: expanded: CREATE TABLE public.hijo (b pg_catalog.int4 DEFAULT nextval('hijo_b_seq'::regclass) NOT NULL )

which is all nice, except that the sequence is using the column name as
owner before the column has been created in the first place. Both these
command will, of course, fail, because both depend on the other to have
been executed first. The tie is easy to break in this case: just don't
emit the OWNED BY clause .. but it makes me a bit nervous to be
hardcoding the decision of parts that might depend on others. OTOH
pg_dump already knows how to split objects in constituent parts as
necessary; maybe it's not so bad.

Well, the sequence can't depend on a table column that doesn't exist
yet, so if it's in effect doing what you've shown there, it's
"cheating" by virtue of knowing that nobody can observe the
intermediate state. Strictly speaking, there's nothing "wrong" with
emitting those commands just as you have them there; they won't run,
but if what you want to do is log what's happened rather than replay
it, that's OK. Producing output that is actually executable is a
strictly harder problem than producing output that accurately
describes what happened. As you say, pg_dump already splits things
and getting executable output out of this facility will require the
same kinds of tricks here. This gets back to my worry about
maintaining two or three copies of the code that solve many of the
same problems in quite different ways...

3. It is possible to coerce ruleutils.c to emit always-qualified names
by using PushOverrideSearchPath() facility; but actually this doesn't
always work, because some places in namespace.c believe that
PG_CATALOG_NAMESPACE is always visible and so certain objects are not
qualified. In particular, text columns using default collation will be
emitted as having collation "default" and not pg_catalog.default as I
would have initially expected. Right now it doesn't seem like this is a
problem, but it is unusual.

We have a quote_all_identifiers flag. We could have a
schema_qualify_all_identifiers flag, too. Then again, why is the
behavior of schema-qualifying absolutely everything even desirable?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Robert Haas (#40)
Re: Add CREATE support to event triggers

Robert Haas escribi�:

On Tue, Feb 4, 2014 at 12:11 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I have run into some issues, though:

1. certain types, particularly timestamp/timestamptz but really this
could happen for any type, have unusual typmod output behavior. For
those one cannot just use the schema-qualified catalog names and then
append the typmod at the end; because what you end up is something like
pg_catalog.timestamptz(4) with time zone
because, for whatever reason, the "with time zone" is part of typmod
output. But this doesn't work at all for input. I'm not sure how to
solve this.

How about doing whatever pg_dump does?

We use format_type() for that as far as I know. What it does
differently is use undecorated names defined by the standard for some
types, which are never schema qualified and are never ambiguous because
they are reserved words that would require quoting if used by
user-defined type names. We can't use that here: somewhere upthread we
noticed issues when using those which is why we're now trying to use
catalog names instead of those special names. (I don't think it's
impossible to use such names: we just need to ensure we handle quoting
correctly for the funny cases such as "char" and "bit.)

One idea is to chop the typmod output string at the closing parens.
That seems to work well for the types that come with core code ... and
the rule with the funny string at the end after the parenthised part of
the typmod works only by type names with hardcoded productions in
gram.y, so there is no danger that outside code will have a problem with
that strategy.

(I verified PostGIS types with typmods just to see an example of
out-of-core code at work, and unsurprisingly it only emits stuff inside
parens.)

2. I have been having object definitions be emitted complete; in
particular, sequences have OWNED BY clauses when they have an owning
column. But this doesn't work with a SERIAL column, because we get
output like this:

Well, the sequence can't depend on a table column that doesn't exist
yet, so if it's in effect doing what you've shown there, it's
"cheating" by virtue of knowing that nobody can observe the
intermediate state. Strictly speaking, there's nothing "wrong" with
emitting those commands just as you have them there; they won't run,
but if what you want to do is log what's happened rather than replay
it, that's OK. Producing output that is actually executable is a
strictly harder problem than producing output that accurately
describes what happened. As you say, pg_dump already splits things
and getting executable output out of this facility will require the
same kinds of tricks here.

Yeah. Moreover this will require that this new event trigger code is
able to handle (at least certain kinds of) ALTER, which expands this
patch in scope by a wide margin.

Producing executable commands is an important goal.

This gets back to my worry about maintaining two or three copies of
the code that solve many of the same problems in quite different
ways...

Agreed. It would be real good to be able to use this code for pg_dump
too, but it seems dubious. The two environments seem just too different
to be able to reuse anything. But if you see a way, by all means shout.

3. It is possible to coerce ruleutils.c to emit always-qualified names
by using PushOverrideSearchPath() facility; but actually this doesn't
always work, because some places in namespace.c believe that
PG_CATALOG_NAMESPACE is always visible and so certain objects are not
qualified. In particular, text columns using default collation will be
emitted as having collation "default" and not pg_catalog.default as I
would have initially expected. Right now it doesn't seem like this is a
problem, but it is unusual.

We have a quote_all_identifiers flag. We could have a
schema_qualify_all_identifiers flag, too.

Actually the bit about collations was just a garden variety bug: turns
out I was using a %{}I specifier (and correspondingly only the collation
name as a string) instead of %{}D and get_catalog_object_by_oid() to
match. I'm not yet sure if this new flag you suggest will really be
needed, but thanks for the idea.

Then again, why is the behavior of schema-qualifying absolutely
everything even desirable?

Well, someone could create a collation in another schema with the same
name as a system collation and the command would become ambiguous. For
example, this command
create table f (a text collate "POSIX");
creates a column whose collation is either public."POSIX" or the system
POSIX collation, depending on whether public appears before pg_catalog
in search_path. Having someone create a POSIX collation in a schema
that appears earlier than pg_catalog is pretty far-fetched, but ISTM
that we really need to cover that scenario.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#41)
Re: Add CREATE support to event triggers

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Robert Haas escribi�:

How about doing whatever pg_dump does?

We use format_type() for that as far as I know. What it does
differently is use undecorated names defined by the standard for some
types, which are never schema qualified and are never ambiguous because
they are reserved words that would require quoting if used by
user-defined type names. We can't use that here: somewhere upthread we
noticed issues when using those which is why we're now trying to use
catalog names instead of those special names. (I don't think it's
impossible to use such names: we just need to ensure we handle quoting
correctly for the funny cases such as "char" and "bit.)

Yeah, but wouldn't that complexity also bubble into user code within the
event triggers? Since there's no real need for SQL standard compliance
in this context, I think minimizing the number of weird formats is a
win.

One idea is to chop the typmod output string at the closing parens.

+1. The only reason timestamptypmodout works like that is that we're
trying to match the SQL standard's spelling of the type names, and
that committee apparently considers it an off day whenever they can't
invent some randomly-incompatible-with-everything syntax.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#41)
Re: Add CREATE support to event triggers

On Wed, Feb 5, 2014 at 2:26 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Then again, why is the behavior of schema-qualifying absolutely
everything even desirable?

Well, someone could create a collation in another schema with the same
name as a system collation and the command would become ambiguous. For
example, this command
create table f (a text collate "POSIX");
creates a column whose collation is either public."POSIX" or the system
POSIX collation, depending on whether public appears before pg_catalog
in search_path. Having someone create a POSIX collation in a schema
that appears earlier than pg_catalog is pretty far-fetched, but ISTM
that we really need to cover that scenario.

Hmm, good point. I guess we don't worry much about this with pg_dump
because we assume that we're restoring into an empty database (and if
not, the user gets to keep both pieces). You're applying a higher
standard here.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#43)
Re: Add CREATE support to event triggers

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Feb 5, 2014 at 2:26 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Then again, why is the behavior of schema-qualifying absolutely
everything even desirable?

Well, someone could create a collation in another schema with the same
name as a system collation and the command would become ambiguous.

Hmm, good point. I guess we don't worry much about this with pg_dump
because we assume that we're restoring into an empty database (and if
not, the user gets to keep both pieces). You're applying a higher
standard here.

Robert, that's just horsepucky. pg_dump is very careful about schemas.
It's also careful to not schema-qualify names unnecessarily, which is an
intentional tradeoff to improve readability of the dump --- at the cost
that the dump might break if restored into a nonempty database with
conflicting objects. In the case of data passed to event triggers,
there's a different tradeoff to be made: people will probably value
consistency over readability, so always-qualify is probably the right
choice here. But in neither case are we being sloppy.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#44)
Re: Add CREATE support to event triggers

On Thu, Feb 6, 2014 at 12:08 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Feb 5, 2014 at 2:26 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Then again, why is the behavior of schema-qualifying absolutely
everything even desirable?

Well, someone could create a collation in another schema with the same
name as a system collation and the command would become ambiguous.

Hmm, good point. I guess we don't worry much about this with pg_dump
because we assume that we're restoring into an empty database (and if
not, the user gets to keep both pieces). You're applying a higher
standard here.

Robert, that's just horsepucky. pg_dump is very careful about schemas.
It's also careful to not schema-qualify names unnecessarily, which is an
intentional tradeoff to improve readability of the dump --- at the cost
that the dump might break if restored into a nonempty database with
conflicting objects. In the case of data passed to event triggers,
there's a different tradeoff to be made: people will probably value
consistency over readability, so always-qualify is probably the right
choice here. But in neither case are we being sloppy.

I didn't mean to imply otherwise. I know the pg_dump tries to avoid
excess schema-qualification for readability among other reasons; what
I meant was that Alvaro is applying a higher standard specifically in
regards to replayability.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tom Lane (#42)
1 attachment(s)
Re: Add CREATE support to event triggers

Tom Lane escribi�:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

One idea is to chop the typmod output string at the closing parens.

+1. The only reason timestamptypmodout works like that is that we're
trying to match the SQL standard's spelling of the type names, and
that committee apparently considers it an off day whenever they can't
invent some randomly-incompatible-with-everything syntax.

Okay, I did it that way.

I also fixed the sequence OWNED BY problem simply by adding support for
ALTER SEQUENCE. Of course, the intention is that all forms of CREATE
and ALTER are supported, but this one seems reasonable standalone
because CREATE TABLE uses it internally.

So in the attached patch I have fixed all the three problems I reported
for the previous version.

I also changed the EventTriggerStashCreatedObject() function in two
ways:

1. rename it to EventTriggerStashCommand(). This is because it will
take ALTER commands as arguments, so the old name seemed a misnomer.
(I guess I was initially thinking it'd only handle object creation,
but obviously that will not work.)

2. Take ObjectType instead of ObjectClass. I'm not really sure about
this change; the old usage of OCLASS at the utility.c level seemed
wrong (in particular, in the DefineStmt case it was having to
translate the stmt->kind into ObjClass manually). But on the other
hand, the new usage of OBJECT_FOO requires a function to translate
each enum value to the catalog that contains objects of that type
(which is repetitive; the OCLASS_FOO code in dependency.c already has
a table with that). And also I had to add OBJECT_USER_MAPPING, which
was missing. I think I might end up reverting this bit, unless
somebody sees a way to beautify the Objtype->catalog OID
transformation. (The bit about missing USER MAPPING stuff seems a
bit troubling; EventTriggerSupportsObjectType wasn't aware of that
object type.)

By way of illustration, here's the output a simple command using the
snitch() function previously posted. You can see that the CREATE TABLE
command is expanded as three commands: CREATE SEQUENCE, CREATE TABLE,
ALTER SEQUENCE.

alvherre=# create unlogged table t1 (a serial);
NOTICE: JSON blob: {
"definition": [
{
"clause": "cache",
"fmt": "CACHE %{value}s",
"value": "1"
},
{
"clause": "cycle",
"fmt": "%{no}s CYCLE",
"no": "NO"
},
{
"clause": "increment_by",
"fmt": "INCREMENT BY %{value}s",
"value": "1"
},
{
"clause": "minvalue",
"fmt": "MINVALUE %{value}s",
"value": "1"
},
{
"clause": "maxvalue",
"fmt": "MAXVALUE %{value}s",
"value": "9223372036854775807"
},
{
"clause": "start",
"fmt": "START WITH %{value}s",
"value": "1"
},
{
"clause": "restart",
"fmt": "RESTART %{value}s",
"value": "1"
}
],
"fmt": "CREATE %{persistence}s SEQUENCE %{identity}D %{definition: }s",
"identity": {
"objname": "t1_a_seq",
"schemaname": "public"
},
"persistence": ""
}
NOTICE: expanded: CREATE SEQUENCE public.t1_a_seq CACHE 1 NO CYCLE INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 RESTART 1
NOTICE: JSON blob: {
"fmt": "CREATE %{persistence}s TABLE %{identity}D %{if_not_exists}s (%{table_elements:, }s) %{inherits}s %{on_commit}s %{tablespace}s",
"identity": {
"objname": "t1",
"schemaname": "public"
},
"if_not_exists": "",
"inherits": {
"fmt": "INHERITS (%{parents:, }D)",
"parents": null,
"present": false
},
"on_commit": {
"fmt": "ON COMMIT %{on_commit_value}s",
"on_commit_value": null,
"present": false
},
"persistence": "UNLOGGED",
"table_elements": [
{
"collation": {
"fmt": "COLLATE %{name}D",
"present": false
},
"coltype": {
"is_array": false,
"schemaname": "pg_catalog",
"typename": "int4",
"typmod": ""
},
"default": {
"default": "nextval('t1_a_seq'::regclass)",
"fmt": "DEFAULT %{default}s"
},
"fmt": "%{name}I %{coltype}T %{default}s %{not_null}s %{collation}s",
"name": "a",
"not_null": "NOT NULL",
"type": "column"
}
],
"table_kind": "plain",
"tablespace": {
"fmt": "TABLESPACE %{tablespace}I",
"present": false,
"tablespace": null
}
}
NOTICE: expanded: CREATE UNLOGGED TABLE public.t1 (a pg_catalog.int4 DEFAULT nextval('t1_a_seq'::regclass) NOT NULL )
NOTICE: JSON blob: {
"definition": [
{
"clause": "owned",
"fmt": "OWNED BY %{owner}D",
"owner": {
"attrname": "a",
"objname": "t1",
"schemaname": "public"
}
}
],
"fmt": "ALTER SEQUENCE %{identity}D %{definition: }s",
"identity": {
"objname": "t1_a_seq",
"schemaname": "public"
}
}
NOTICE: expanded: ALTER SEQUENCE public.t1_a_seq OWNED BY public.t1.a
CREATE TABLE

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

event-trigger-create-5.patchtext/x-diff; charset=us-asciiDownload
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ea81223..a2ad07f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -708,6 +708,122 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 }
 
 /*
+ * Return the OID of the catalog corresponding to the given object type
+ */
+Oid
+get_objtype_catalog_oid(ObjectType objtype)
+{
+	Oid		catalog_id;
+
+	switch (objtype)
+	{
+		case OBJECT_INDEX:
+		case OBJECT_SEQUENCE:
+		case OBJECT_TABLE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_FOREIGN_TABLE:
+			catalog_id = RelationRelationId;
+			break;
+		case OBJECT_COLUMN:
+			catalog_id = AttributeRelationId;
+			break;
+		case OBJECT_RULE:
+			catalog_id = RewriteRelationId;
+			break;
+		case OBJECT_TRIGGER:
+			catalog_id = TriggerRelationId;
+			break;
+		case OBJECT_CONSTRAINT:
+			catalog_id = ConstraintRelationId;
+			break;
+		case OBJECT_DATABASE:
+			catalog_id = DatabaseRelationId;
+			break;
+		case OBJECT_EXTENSION:
+			catalog_id = ExtensionRelationId;
+			break;
+		case OBJECT_TABLESPACE:
+			catalog_id = TableSpaceRelationId;
+			break;
+		case OBJECT_ROLE:
+			catalog_id = AuthIdRelationId;
+			break;
+		case OBJECT_SCHEMA:
+			catalog_id = NamespaceRelationId;
+			break;
+		case OBJECT_LANGUAGE:
+			catalog_id = LanguageRelationId;
+			break;
+		case OBJECT_FDW:
+			catalog_id = ForeignDataWrapperRelationId;
+			break;
+		case OBJECT_FOREIGN_SERVER:
+			catalog_id = ForeignServerRelationId;
+			break;
+		case OBJECT_USER_MAPPING:
+			catalog_id = UserMappingRelationId;
+			break;
+		case OBJECT_EVENT_TRIGGER:
+			catalog_id = EventTriggerRelationId;
+			break;
+		case OBJECT_TYPE:
+		case OBJECT_DOMAIN:
+			catalog_id = TypeRelationId;
+			break;
+		case OBJECT_ATTRIBUTE:
+			catalog_id = TypeRelationId;	/* XXX? */
+			break;
+		case OBJECT_AGGREGATE:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_FUNCTION:
+			catalog_id = ProcedureRelationId;
+			break;
+		case OBJECT_OPERATOR:
+			catalog_id = OperatorRelationId;
+			break;
+		case OBJECT_COLLATION:
+			catalog_id = CollationRelationId;
+			break;
+		case OBJECT_CONVERSION:
+			catalog_id = ConversionRelationId;
+			break;
+		case OBJECT_OPCLASS:
+			catalog_id = OperatorClassRelationId;
+			break;
+		case OBJECT_OPFAMILY:
+			catalog_id = OperatorFamilyRelationId;
+			break;
+		case OBJECT_LARGEOBJECT:
+			catalog_id = LargeObjectRelationId;
+			break;
+		case OBJECT_CAST:
+			catalog_id = CastRelationId;
+			break;
+		case OBJECT_TSPARSER:
+			catalog_id = TSParserRelationId;
+			break;
+		case OBJECT_TSDICTIONARY:
+			catalog_id = TSDictionaryRelationId;
+			break;
+		case OBJECT_TSTEMPLATE:
+			catalog_id = TSTemplateRelationId;
+			break;
+		case OBJECT_TSCONFIGURATION:
+			catalog_id = TSConfigRelationId;
+			break;
+		default:
+				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+				/* placate compiler, in case it thinks elog might return */
+				catalog_id = InvalidOid;
+	}
+
+	/* Return the object address and the relation. */
+	return catalog_id;
+}
+
+/*
  * Find an ObjectAddress for a type of object that is identified by an
  * unqualified name.
  */
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 73e6e20..bdc15f1 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -55,6 +55,9 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+/* the OID of the created table, for ExecCreateTableAs consumption */
+static Oid	CreateAsRelid = InvalidOid;
+
 static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
 static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
@@ -64,7 +67,7 @@ static void intorel_destroy(DestReceiver *self);
 /*
  * ExecCreateTableAs -- execute a CREATE TABLE AS command
  */
-void
+Oid
 ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag)
 {
@@ -75,6 +78,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	Oid			save_userid = InvalidOid;
 	int			save_sec_context = 0;
 	int			save_nestlevel = 0;
+	Oid			relOid;
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
@@ -98,7 +102,9 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		Assert(!is_matview);	/* excluded by syntax */
 		ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
 
-		return;
+		relOid = CreateAsRelid;
+		CreateAsRelid = InvalidOid;
+		return relOid;
 	}
 	Assert(query->commandType == CMD_SELECT);
 
@@ -190,6 +196,11 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
 	}
+
+	relOid = CreateAsRelid;
+	CreateAsRelid = InvalidOid;
+
+	return relOid;
 }
 
 /*
@@ -421,6 +432,9 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	myState->rel = intoRelationDesc;
 	myState->output_cid = GetCurrentCommandId(true);
 
+	/* and remember the new relation's OID for ExecCreateTableAs */
+	CreateAsRelid = RelationGetRelid(myState->rel);
+
 	/*
 	 * We can skip WAL-logging the insertions, unless PITR or streaming
 	 * replication is in use. We can skip the FSM in any case.
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 59f0842..77952d4 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -31,10 +31,12 @@
 #include "pgstat.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
+#include "tcop/deparse_utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/evtcache.h"
 #include "utils/fmgroids.h"
+#include "utils/json.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -48,6 +50,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	List	   *stash;
 	struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
 
@@ -491,7 +494,7 @@ AlterEventTriggerOwner(const char *name, Oid newOwnerId)
 }
 
 /*
- * Change extension owner, by OID
+ * Change event trigger owner, by OID
  */
 void
 AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId)
@@ -946,6 +949,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TYPE:
+		case OBJECT_USER_MAPPING:
 		case OBJECT_VIEW:
 			return true;
 	}
@@ -1022,13 +1026,6 @@ EventTriggerBeginCompleteQuery(void)
 	EventTriggerQueryState *state;
 	MemoryContext cxt;
 
-	/*
-	 * Currently, sql_drop events are the only reason to have event trigger
-	 * state at all; so if there are none, don't install one.
-	 */
-	if (!trackDroppedObjectsNeeded())
-		return false;
-
 	cxt = AllocSetContextCreate(TopMemoryContext,
 								"event trigger state",
 								ALLOCSET_DEFAULT_MINSIZE,
@@ -1036,8 +1033,10 @@ EventTriggerBeginCompleteQuery(void)
 								ALLOCSET_DEFAULT_MAXSIZE);
 	state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));
 	state->cxt = cxt;
-	slist_init(&(state->SQLDropList));
+	if (trackDroppedObjectsNeeded())
+		slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->stash = NIL;
 
 	state->previous = currentEventTriggerState;
 	currentEventTriggerState = state;
@@ -1217,7 +1216,7 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 		 errmsg("%s can only be called in a sql_drop event trigger function",
-				"pg_event_trigger_dropped_objects()")));
+				PG_FUNCNAME_MACRO)));
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -1294,3 +1293,776 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 
 	return (Datum) 0;
 }
+
+/*
+ * Support for tracking of objects created during command run.
+ *
+ * When a command is run that creates some SQL objects, we collect the
+ * classid/objid of the objects being created, as well as the parsetree of the
+ * creation command; later, when event triggers are run for that command, they
+ * can use pg_event_trigger_get_creation_commands which computes and returns a
+ * usable representation of the creation commands for the objects.
+ */
+typedef struct stashedObject
+{
+	Oid			objectId;
+	ObjectType	objtype;
+	Node	   *parsetree;
+} stashedObject;
+
+void
+EventTriggerStashCommand(Oid objectId, ObjectType objtype, Node *parsetree)
+{
+	MemoryContext oldcxt;
+	stashedObject *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(stashedObject));
+
+	stashed->objectId = objectId;
+	stashed->objtype = objtype;
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+Datum
+pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ListCell   *lc;
+
+	/*
+	 * Protect this function from being called out of context
+	 */
+	if (!currentEventTriggerState)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s can only be called in an event trigger function",
+						PG_FUNCNAME_MACRO)));
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	MemoryContextSwitchTo(oldcontext);
+
+	foreach(lc, currentEventTriggerState->stash)
+	{
+		stashedObject *obj = lfirst(lc);
+		char	   *command;
+
+		command = deparse_utility_command(obj->objectId, obj->parsetree);
+
+		/*
+		 * Some parse trees return NULL when deparse is attempted; we don't
+		 * emit anything for them.
+		 */
+		if (command != NULL)
+		{
+			Datum		values[2];
+			bool		nulls[2];
+			ObjectAddress addr;
+			char	   *identity;
+			int			i = 0;
+
+			addr.classId = get_objtype_catalog_oid(obj->objtype);
+			addr.objectId = obj->objectId;
+			addr.objectSubId = 0;
+			identity = getObjectIdentity(&addr);
+
+			MemSet(nulls, 0, sizeof(nulls));
+
+			/* identity */
+			values[i++] = CStringGetTextDatum(identity);
+			/* command */
+			values[i++] = CStringGetTextDatum(command);
+
+			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+			pfree(identity);
+		}
+	}
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	PG_RETURN_VOID();
+}
+
+/* ************************* JSON STUFF FROM HERE ************************* *
+ *	Code below is used to decode blobs returned by deparse_utility_command	*
+ *																			*/
+
+/*
+ * Note we only support types that are valid in command representation from
+ * deparse_utility_command.
+ */
+typedef enum
+{
+	JsonIsArray,
+	JsonIsObject,
+	JsonIsString
+} JsonType;
+
+typedef enum
+{
+	SpecTypename,
+	SpecOperatorname,
+	SpecDottedName,
+	SpecString,
+	SpecIdentifier
+} convSpecifier;
+
+/*
+ * Extract the named json field, which must be of type string, from the given
+ * JSON datum, which must be of type object.  If the field doesn't exist,
+ * NULL is returned.  Otherwise the string value is returned.
+ */
+static char *
+expand_get_strval(Datum json, char *field_name)
+{
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	char	   *value_str;
+
+	InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+	fcinfo.arg[0] = json;
+	fcinfo.argnull[0] = false;
+	fcinfo.arg[1] = CStringGetTextDatum(field_name);
+	fcinfo.argnull[1] = false;
+
+	result = (*json_object_field_text) (&fcinfo);
+
+	if (fcinfo.isnull)
+		return NULL;
+
+	value_str = TextDatumGetCString(result);
+
+	pfree(DatumGetPointer(result));
+
+	return value_str;
+}
+
+/*
+ * Extract the named json field, which must be of type boolean, from the given
+ * JSON datum, which must be of type object.  If the field doesn't exist,
+ * isnull is set to TRUE and the return value should not be consulted.
+ * Otherwise the boolean value is returned.
+ */
+static bool
+expand_get_boolval(Datum json, char *field_name, bool *isnull)
+{
+	FunctionCallInfoData fcinfo;
+	Datum		result;
+	char	   *value_str;
+
+	InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+	fcinfo.arg[0] = json;
+	fcinfo.argnull[0] = false;
+	fcinfo.arg[1] = CStringGetTextDatum(field_name);
+	fcinfo.argnull[1] = false;
+
+	result = (*json_object_field_text) (&fcinfo);
+
+	if (fcinfo.isnull)
+	{
+		*isnull = true;
+		return false;
+	}
+
+	value_str = TextDatumGetCString(result);
+
+	if (strcmp(value_str, "true") == 0)
+		return true;
+
+	Assert(strcmp(value_str, "false") == 0);
+	return false;
+}
+
+/*
+ * Given a JSON value, return its type.
+ *
+ * We return both a JsonType (for easy control flow), and a string name (for
+ * error reporting).
+ */
+static JsonType
+jsonval_get_type(Datum jsonval, char **typename)
+{
+	JsonType	json_elt_type;
+	Datum		paramtype_datum;
+	char	   *paramtype;
+
+	paramtype_datum = DirectFunctionCall1(json_typeof, jsonval);
+	paramtype = TextDatumGetCString(paramtype_datum);
+
+	if (strcmp(paramtype, "array") == 0)
+		json_elt_type = JsonIsArray;
+	else if (strcmp(paramtype, "object") == 0)
+		json_elt_type = JsonIsObject;
+	else if (strcmp(paramtype, "string") == 0)
+		json_elt_type = JsonIsString;
+	else
+		/* XXX improve this; need to specify array index or param name */
+		elog(ERROR, "unexpected JSON element type %s",
+			 paramtype);
+
+	if (typename)
+		*typename = pstrdup(paramtype);
+
+	return json_elt_type;
+}
+
+/*
+ * dequote_jsonval
+ *		Take a string value extracted from a JSON object, and return a copy of it
+ *		with the quoting removed.
+ *
+ * Another alternative to this would be to run the extraction routine again,
+ * using the "_text" variant which returns the value without quotes; but this
+ * is expensive, and moreover it complicates the logic a lot because not all
+ * values are extracted in the same way (some are extracted using
+ * json_object_field, others using json_array_element).  Dequoting the object
+ * already at hand is a lot easier.
+ */
+static char *
+dequote_jsonval(char *jsonval)
+{
+	char	   *result;
+	int			inputlen = strlen(jsonval);
+	int			i;
+	int			j = 0;
+
+	result = palloc(strlen(jsonval) + 1);
+
+	/* skip the start and end quotes right away */
+	for (i = 1; i < inputlen - 1; i++)
+	{
+		/*
+		 * XXX this skips the \ in a \" sequence but leaves other escaped
+		 * sequences in place.	Are there other cases we need to handle
+		 * specially?
+		 */
+		if (jsonval[i] == '\\' &&
+			jsonval[i + 1] == '"')
+		{
+			i++;
+			continue;
+		}
+
+		result[j++] = jsonval[i];
+	}
+	result[j] = '\0';
+
+	return result;
+}
+
+/*
+ * Expand a json value as an identifier.  The value must be of type string.
+ */
+static void
+expand_jsonval_identifier(StringInfo buf, Datum jsonval)
+{
+	char	   *unquoted;
+
+	unquoted = dequote_jsonval(TextDatumGetCString(jsonval));
+	appendStringInfo(buf, "%s", quote_identifier(unquoted));
+
+	pfree(unquoted);
+}
+
+/*
+ * Expand a json value as a dotted-name.  The value must be of type object
+ * and must contain elements "schemaname" (optional), "objname" (mandatory),
+ * "attrname" (optional).
+ *
+ * XXX do we need a "catalogname" as well?
+ */
+static void
+expand_jsonval_dottedname(StringInfo buf, Datum jsonval)
+{
+	char	   *schema;
+	char	   *objname;
+	char	   *attrname;
+	const char *qschema;
+	const char *qname;
+
+	schema = expand_get_strval(jsonval, "schemaname");
+	objname = expand_get_strval(jsonval, "objname");
+	if (objname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL object name in %%D element")));
+	qname = quote_identifier(objname);
+	if (schema == NULL)
+	{
+		appendStringInfo(buf, "%s", qname);
+	}
+	else
+	{
+		qschema = quote_identifier(schema);
+		appendStringInfo(buf, "%s.%s",
+						 qschema, qname);
+		if (qschema != schema)
+			pfree((char *) qschema);
+		pfree(schema);
+	}
+
+	attrname = expand_get_strval(jsonval, "attrname");
+	if (attrname)
+	{
+		const char *qattr;
+
+		qattr = quote_identifier(attrname);
+		appendStringInfo(buf, ".%s", qattr);
+		if (qattr != attrname)
+			pfree((char *) qattr);
+		pfree(attrname);
+	}
+
+	if (qname != objname)
+		pfree((char *) qname);
+	pfree(objname);
+}
+
+/*
+ * expand a json value as a type name.
+ */
+static void
+expand_jsonval_typename(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		array_isnull;
+	bool		is_array;
+
+	typename = expand_get_strval(jsonval, "typename");
+	if (typename == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL type name in %%T element")));
+	typmodstr = expand_get_strval(jsonval, "typmod");	/* OK if null */
+	is_array = expand_get_boolval(jsonval, "is_array", &array_isnull);
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/* schema might be NULL or empty here, beware */
+	if (schema == NULL || schema[0] == '\0')
+		appendStringInfo(buf, "%s%s%s",
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+	else
+		appendStringInfo(buf, "%s.%s%s%s",
+						 quote_identifier(schema),
+						 quote_identifier(typename),
+						 typmodstr ? typmodstr : "",
+						 is_array ? "[]" : "");
+}
+
+/*
+ * Expand a json value as an operator name
+ */
+static void
+expand_jsonval_operator(StringInfo buf, Datum jsonval)
+{
+	char	   *schema = NULL;
+	char	   *operator;
+
+	operator = expand_get_strval(jsonval, "objname");
+	if (operator == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL operator name in %%O element")));
+	schema = expand_get_strval(jsonval, "schemaname");
+
+	/* schema might be NULL or empty */
+	if (schema == NULL || schema[0] == '\0')
+		appendStringInfo(buf, "%s", operator);
+	else
+		appendStringInfo(buf, "%s.%s",
+						 quote_identifier(schema),
+						 operator);
+}
+
+/*
+ * Expand a json value as a string.  The value must be of type string or of
+ * type object, in which case it must contain a "fmt" element which will be
+ * recursively expanded; also, if the object contains an element "present"
+ * and it is set to false, the expansion is the empty string.
+ */
+static void
+expand_jsonval_string(StringInfo buf, Datum jsonval, JsonType json_elt_type)
+{
+	if (json_elt_type == JsonIsString)
+	{
+		char	   *str;
+		char	   *unquoted;
+
+		str = TextDatumGetCString(jsonval);
+		unquoted = dequote_jsonval(str);
+		appendStringInfo(buf, "%s", unquoted);
+		pfree(str);
+		pfree(unquoted);
+	}
+	else if (json_elt_type == JsonIsObject)
+	{
+		bool		present;
+		bool		isnull;
+
+		present = expand_get_boolval(jsonval, "present", &isnull);
+
+		if (isnull || present)
+		{
+			Datum		inner;
+			char	   *str;
+
+			inner = DirectFunctionCall1(pg_event_trigger_expand_command,
+										jsonval);
+			str = TextDatumGetCString(inner);
+
+			appendStringInfoString(buf, str);
+			pfree(DatumGetPointer(inner));
+			pfree(str);
+		}
+	}
+}
+
+/*
+ * Expand one json element according to rules.
+ */
+static void
+expand_one_element(StringInfo buf, char *param,
+				   Datum jsonval, char *valtype, JsonType json_elt_type,
+				   convSpecifier specifier)
+{
+	/*
+	 * Validate the parameter type.  If dotted-name was specified, then a JSON
+	 * object element is expected; if an identifier was specified, then a JSON
+	 * string is expected.	If a string was specified, then either a JSON
+	 * object or a string is expected.
+	 */
+	if (specifier == SpecDottedName && json_elt_type != JsonIsObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%D element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecTypename && json_elt_type != JsonIsObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%T element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecOperatorname && json_elt_type != JsonIsObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON object for %%O element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecIdentifier && json_elt_type != JsonIsString)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string for %%I element \"%s\", got %s",
+						param, valtype)));
+	if (specifier == SpecString &&
+		json_elt_type != JsonIsString && json_elt_type != JsonIsObject)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("expected JSON string or object for %%s element \"%s\", got %s",
+						param, valtype)));
+
+	switch (specifier)
+	{
+		case SpecIdentifier:
+			expand_jsonval_identifier(buf, jsonval);
+			break;
+
+		case SpecDottedName:
+			expand_jsonval_dottedname(buf, jsonval);
+			break;
+
+		case SpecString:
+			expand_jsonval_string(buf, jsonval, json_elt_type);
+			break;
+
+		case SpecTypename:
+			expand_jsonval_typename(buf, jsonval);
+			break;
+
+		case SpecOperatorname:
+			expand_jsonval_operator(buf, jsonval);
+			break;
+	}
+}
+
+/*
+ * Expand one JSON array element according to rules.
+ */
+static void
+expand_one_array_element(StringInfo buf, Datum array, int idx, char *param,
+						 convSpecifier specifier)
+{
+	Datum		elemval;
+	JsonType	json_elt_type;
+	char	   *elemtype;
+
+	elemval = DirectFunctionCall2(json_array_element,
+								  PointerGetDatum(array),
+								  Int32GetDatum(idx));
+	json_elt_type = jsonval_get_type(elemval, &elemtype);
+
+	expand_one_element(buf, param,
+					   elemval, elemtype, json_elt_type,
+					   specifier);
+}
+
+#define ADVANCE_PARSE_POINTER(ptr,end_ptr) \
+	do { \
+		if (++(ptr) >= (end_ptr)) \
+			ereport(ERROR, \
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+					 errmsg("unterminated format specifier"))); \
+	} while (0)
+
+/*------
+ * Returns a formatted string from a JSON object.
+ *
+ * The starting point is the element named "fmt" (which must be a string).
+ * This format string may contain zero or more %-escapes, which consist of an
+ * element name enclosed in { }, possibly followed by a conversion modifier,
+ * followed by a conversion specifier.	Possible conversion specifiers are:
+ *
+ * %		expand to a literal %.
+ * I		expand as a single, non-qualified identifier
+ * D		expand as a possibly-qualified identifier
+ * T		expand as a type name
+ * O		expand as an operator name
+ * s		expand as a simple string (no quoting)
+ *
+ * The element name may have an optional separator specification preceded
+ * by a colon.	Its presence indicates that the element is expected to be
+ * an array; the specified separator is used to join the array elements.
+ *
+ * XXX the current implementation works fine, but is likely to be slow: for
+ * each element found in the fmt string we parse the JSON object once.	It
+ * might be better to use jsonapi.h directly so that we build a hash or tree of
+ * elements and their values once before parsing the fmt string, and later scan
+ * fmt using the tree.
+ *------
+ */
+Datum
+pg_event_trigger_expand_command(PG_FUNCTION_ARGS)
+{
+	text	   *json = PG_GETARG_TEXT_P(0);
+	char	   *fmt_str;
+	int			fmt_len;
+	const char *cp;
+	const char *start_ptr;
+	const char *end_ptr;
+	StringInfoData str;
+
+	fmt_str = expand_get_strval(PointerGetDatum(json), "fmt");
+	if (fmt_str == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid NULL format string")));
+	fmt_len = strlen(fmt_str);
+
+	start_ptr = fmt_str;
+	end_ptr = start_ptr + fmt_len;
+	initStringInfo(&str);
+
+	for (cp = start_ptr; cp < end_ptr; cp++)
+	{
+		convSpecifier specifier;
+		bool		is_array;
+		char	   *param = NULL;
+		char	   *arraysep = NULL;
+		Datum		paramval;
+		char	   *paramtype;
+		JsonType	json_elt_type;
+
+		if (*cp != '%')
+		{
+			appendStringInfoCharMacro(&str, *cp);
+			continue;
+		}
+
+		is_array = false;
+
+		ADVANCE_PARSE_POINTER(cp, end_ptr);
+
+		/* Easy case: %% outputs a single % */
+		if (*cp == '%')
+		{
+			appendStringInfoCharMacro(&str, *cp);
+			continue;
+		}
+
+		/*
+		 * Scan the mandatory element name.  Allow for an array separator
+		 * (which may be the empty string) to be specified after colon.
+		 */
+		if (*cp == '{')
+		{
+			StringInfoData parbuf;
+			StringInfoData arraysepbuf;
+			StringInfo	appendTo;
+
+			initStringInfo(&parbuf);
+			appendTo = &parbuf;
+
+			ADVANCE_PARSE_POINTER(cp, end_ptr);
+			for (; cp < end_ptr;)
+			{
+				if (*cp == ':')
+				{
+					/*
+					 * found array separator delimiter; element name is now
+					 * complete, start filling the separator.
+					 */
+					initStringInfo(&arraysepbuf);
+					appendTo = &arraysepbuf;
+					is_array = true;
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					continue;
+				}
+
+				if (*cp == '}')
+				{
+					ADVANCE_PARSE_POINTER(cp, end_ptr);
+					break;
+				}
+				appendStringInfoCharMacro(appendTo, *cp);
+				ADVANCE_PARSE_POINTER(cp, end_ptr);
+			}
+			param = parbuf.data;
+			if (is_array)
+				arraysep = arraysepbuf.data;
+		}
+		if (param == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("missing conversion name in conversion specifier")));
+
+		/*------
+		 * The following conversion specifiers are currently recognized:
+		 * 'I' -- expand as an identifier, adding quotes if necessary
+		 * 'D' -- expand as a dotted-name, for qualified names; each element is
+		 *		  quoted if necessary
+		 * 'O' -- expand as an operator name. Same as 'D', but the objname
+		 *		  element is not quoted.
+		 * 's' -- expand as a simple string; no quoting.
+		 * 'T' -- expand as a typename, with ad-hoc rules
+		 */
+		switch (*cp)
+		{
+			case 'I':
+				specifier = SpecIdentifier;
+				break;
+			case 'D':
+				specifier = SpecDottedName;
+				break;
+			case 's':
+				specifier = SpecString;
+				break;
+			case 'T':
+				specifier = SpecTypename;
+				break;
+			case 'O':
+				specifier = SpecOperatorname;
+				break;
+			default:
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("invalid conversion specifier \"%c\"", *cp)));
+		}
+
+		/*
+		 * Obtain the element to be expanded.  Note we cannot use
+		 * DirectFunctionCall here, because the element might not exist.
+		 */
+		{
+			FunctionCallInfoData fcinfo;
+
+			InitFunctionCallInfoData(fcinfo, NULL, 2, InvalidOid, NULL, NULL);
+
+			fcinfo.arg[0] = PointerGetDatum(json);
+			fcinfo.argnull[0] = false;
+			fcinfo.arg[1] = CStringGetTextDatum(param);
+			fcinfo.argnull[1] = false;
+
+			paramval = (*json_object_field) (&fcinfo);
+
+			if (fcinfo.isnull)
+			{
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("non-existant element \"%s\" in JSON formatting object",
+								param)));
+			}
+		}
+
+		/* figure out its type */
+		json_elt_type = jsonval_get_type(paramval, &paramtype);
+
+		/* Validate that we got an array if the format string specified one. */
+		if (is_array && json_elt_type != JsonIsArray)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("expected JSON array for element \"%s\", got %s",
+							param, paramtype)));
+
+		/* And finally print out the data */
+		if (is_array)
+		{
+			int			count;
+			bool		putsep = false;
+			int			i;
+
+			count = DatumGetInt32(DirectFunctionCall1(json_array_length,
+													  paramval));
+			for (i = 0; i < count; i++)
+			{
+				if (putsep)
+					appendStringInfoString(&str, arraysep);
+				putsep = true;
+
+				expand_one_array_element(&str, paramval, i, param, specifier);
+			}
+		}
+		else
+		{
+			expand_one_element(&str, param, paramval, paramtype, json_elt_type,
+							   specifier);
+		}
+	}
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(str.data));
+}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index a331f3e..0f0ecbe 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -132,7 +132,7 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
  * The matview's "populated" state is changed based on whether the contents
  * reflect the result set of the materialized view's query.
  */
-void
+Oid
 ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag)
 {
@@ -274,6 +274,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	}
 	else
 		refresh_by_heap_swap(matviewOid, OIDNewHeap);
+
+	return matviewOid;
 }
 
 /*
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 2599e28..0862aee 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -24,6 +24,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/schemacmds.h"
 #include "miscadmin.h"
 #include "parser/parse_utilcmd.h"
@@ -130,6 +131,14 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
 	PushOverrideSearchPath(overridePath);
 
 	/*
+	 * Report the new schema to possibly interested event triggers.  Note we
+	 * must do this here and not in ProcessUtilitySlow because otherwise the
+	 * objects created below are reported before the schema, which would be
+	 * wrong.
+	 */
+	EventTriggerStashCommand(namespaceId, OBJECT_SCHEMA, (Node *) stmt);
+
+	/*
 	 * Examine the list of commands embedded in the CREATE SCHEMA command, and
 	 * reorganize them into a sequentially executable order with no forward
 	 * references.	Note that the result is still a list of raw parsetrees ---
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ed696be..564834e 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1552,7 +1552,42 @@ pg_sequence_parameters(PG_FUNCTION_ARGS)
 	return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
 }
 
+/*
+ * Return sequence parameters, detailed
+ */
+Form_pg_sequence
+get_sequence_values(Oid sequenceId)
+{
+	Buffer		buf;
+	SeqTable	elm;
+	Relation	seqrel;
+	HeapTupleData seqtuple;
+	Form_pg_sequence seq;
+	Form_pg_sequence retSeq;
+
+	retSeq = palloc(sizeof(FormData_pg_sequence));
+
+	/* open and AccessShareLock sequence */
+	init_sequence(sequenceId, &elm, &seqrel);
+
+	if (pg_class_aclcheck(sequenceId, GetUserId(),
+						  ACL_SELECT | ACL_UPDATE | ACL_USAGE) != ACLCHECK_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for sequence %s",
+						RelationGetRelationName(seqrel))));
+
+	seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple);
+
+	memcpy(retSeq, seq, sizeof(FormData_pg_sequence));
+
+	UnlockReleaseBuffer(buf);
+	relation_close(seqrel, NoLock);
+
+	return retSeq;
+}
 
+/* definition elements */
 void
 seq_redo(XLogRecPtr lsn, XLogRecord *record)
 {
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 08b037e..a6d38fa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -43,6 +43,7 @@
 #include "catalog/pg_type_fn.h"
 #include "catalog/storage.h"
 #include "catalog/toasting.h"
+#include "commands/alter_table.h"
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
@@ -112,54 +113,6 @@ typedef struct OnCommitItem
 static List *on_commits = NIL;
 
 
-/*
- * State information for ALTER TABLE
- *
- * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
- * structs, one for each table modified by the operation (the named table
- * plus any child tables that are affected).  We save lists of subcommands
- * to apply to this table (possibly modified by parse transformation steps);
- * these lists will be executed in Phase 2.  If a Phase 3 step is needed,
- * necessary information is stored in the constraints and newvals lists.
- *
- * Phase 2 is divided into multiple passes; subcommands are executed in
- * a pass determined by subcommand type.
- */
-
-#define AT_PASS_UNSET			-1		/* UNSET will cause ERROR */
-#define AT_PASS_DROP			0		/* DROP (all flavors) */
-#define AT_PASS_ALTER_TYPE		1		/* ALTER COLUMN TYPE */
-#define AT_PASS_OLD_INDEX		2		/* re-add existing indexes */
-#define AT_PASS_OLD_CONSTR		3		/* re-add existing constraints */
-#define AT_PASS_COL_ATTRS		4		/* set other column attributes */
-/* We could support a RENAME COLUMN pass here, but not currently used */
-#define AT_PASS_ADD_COL			5		/* ADD COLUMN */
-#define AT_PASS_ADD_INDEX		6		/* ADD indexes */
-#define AT_PASS_ADD_CONSTR		7		/* ADD constraints, defaults */
-#define AT_PASS_MISC			8		/* other stuff */
-#define AT_NUM_PASSES			9
-
-typedef struct AlteredTableInfo
-{
-	/* Information saved before any work commences: */
-	Oid			relid;			/* Relation to work on */
-	char		relkind;		/* Its relkind */
-	TupleDesc	oldDesc;		/* Pre-modification tuple descriptor */
-	/* Information saved by Phase 1 for Phase 2: */
-	List	   *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
-	/* Information saved by Phases 1/2 for Phase 3: */
-	List	   *constraints;	/* List of NewConstraint */
-	List	   *newvals;		/* List of NewColumnValue */
-	bool		new_notnull;	/* T if we added new NOT NULL constraints */
-	bool		rewrite;		/* T if a rewrite is forced */
-	Oid			newTableSpace;	/* new tablespace; 0 means no change */
-	/* Objects to rebuild after completing ALTER TYPE operations */
-	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
-	List	   *changedConstraintDefs;	/* string definitions of same */
-	List	   *changedIndexOids;		/* OIDs of indexes to rebuild */
-	List	   *changedIndexDefs;		/* string definitions of same */
-} AlteredTableInfo;
-
 /* Struct describing one new constraint to check in Phase 3 scan */
 /* Note: new NOT NULL constraints are handled elsewhere */
 typedef struct NewConstraint
@@ -7762,7 +7715,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				if (!list_member_oid(tab->changedConstraintOids,
 									 foundObject.objectId))
 				{
-					char	   *defstring = pg_get_constraintdef_string(foundObject.objectId);
+					char	   *defstring = pg_get_constraintdef_string(foundObject.objectId,
+																		true);
 
 					/*
 					 * Put NORMAL dependencies at the front of the list and
diff --git a/src/backend/tcop/Makefile b/src/backend/tcop/Makefile
index 674302f..34acdce 100644
--- a/src/backend/tcop/Makefile
+++ b/src/backend/tcop/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/tcop
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS= dest.o fastpath.o postgres.o pquery.o utility.o
+OBJS= dest.o deparse_utility.o fastpath.o postgres.o pquery.o utility.o
 
 ifneq (,$(filter $(PORTNAME),cygwin win32))
 override CPPFLAGS += -DWIN32_STACK_RLIMIT=$(WIN32_STACK_RLIMIT)
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
new file mode 100644
index 0000000..912d0a6
--- /dev/null
+++ b/src/backend/tcop/deparse_utility.c
@@ -0,0 +1,1620 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.c
+ *	  Functions to convert utility commands to machine-parseable
+ *	  representation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ *
+ * This is intended to provide JSON blobs representing DDL commands, which can
+ * later be re-processed into plain strings by well-defined sprintf-like
+ * expansion.  These JSON objects are intended to allow for machine-editing of
+ * the commands, by replacing certain nodes within the objects.
+ *
+ * Much of the information in the output blob actually comes from system
+ * catalogs, not from the command parse node, as it is impossible to reliably
+ * construct a fully-specified command (i.e. one not dependent on search_path
+ * etc) looking only at the parse node.
+ *
+ * IDENTIFICATION
+ *	  src/backend/tcop/deparse_utility.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/heap.h"
+#include "catalog/index.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_aggregate.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "lib/ilist.h"
+#include "lib/stringinfo.h"
+#include "nodes/makefuncs.h"
+#include "nodes/parsenodes.h"
+#include "parser/analyze.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "tcop/deparse_utility.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/json.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+typedef enum
+{
+	ObjTypeNull,
+	ObjTypeBool,
+	ObjTypeString,
+	ObjTypeArray,
+	ObjTypeObject
+} ObjType;
+
+typedef struct ObjElem
+{
+	char	   *name;
+	ObjType		objtype;
+	bool		bool_value;
+	char	   *str_value;
+	struct ObjTree *obj_value;
+	List	   *array_value;
+	slist_node	node;
+} ObjElem;
+
+typedef struct ObjTree
+{
+	MemoryContext cxt;
+	slist_head	params;
+	int			numParams;
+} ObjTree;
+
+
+static void append_null_object(ObjTree *tree, char *name);
+static void append_bool_object(ObjTree *tree, char *name,
+				   bool value);
+static void append_string_object(ObjTree *tree, char *name,
+					 char *value);
+static void append_object_object(ObjTree *tree, char *name,
+					 ObjTree *value);
+static void append_array_object(ObjTree *tree, char *name,
+					List *array);
+
+/*
+ * Allocate a new object tree to store parameter values.  If parent is NULL, a
+ * new memory context is created for all allocations involving the parameters;
+ * if it's not null, then the memory context from the given object is used.
+ */
+static ObjTree *
+new_objtree(ObjTree *parent)
+{
+	MemoryContext cxt;
+	ObjTree    *params;
+
+	if (parent == NULL)
+	{
+		cxt = AllocSetContextCreate(CurrentMemoryContext,
+									"deparse parameters",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+	}
+	else
+		cxt = parent->cxt;
+
+	params = MemoryContextAlloc(cxt, sizeof(ObjTree));
+	params->cxt = cxt;
+	params->numParams = 0;
+	slist_init(&params->params);
+
+	return params;
+}
+
+/*
+ * Allocate a new object to store parameters.  As above, if parent is NULL,
+ * a new memory context is created; otherwise the parent is used to extract
+ * the memory context to use.
+ *
+ * The "fmt" argument is used to append as a "fmt" element in the output blob.
+ * numobjs indicates the number of extra elements to append; for each one,
+ * a name, type and value must be supplied.  Note we don't have the luxury of
+ * sprintf-like compiler warnings for malformed argument lists.
+ */
+static ObjTree *
+new_objtree_VA(ObjTree *parent, char *fmt, int numobjs,...)
+{
+	ObjTree    *tree;
+	va_list		args;
+	int			i;
+
+	/* Set up the toplevel object and its "fmt" */
+	tree = new_objtree(parent);
+	append_string_object(tree, "fmt", fmt);
+
+	/* And process the given varargs */
+	va_start(args, numobjs);
+	for (i = 0; i < numobjs; i++)
+	{
+		ObjTree    *value;
+		ObjType		type;
+		char	   *name;
+		char	   *strval;
+		bool		boolval;
+		List	   *list;
+
+		name = va_arg(args, char *);
+		type = va_arg(args, ObjType);
+
+		/* Null params don't have a value (obviously) */
+		if (type == ObjTypeNull)
+		{
+			append_null_object(tree, name);
+			continue;
+		}
+
+		/*
+		 * For all other param types there must be a value in the varargs.
+		 * Fetch it and add the fully formed subobject into the main object.
+		 */
+		switch (type)
+		{
+			case ObjTypeBool:
+				boolval = va_arg(args, int);
+				append_bool_object(tree, name, boolval);
+				break;
+			case ObjTypeString:
+				strval = va_arg(args, char *);
+				append_string_object(tree, name, strval);
+				break;
+			case ObjTypeObject:
+				value = va_arg(args, ObjTree *);
+				append_object_object(tree, name, value);
+				break;
+			case ObjTypeArray:
+				list = va_arg(args, List *);
+				append_array_object(tree, name, list);
+				break;
+			default:
+				elog(ERROR, "invalid parameter type %d", type);
+		}
+	}
+
+	va_end(args);
+	return tree;
+}
+
+/*
+ * Add a new parameter with a NULL value
+ */
+static void
+append_null_object(ObjTree *tree, char *name)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeNull;
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new boolean parameter
+ */
+static void
+append_bool_object(ObjTree *tree, char *name, bool value)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeBool;
+	param->bool_value = value;
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new string parameter.
+ *
+ * Note: we don't pstrdup the source string.  Caller must ensure the
+ * source string lives long enough.
+ */
+static void
+append_string_object(ObjTree *tree, char *name, char *value)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeString;
+	param->str_value = value;	/* XXX not duped */
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new object parameter
+ */
+static void
+append_object_object(ObjTree *tree, char *name, ObjTree *value)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeObject;
+	param->obj_value = value;	/* XXX not duped */
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Add a new array parameter
+ */
+static void
+append_array_object(ObjTree *tree, char *name, List *array)
+{
+	ObjElem    *param;
+
+	param = MemoryContextAllocZero(tree->cxt, sizeof(ObjElem));
+
+	param->name = MemoryContextStrdup(tree->cxt, name);
+	param->objtype = ObjTypeArray;
+	param->array_value = array; /* XXX not duped */
+
+	slist_push_head(&tree->params, &param->node);
+	tree->numParams++;
+}
+
+/*
+ * Create a JSON blob from our ad-hoc representation.
+ *
+ * Note this allocates memory in tree->cxt.  This is okay because we don't need
+ * a separate memory context, and the one provided by the tree object has the
+ * right lifetime already.
+ *
+ * XXX this implementation will fail if there are more JSON objects in the tree
+ * than the maximum number of columns in a heap tuple.  To fix we would first call
+ * construct_md_array and then json_object.
+ */
+static char *
+jsonize_objtree(ObjTree *tree)
+{
+	MemoryContext oldcxt;
+	TupleDesc	tupdesc;
+	Datum	   *values;
+	bool	   *nulls;
+	slist_iter	iter;
+	int			i;
+	HeapTuple	htup;
+	Datum		json;
+	char	   *jsonstr;
+
+	/*
+	 * Use the objtree's memory context, so that everything we allocate in
+	 * this routine (other than our return string) can be blown up easily
+	 * by our caller.
+	 */
+	oldcxt = MemoryContextSwitchTo(tree->cxt);
+	tupdesc = CreateTemplateTupleDesc(tree->numParams, false);
+	values = palloc(sizeof(Datum) * tree->numParams);
+	nulls = palloc(sizeof(bool) * tree->numParams);
+
+	i = 1;
+	slist_foreach(iter, &tree->params)
+	{
+		ObjElem    *object = slist_container(ObjElem, node, iter.cur);
+		Oid			typeid;
+
+		switch (object->objtype)
+		{
+			case ObjTypeNull:
+			case ObjTypeString:
+				typeid = TEXTOID;
+				break;
+			case ObjTypeBool:
+				typeid = BOOLOID;
+				break;
+			case ObjTypeArray:
+			case ObjTypeObject:
+				typeid = JSONOID;
+				break;
+			default:
+				elog(ERROR, "unable to determine type id");
+				typeid = InvalidOid;
+		}
+
+		TupleDescInitEntry(tupdesc, i, object->name, typeid, -1, 0);
+
+		nulls[i - 1] = false;
+		switch (object->objtype)
+		{
+			case ObjTypeNull:
+				nulls[i - 1] = true;
+				break;
+			case ObjTypeBool:
+				values[i - 1] = BoolGetDatum(object->bool_value);
+				break;
+			case ObjTypeString:
+				values[i - 1] = CStringGetTextDatum(object->str_value);
+				break;
+			case ObjTypeArray:
+				{
+					ArrayType  *arrayt;
+					Datum	   *arrvals;
+					Datum		jsonary;
+					ListCell   *cell;
+					int			length = list_length(object->array_value);
+					int			j;
+
+					/*
+					 * Arrays are stored as Lists up to this point, with each
+					 * element being a ObjElem; we need to construct an
+					 * ArrayType with them to turn the whole thing into a JSON
+					 * array.
+					 */
+					j = 0;
+					arrvals = palloc(sizeof(Datum) * length);
+					foreach(cell, object->array_value)
+					{
+						ObjTree    *json = lfirst(cell);
+
+						arrvals[j++] =
+							CStringGetTextDatum(jsonize_objtree(json));
+					}
+					arrayt = construct_array(arrvals, length,
+											 JSONOID, -1, false, 'i');
+
+					jsonary = DirectFunctionCall1(array_to_json,
+												  (PointerGetDatum(arrayt)));
+
+					values[i - 1] = jsonary;
+				}
+				break;
+			case ObjTypeObject:
+				values[i - 1] =
+					CStringGetTextDatum(jsonize_objtree(object->obj_value));
+				break;
+		}
+
+		i++;
+	}
+
+	BlessTupleDesc(tupdesc);
+	htup = heap_form_tuple(tupdesc, values, nulls);
+	json = DirectFunctionCall1(row_to_json, HeapTupleGetDatum(htup));
+
+	/* switch to caller's context so that our output is allocated there */
+	MemoryContextSwitchTo(oldcxt);
+
+	jsonstr = TextDatumGetCString(json);
+
+	return jsonstr;
+}
+
+/*
+ * Release all memory used by parameters and their expansion
+ */
+static void
+free_objtree(ObjTree *tree)
+{
+	MemoryContextDelete(tree->cxt);
+}
+
+/*
+ * A helper routine to setup %{}T elements.
+ */
+static ObjTree *
+new_objtree_for_type(ObjTree *parent, Oid typeId, int32 typmod)
+{
+	ObjTree    *typeParam;
+	char	   *typnsp;
+	char	   *typename;
+	char	   *typmodstr;
+	bool		is_array;
+
+	format_type_detailed(typeId, typmod,
+						 &typnsp, &typename, &typmodstr, &is_array);
+
+	/*
+	 * XXX We need this kludge to support types whose typmods include extra
+	 * verbiage after the parenthised value.  Really, this only applies to
+	 * timestamp and timestamptz, whose the typmod takes the form "(N)
+	 * with[out] time zone", which causes a syntax error with schema-qualified
+	 * names extracted from pg_type (as opposed to specialized type names
+	 * defined by the SQL standard).
+	 */
+	if (typmodstr)
+	{
+		char	*clpar;
+
+		clpar = strchr(typmodstr, ')');
+		if (clpar)
+			*(clpar + 1) = '\0';
+	}
+
+	/* We don't use new_objtree_VA here because types don't have a "fmt" */
+	typeParam = new_objtree(parent);
+	append_string_object(typeParam, "schemaname", typnsp);
+	append_string_object(typeParam, "typename", typename);
+	append_string_object(typeParam, "typmod", typmodstr);
+	append_bool_object(typeParam, "is_array", is_array);
+
+	return typeParam;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements
+ *
+ * The difference between those two element types is whether the objname will
+ * be quoted as an identifier or not, which is not something that this routine
+ * concerns itself with; that will be up to the expand function.
+ */
+static ObjTree *
+new_objtree_for_qualname(ObjTree *parent, Oid nspid, char *name)
+{
+	ObjTree    *qualified;
+	char	   *namespace;
+
+	/*
+	 * We don't use new_objtree_VA here because these names don't have a "fmt"
+	 */
+	qualified = new_objtree(parent);
+	namespace = get_namespace_name(nspid);
+	append_string_object(qualified, "schemaname", namespace);
+	append_string_object(qualified, "objname", pstrdup(name));
+
+	return qualified;
+}
+
+/*
+ * A helper routine to setup %{}D and %{}O elements, with the object specified
+ * by classId/objId
+ */
+static ObjTree *
+new_objtree_for_qualname_id(ObjTree *parent, Oid classId, Oid objectId)
+{
+	ObjTree    *qualified;
+	Relation	catalog;
+	HeapTuple	catobj;
+	Datum		objnsp;
+	Datum		objname;
+	AttrNumber	Anum_name;
+	AttrNumber	Anum_namespace;
+	bool		isnull;
+
+	catalog = heap_open(classId, AccessShareLock);
+
+	catobj = get_catalog_object_by_oid(catalog, objectId);
+	if (!catobj)
+		elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
+			 objectId, RelationGetRelationName(catalog));
+	Anum_name = get_object_attnum_name(classId);
+	Anum_namespace = get_object_attnum_namespace(classId);
+
+	objnsp = heap_getattr(catobj, Anum_namespace, RelationGetDescr(catalog),
+						  &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL namespace");
+	objname = heap_getattr(catobj, Anum_name, RelationGetDescr(catalog),
+						   &isnull);
+	if (isnull)
+		elog(ERROR, "unexpected NULL name");
+
+	qualified = new_objtree_for_qualname(parent,
+										 DatumGetObjectId(objnsp),
+										 NameStr(*DatumGetName(objname)));
+
+	pfree(catobj);
+	heap_close(catalog, AccessShareLock);
+
+	return qualified;
+}
+
+/*
+ * Return the string representation of the given RELPERSISTENCE value
+ */
+static char *
+get_persistence_str(char persistence)
+{
+	switch (persistence)
+	{
+		case RELPERSISTENCE_TEMP:
+			return "TEMPORARY";
+		case RELPERSISTENCE_UNLOGGED:
+			return "UNLOGGED";
+		case RELPERSISTENCE_PERMANENT:
+			return "";
+		default:
+			return "???";
+	}
+}
+
+/*
+ * deparse_ViewStmt
+ *		deparse a ViewStmt
+ *
+ * Given a view OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_ViewStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *viewStmt;
+	ObjTree    *tmp;
+	char	   *command;
+	Relation	relation;
+	OverrideSearchPath *overridePath;
+
+	relation = relation_open(objectId, AccessShareLock);
+
+	viewStmt = new_objtree_VA(NULL,
+					 "CREATE %{persistence}s VIEW %{identity}D AS %{query}s",
+							  1, "persistence", ObjTypeString,
+					  get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(viewStmt,
+								   relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(viewStmt, "identity", tmp);
+
+	/*
+	 * We want all names to be qualified, so set an empty search path before
+	 * calling ruleutils.c.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	PushOverrideSearchPath(overridePath);
+
+	append_string_object(viewStmt, "query",
+						 pg_get_viewdef_internal(objectId));
+	PopOverrideSearchPath();
+
+	command = jsonize_objtree(viewStmt);
+	free_objtree(viewStmt);
+
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * deparse_ColumnDef
+ *		Subroutine for CREATE TABLE deparsing
+ *
+ * Deparse a ColumnDef node within a regular (non typed) table creation.
+ *
+ * NOT NULL constraints in the column definition are emitted directly in the
+ * column definition by this routine; other constraints must be emitted
+ * elsewhere (the info in the parse node is incomplete anyway.)
+ */
+static ObjTree *
+deparse_ColumnDef(ObjTree *parent, Relation relation, List *dpcontext,
+				  ColumnDef *coldef)
+{
+	ObjTree    *column;
+	ObjTree    *tmp;
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+
+	/*
+	 * Inherited columns without local definitions must not be emitted. XXX --
+	 * maybe it is useful to have them with "present = false" or some such?
+	 */
+	if (!coldef->is_local)
+		return NULL;
+
+	attrTup = SearchSysCacheAttName(relid, coldef->colname);
+	if (!HeapTupleIsValid(attrTup))
+		elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+			 coldef->colname, relid);
+	attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+	get_atttypetypmodcoll(relid, attrForm->attnum,
+						  &typid, &typmod, &typcollation);
+
+	column = new_objtree_VA(parent,
+							"%{name}I %{coltype}T %{default}s %{not_null}s %{collation}s",
+							3,
+							"type", ObjTypeString, "column",
+							"name", ObjTypeString, coldef->colname,
+							"coltype", ObjTypeObject,
+							new_objtree_for_type(parent, typid, typmod));
+
+	tmp = new_objtree_VA(parent, "COLLATE %{name}D", 0);
+	if (OidIsValid(typcollation))
+	{
+		ObjTree *collname;
+
+		collname = new_objtree_for_qualname_id(parent,
+											   CollationRelationId,
+											   typcollation);
+		append_object_object(tmp, "name", collname);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "collation", tmp);
+
+	/*
+	 * Emit a NOT NULL declaration if necessary.  Note that we cannot trust
+	 * pg_attribute.attnotnull here, because that bit is also set when primary
+	 * keys are specified; and we must not emit a NOT NULL constraint in that
+	 * case, unless explicitely specified; so we scan the list of constraints
+	 * attached to this column.  (Fortunately, NOT NULL constraints cannot be
+	 * table constraints.)
+	 */
+	saw_notnull = false;
+	foreach(cell, coldef->constraints)
+	{
+		Constraint *constr = (Constraint *) lfirst(cell);
+
+		if (constr->contype == CONSTR_NOTNULL)
+			saw_notnull = true;
+	}
+
+	if (saw_notnull)
+		append_string_object(column, "not_null", "NOT NULL");
+	else
+		append_string_object(column, "not_null", "");
+
+	tmp = new_objtree_VA(parent, "DEFAULT %{default}s", 0);
+	if (attrForm->atthasdef)
+	{
+		char *defstr;
+
+		defstr = RelationGetColumnDefault(relation, attrForm->attnum,
+										  dpcontext);
+
+		append_string_object(tmp, "default", defstr);
+	}
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(column, "default", tmp);
+
+	ReleaseSysCache(attrTup);
+
+	return column;
+}
+
+/*
+ * deparse_ColumnDef_Typed
+ *		Subroutine for CREATE TABLE OF deparsing
+ *
+ * Deparse a ColumnDef node within a typed table creation.	This is simpler
+ * than the regular case, because we don't have to emit the type declaration,
+ * collation, or default.  Here we only return something if the column is being
+ * declared NOT NULL.
+ *
+ * As in deparse_ColumnDef, any other constraint is processed elsewhere.
+ *
+ * FIXME --- actually, what about default values?
+ */
+static ObjTree *
+deparse_ColumnDef_typed(ObjTree *parent, Relation relation, List *dpcontext,
+						ColumnDef *coldef)
+{
+	ObjTree    *column = NULL;
+	Oid			relid = RelationGetRelid(relation);
+	HeapTuple	attrTup;
+	Form_pg_attribute attrForm;
+	Oid			typid;
+	int32		typmod;
+	Oid			typcollation;
+	bool		saw_notnull;
+	ListCell   *cell;
+
+	attrTup = SearchSysCacheAttName(relid, coldef->colname);
+	if (!HeapTupleIsValid(attrTup))
+		elog(ERROR, "could not find cache entry for column \"%s\" of relation %u",
+			 coldef->colname, relid);
+	attrForm = (Form_pg_attribute) GETSTRUCT(attrTup);
+
+	get_atttypetypmodcoll(relid, attrForm->attnum,
+						  &typid, &typmod, &typcollation);
+
+	/*
+	 * Search for a NOT NULL declaration.  As in deparse_ColumnDef, we rely on
+	 * finding a constraint on the column rather than coldef->is_not_null.
+	 */
+	saw_notnull = false;
+	foreach(cell, coldef->constraints)
+	{
+		Constraint *constr = (Constraint *) lfirst(cell);
+
+		if (constr->contype == CONSTR_NOTNULL)
+		{
+			saw_notnull = true;
+			break;
+		}
+	}
+
+	if (saw_notnull)
+		column = new_objtree_VA(parent,
+								"%{name}I WITH OPTIONS NOT NULL", 2,
+								"type", ObjTypeString, "column_notnull",
+								"name", ObjTypeString, coldef->colname);
+
+	ReleaseSysCache(attrTup);
+	return column;
+}
+
+/*
+ * deparseTableElements
+ *		Subroutine for CREATE TABLE deparsing
+ *
+ * Deal with all the table elements (columns and constraints).
+ *
+ * Note we ignore constraints in the parse node here; they are extracted from
+ * system catalogs instead.
+ */
+static List *
+deparseTableElements(List *elements, ObjTree *parent, Relation relation,
+					 List *tableElements, List *dpcontext, bool typed)
+{
+	ListCell   *lc;
+
+	foreach(lc, tableElements)
+	{
+		Node	   *elt = (Node *) lfirst(lc);
+
+		switch (nodeTag(elt))
+		{
+			case T_ColumnDef:
+				{
+					ObjTree    *column;
+
+					column = typed ?
+						deparse_ColumnDef_typed(parent, relation, dpcontext,
+												(ColumnDef *) elt) :
+						deparse_ColumnDef(parent, relation, dpcontext,
+										  (ColumnDef *) elt);
+
+					if (column != NULL)
+						elements = lappend(elements, column);
+				}
+				break;
+			case T_Constraint:
+				break;
+			default:
+				elog(ERROR, "invalid node type %d", nodeTag(elt));
+		}
+	}
+
+	return elements;
+}
+
+/*
+ * obtainTableConstraints
+ *		Subroutine for CREATE TABLE deparsing
+ *
+ * Given a table OID, obtain its constraints and append them to the given
+ * elements list.  The updated list is returned.
+ *
+ * This works for both typed and regular tables.
+ */
+static List *
+obtainTableConstraints(List *elements, Oid objectId, ObjTree *parent)
+{
+	Relation	conRel;
+	ScanKeyData key;
+	SysScanDesc scan;
+	HeapTuple	tuple;
+	ObjTree    *tmp;
+	OverrideSearchPath *overridePath;
+
+	/*
+	 * scan pg_constraint to fetch all constraints linked to the given
+	 * relation.
+	 */
+	conRel = heap_open(ConstraintRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				Anum_pg_constraint_conrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(objectId));
+	scan = systable_beginscan(conRel, ConstraintRelidIndexId,
+							  true, NULL, 1, &key);
+
+	/*
+	 * We need to ensure all names in the constraint definitions are
+	 * qualified, so set an empty search_path for the duration of this loop.
+	 */
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	PushOverrideSearchPath(overridePath);
+
+	/*
+	 * For each constraint, add a node to the list of table elements.  In
+	 * these nodes we include not only the printable information ("fmt"), but
+	 * also separate attributes to indicate the type of constraint, for
+	 * automatic processing.
+	 */
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_constraint constrForm;
+		char	   *contype;
+
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+
+		switch (constrForm->contype)
+		{
+			case CONSTRAINT_CHECK:
+				contype = "check";
+				break;
+			case CONSTRAINT_FOREIGN:
+				contype = "foreign key";
+				break;
+			case CONSTRAINT_PRIMARY:
+				contype = "primary key";
+				break;
+			case CONSTRAINT_UNIQUE:
+				contype = "unique";
+				break;
+			case CONSTRAINT_TRIGGER:
+				contype = "trigger";
+				break;
+			case CONSTRAINT_EXCLUSION:
+				contype = "exclusion";
+				break;
+			default:
+				elog(ERROR, "unrecognized constraint type");
+		}
+
+		/*
+		 * "type" and "contype" are not part of the printable output, but are
+		 * useful to programmatically distinguish these from columns and among
+		 * different constraint types.
+		 *
+		 * XXX it might be useful to also list the column names in a PK, etc.
+		 */
+		tmp = new_objtree_VA(parent,
+							 "CONSTRAINT %{name}I %{definition}s",
+							 4,
+							 "type", ObjTypeString, "constraint",
+							 "contype", ObjTypeString, contype,
+						 "name", ObjTypeString, NameStr(constrForm->conname),
+							 "definition", ObjTypeString,
+						  pg_get_constraintdef_string(HeapTupleGetOid(tuple),
+													  false));
+		elements = lappend(elements, tmp);
+	}
+
+	PopOverrideSearchPath();
+
+	systable_endscan(scan);
+	heap_close(conRel, AccessShareLock);
+
+	return elements;
+}
+
+/*
+ * deparse_CreateStmt
+ *		Deparse a CreateStmt (CREATE TABLE)
+ *
+ * Given a table OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_CreateStmt(Oid objectId, Node *parsetree)
+{
+	CreateStmt *node = (CreateStmt *) parsetree;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	List	   *dpcontext;
+	ObjTree    *createStmt;
+	ObjTree    *tmp;
+	char	   *command;
+	char	   *fmtstr;
+
+	/*
+	 * Typed tables use a slightly different format string: we must not put
+	 * table_elements with parents directly in the fmt string, because if
+	 * there are no options the parens must not be emitted; and also, typed
+	 * tables do not allow for inheritance.
+	 */
+	if (node->ofTypename)
+		fmtstr = "CREATE %{persistence}s TABLE %{identity}D %{if_not_exists}s "
+			"OF %{of_type}T %{table_elements}s "
+			"%{on_commit}s %{tablespace}s";
+	else
+		fmtstr = "CREATE %{persistence}s TABLE %{identity}D %{if_not_exists}s "
+			"(%{table_elements:, }s) %{inherits}s "
+			"%{on_commit}s %{tablespace}s";
+
+	createStmt =
+		new_objtree_VA(NULL, fmtstr, 1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(createStmt,
+								   relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createStmt, "identity", tmp);
+
+	append_string_object(createStmt, "if_not_exists",
+						 node->if_not_exists ? "IF NOT EXISTS" : "");
+
+	dpcontext = deparse_context_for(RelationGetRelationName(relation),
+								  objectId);
+
+	if (node->ofTypename)
+	{
+		List	   *tableelts = NIL;
+
+		/*
+		 * We can't put table elements directly in the fmt string as an array
+		 * surrounded by parens here, because an empty clause would cause a
+		 * syntax error.  Therefore, we use an indirection element and set
+		 * present=false when there are no elements.
+		 */
+		append_string_object(createStmt, "table_kind", "typed");
+
+		tmp = new_objtree_for_type(createStmt, relation->rd_rel->reloftype, -1);
+		append_object_object(createStmt, "of_type", tmp);
+
+		tableelts = deparseTableElements(NIL, createStmt, relation,
+										 node->tableElts, dpcontext, true);
+		tableelts = obtainTableConstraints(tableelts, objectId, createStmt);
+		if (tableelts == NIL)
+			tmp = new_objtree_VA(createStmt, "", 1,
+								 "present", ObjTypeBool, false);
+		else
+			tmp = new_objtree_VA(createStmt, "(%{elements:, }s)", 1,
+								 "elements", ObjTypeArray, tableelts);
+		append_object_object(createStmt, "table_elements", tmp);
+	}
+	else
+	{
+		List	   *tableelts = NIL;
+
+		/*
+		 * There is no need to process LIKE clauses separately; they have
+		 * already been transformed into columns and constraints.
+		 */
+		append_string_object(createStmt, "table_kind", "plain");
+
+		/*
+		 * Process table elements: column definitions and constraints.	Only
+		 * the column definitions are obtained from the parse node itself.	To
+		 * get constraints we rely on pg_constraint, because the parse node
+		 * might be missing some things such as the name of the constraints.
+		 */
+		tableelts = deparseTableElements(NIL, createStmt, relation,
+										 node->tableElts, dpcontext, false);
+		tableelts = obtainTableConstraints(tableelts, objectId, createStmt);
+
+		append_array_object(createStmt, "table_elements", tableelts);
+
+		/*
+		 * Add inheritance specification.  We cannot simply scan the list of
+		 * parents from the parser node, because that may lack the actual
+		 * qualified names of the parent relations.  Rather than trying to
+		 * re-resolve them from the information in the parse node, it seems
+		 * more accurate and convenient to grab it from pg_inherits.
+		 */
+		tmp = new_objtree_VA(createStmt, "INHERITS (%{parents:, }D)", 0);
+		if (list_length(node->inhRelations) > 0)
+		{
+			List	   *parents = NIL;
+			Relation	inhRel;
+			SysScanDesc scan;
+			ScanKeyData key;
+			HeapTuple	tuple;
+
+			inhRel = heap_open(InheritsRelationId, RowExclusiveLock);
+
+			ScanKeyInit(&key,
+						Anum_pg_inherits_inhrelid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(objectId));
+
+			scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId,
+									  true, NULL, 1, &key);
+
+			while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+			{
+				ObjTree    *parent;
+				Form_pg_inherits formInh = (Form_pg_inherits) GETSTRUCT(tuple);
+
+				parent = new_objtree_for_qualname_id(createStmt,
+													 RelationRelationId,
+													 formInh->inhparent);
+
+				parents = lappend(parents, parent);
+			}
+
+			systable_endscan(scan);
+			heap_close(inhRel, RowExclusiveLock);
+
+			append_array_object(tmp, "parents", parents);
+		}
+		else
+		{
+			append_null_object(tmp, "parents");
+			append_bool_object(tmp, "present", false);
+		}
+		append_object_object(createStmt, "inherits", tmp);
+	}
+
+	tmp = new_objtree_VA(createStmt, "TABLESPACE %{tablespace}I", 0);
+	if (node->tablespacename)
+		append_string_object(tmp, "tablespace", node->tablespacename);
+	else
+	{
+		append_null_object(tmp, "tablespace");
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(createStmt, "tablespace", tmp);
+
+	tmp = new_objtree_VA(createStmt, "ON COMMIT %{on_commit_value}s", 0);
+	switch (node->oncommit)
+	{
+		case ONCOMMIT_DROP:
+			append_string_object(tmp, "on_commit_value", "DROP");
+			break;
+
+		case ONCOMMIT_DELETE_ROWS:
+			append_string_object(tmp, "on_commit_value", "DELETE ROWS");
+			break;
+
+		case ONCOMMIT_PRESERVE_ROWS:
+			append_string_object(tmp, "on_commit_value", "PRESERVE ROWS");
+			break;
+
+		case ONCOMMIT_NOOP:
+			append_null_object(tmp, "on_commit_value");
+			append_bool_object(tmp, "present", false);
+			break;
+	}
+	append_object_object(createStmt, "on_commit", tmp);
+
+	command = jsonize_objtree(createStmt);
+
+	free_objtree(createStmt);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+static inline ObjTree *
+deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf("%lu", seqdata->cache_value);
+	tmp = new_objtree_VA(parent, "CACHE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "cache",
+						 "value", ObjTypeString, tmpstr);
+	return tmp;
+}
+
+static inline ObjTree *
+deparse_Seq_Cycle(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+
+	tmp = new_objtree_VA(parent, "%{no}s CYCLE",
+						 2,
+						 "clause", ObjTypeString, "cycle",
+						 "no", ObjTypeString,
+						 seqdata->is_cycled ? "" : "NO");
+	return tmp;
+}
+
+static inline ObjTree *
+deparse_Seq_IncrementBy(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf("%lu", seqdata->increment_by);
+	tmp = new_objtree_VA(parent, "INCREMENT BY %{value}s",
+						 2,
+						 "clause", ObjTypeString, "increment_by",
+						 "value", ObjTypeString, tmpstr);
+	return tmp;
+}
+
+static inline ObjTree *
+deparse_Seq_Minvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf("%lu", seqdata->min_value);
+	tmp = new_objtree_VA(parent, "MINVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "minvalue",
+						 "value", ObjTypeString, tmpstr);
+	return tmp;
+}
+
+static inline ObjTree *
+deparse_Seq_Maxvalue(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf("%lu", seqdata->max_value);
+	tmp = new_objtree_VA(parent, "MAXVALUE %{value}s",
+						 2,
+						 "clause", ObjTypeString, "maxvalue",
+						 "value", ObjTypeString, tmpstr);
+
+	return tmp;
+}
+
+static inline ObjTree *
+deparse_Seq_Startwith(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf("%lu", seqdata->start_value);
+	tmp = new_objtree_VA(parent, "START WITH %{value}s",
+						 2,
+						 "clause", ObjTypeString, "start",
+						 "value", ObjTypeString, tmpstr);
+	return tmp;
+}
+
+static inline ObjTree *
+deparse_Seq_Restart(ObjTree *parent, Form_pg_sequence seqdata)
+{
+	ObjTree	   *tmp;
+	char	   *tmpstr;
+
+	tmpstr = psprintf("%lu", seqdata->last_value);
+	tmp = new_objtree_VA(parent, "RESTART %{value}s",
+						 2,
+						 "clause", ObjTypeString, "restart",
+						 "value", ObjTypeString, tmpstr);
+	return tmp;
+}
+
+static ObjTree *
+deparse_Seq_OwnedBy(ObjTree *parent, Oid sequenceId)
+{
+	ObjTree    *ownedby = NULL;
+	Relation	depRel;
+	SysScanDesc scan;
+	ScanKeyData keys[3];
+	HeapTuple	tuple;
+
+	depRel = heap_open(DependRelationId, AccessShareLock);
+	ScanKeyInit(&keys[0],
+				Anum_pg_depend_classid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationRelationId));
+	ScanKeyInit(&keys[1],
+				Anum_pg_depend_objid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(sequenceId));
+	ScanKeyInit(&keys[2],
+				Anum_pg_depend_objsubid,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(0));
+
+	scan = systable_beginscan(depRel, DependDependerIndexId, true,
+							  NULL, 3, keys);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Oid			ownerId;
+		Form_pg_depend depform;
+		ObjTree    *tmp;
+		char	   *colname;
+
+		depform = (Form_pg_depend) GETSTRUCT(tuple);
+
+		/* only consider AUTO dependencies on pg_class */
+		if (depform->deptype != DEPENDENCY_AUTO)
+			continue;
+		if (depform->refclassid != RelationRelationId)
+			continue;
+		if (depform->refobjsubid <= 0)
+			continue;
+
+		ownerId = depform->refobjid;
+		colname = get_attname(ownerId, depform->refobjsubid);
+		if (colname == NULL)
+			continue;
+
+		tmp = new_objtree_for_qualname_id(parent, RelationRelationId, ownerId);
+		append_string_object(tmp, "attrname", colname);
+		ownedby = new_objtree_VA(parent,
+								 "OWNED BY %{owner}D",
+								 2,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeObject, tmp);
+	}
+
+	systable_endscan(scan);
+	relation_close(depRel, AccessShareLock);
+
+	/*
+	 * If there's no owner column, emit an empty OWNED BY element, set up so
+	 * that it won't print anything.
+	 */
+	if (!ownedby)
+		/* XXX this shouldn't happen ... */
+		ownedby = new_objtree_VA(parent,
+								 "OWNED BY %{owner}D",
+								 3,
+								 "clause", ObjTypeString, "owned",
+								 "owner", ObjTypeNull,
+								 "present", ObjTypeBool, false);
+
+	return ownedby;
+}
+
+
+/*
+ * deparse_CreateSeqStmt
+ *		deparse a CreateSeqStmt
+ *
+ * Given a sequence OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ */
+static char *
+deparse_CreateSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree    *createSeq;
+	ObjTree    *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	char	   *command;
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+
+	seqdata = get_sequence_values(objectId);
+
+	createSeq =
+		new_objtree_VA(NULL,
+					   "CREATE %{persistence}s SEQUENCE %{identity}D "
+					   "%{definition: }s",
+					   1,
+					   "persistence", ObjTypeString,
+					   get_persistence_str(relation->rd_rel->relpersistence));
+
+	tmp = new_objtree_for_qualname(createSeq,
+								   relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(createSeq, "identity", tmp);
+
+	/* definition elements */
+	elems = lappend(elems, deparse_Seq_Cache(createSeq, seqdata));
+	elems = lappend(elems, deparse_Seq_Cycle(createSeq, seqdata));
+	elems = lappend(elems, deparse_Seq_IncrementBy(createSeq, seqdata));
+	elems = lappend(elems, deparse_Seq_Minvalue(createSeq, seqdata));
+	elems = lappend(elems, deparse_Seq_Maxvalue(createSeq, seqdata));
+	elems = lappend(elems, deparse_Seq_Startwith(createSeq, seqdata));
+	elems = lappend(elems, deparse_Seq_Restart(createSeq, seqdata));
+	/* we purposefully do not emit OWNED BY here */
+
+	append_array_object(createSeq, "definition", elems);
+
+	command = jsonize_objtree(createSeq);
+
+	free_objtree(createSeq);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+static char *
+deparse_AlterSeqStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *alterSeq;
+	ObjTree	   *tmp;
+	Relation	relation = relation_open(objectId, AccessShareLock);
+	char	   *command;
+	Form_pg_sequence seqdata;
+	List	   *elems = NIL;
+	ListCell   *cell;
+
+	seqdata = get_sequence_values(objectId);
+
+	alterSeq =
+		new_objtree_VA(NULL,
+					   "ALTER SEQUENCE %{identity}D %{definition: }s", 0);
+	tmp = new_objtree_for_qualname(alterSeq,
+								   relation->rd_rel->relnamespace,
+								   RelationGetRelationName(relation));
+	append_object_object(alterSeq, "identity", tmp);
+
+	foreach(cell, ((AlterSeqStmt *) parsetree)->options)
+	{
+		DefElem *elem = (DefElem *) lfirst(cell);
+		ObjTree *newelm;
+
+		if (strcmp(elem->defname, "cache") == 0)
+			newelm = deparse_Seq_Cache(alterSeq, seqdata);
+		else if (strcmp(elem->defname, "cycle") == 0)
+			newelm = deparse_Seq_Cycle(alterSeq, seqdata);
+		else if (strcmp(elem->defname, "increment_by") == 0)
+			newelm = deparse_Seq_IncrementBy(alterSeq, seqdata);
+		else if (strcmp(elem->defname, "minvalue") == 0)
+			newelm = deparse_Seq_Minvalue(alterSeq, seqdata);
+		else if (strcmp(elem->defname, "maxvalue") == 0)
+			newelm = deparse_Seq_Maxvalue(alterSeq, seqdata);
+		else if (strcmp(elem->defname, "start") == 0)
+			newelm = deparse_Seq_Startwith(alterSeq, seqdata);
+		else if (strcmp(elem->defname, "restart") == 0)
+			newelm = deparse_Seq_Restart(alterSeq, seqdata);
+		else if (strcmp(elem->defname, "owned_by") == 0)
+			newelm = deparse_Seq_OwnedBy(alterSeq, objectId);
+		else
+			elog(ERROR, "invalid sequence option %s", elem->defname);
+
+		elems = lappend(elems, newelm);
+	}
+
+	append_array_object(alterSeq, "definition", elems);
+
+	command = jsonize_objtree(alterSeq);
+
+	free_objtree(alterSeq);
+	relation_close(relation, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * deparse_IndexStmt
+ *		deparse an IndexStmt
+ *
+ * Given an index OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ *
+ * If the index corresponds to a constraint, NULL is returned.
+ */
+static char *
+deparse_IndexStmt(Oid objectId, Node *parsetree)
+{
+	IndexStmt  *node = (IndexStmt *) parsetree;
+	ObjTree    *indexStmt;
+	ObjTree    *tmp;
+	Relation	idxrel;
+	Relation	heaprel;
+	OverrideSearchPath *overridePath;
+	char	   *command;
+	char	   *index_am;
+	char	   *definition;
+	char	   *reloptions;
+	char	   *tablespace;
+	char	   *whereClause;
+
+	if (node->primary || node->isconstraint)
+	{
+		/*
+		 * indexes for PRIMARY KEY and other constraints are output
+		 * separately; return empty here.
+		 */
+		return NULL;
+	}
+
+	idxrel = relation_open(objectId, AccessShareLock);
+	heaprel = relation_open(idxrel->rd_index->indrelid, AccessShareLock);
+
+	overridePath = GetOverrideSearchPath(CurrentMemoryContext);
+	overridePath->schemas = NIL;
+	PushOverrideSearchPath(overridePath);
+
+	pg_get_indexdef_detailed(objectId,
+							 &index_am, &definition, &reloptions,
+							 &tablespace, &whereClause);
+	PopOverrideSearchPath();
+
+	indexStmt =
+		new_objtree_VA(NULL,
+					   "CREATE %{unique}s INDEX %{concurrently}s %{name}I "
+					   "ON %{table}D USING %{index_am}s (%{definition}s) "
+					   "%{with}s %{tablespace}s %{where_clause}s",
+					   5,
+					   "unique", ObjTypeString, node->unique ? "UNIQUE" : "",
+					   "concurrently", ObjTypeString,
+					   node->concurrent ? "CONCURRENTLY" : "",
+					   "name", ObjTypeString, RelationGetRelationName(idxrel),
+					   "definition", ObjTypeString, definition,
+					   "index_am", ObjTypeString, index_am);
+
+	tmp = new_objtree_for_qualname(indexStmt,
+								   heaprel->rd_rel->relnamespace,
+								   RelationGetRelationName(heaprel));
+	append_object_object(indexStmt, "table", tmp);
+
+	/* reloptions */
+	tmp = new_objtree_VA(indexStmt, "WITH (%{opts}s)", 0);
+	if (reloptions)
+		append_string_object(tmp, "opts", reloptions);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(indexStmt, "with", tmp);
+
+	/* tablespace */
+	tmp = new_objtree_VA(indexStmt, "TABLESPACE %{tablespace}s", 0);
+	if (tablespace)
+		append_string_object(tmp, "tablespace", tablespace);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(indexStmt, "tablespace", tmp);
+
+	/* WHERE clause */
+	tmp = new_objtree_VA(indexStmt, "WHERE %{where}s", 0);
+	if (whereClause)
+		append_string_object(tmp, "where", whereClause);
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(indexStmt, "where_clause", tmp);
+
+	command = jsonize_objtree(indexStmt);
+	free_objtree(indexStmt);
+
+	heap_close(idxrel, AccessShareLock);
+	heap_close(heaprel, AccessShareLock);
+
+	return command;
+}
+
+/*
+ * deparse_CreateSchemaStmt
+ *		deparse a CreateSchemaStmt
+ *
+ * Given a schema OID and the parsetree that created it, return the JSON blob
+ * representing the creation command.
+ *
+ * Note we don't output the schema elements given in the creation command.
+ * They must be output separately.	 (In the current implementation,
+ * CreateSchemaCommand passes them back to ProcessUtility, which will lead to
+ * this file if appropriate.)
+ */
+static char *
+deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
+{
+	CreateSchemaStmt *node = (CreateSchemaStmt *) parsetree;
+	ObjTree    *createSchema;
+	ObjTree    *auth;
+	char	   *command;
+
+	createSchema =
+		new_objtree_VA(NULL,
+				"CREATE SCHEMA %{if_not_exists}s %{name}I %{authorization}s",
+					   2,
+					   "name", ObjTypeString, node->schemaname,
+					   "if_not_exists", ObjTypeString,
+					   node->if_not_exists ? "IF NOT EXISTS" : "");
+
+	auth = new_objtree_VA(createSchema,
+						  "AUTHORIZATION %{authorization_role}I", 0);
+	if (node->authid)
+		append_string_object(auth, "authorization_role", node->authid);
+	else
+	{
+		append_null_object(auth, "authorization_role");
+		append_bool_object(auth, "present", false);
+	}
+	append_object_object(createSchema, "authorization", auth);
+
+	command = jsonize_objtree(createSchema);
+	free_objtree(createSchema);
+
+	return command;
+}
+
+/*
+ * Given a utility command parsetree and the OID of the corresponding object,
+ * return a JSON representation of the command.
+ *
+ * The command is expanded fully, so that there are no ambiguities even in the
+ * face of search_path changes.
+ *
+ * Note we currently only support commands for which ProcessUtilitySlow saves
+ * objects to create; currently this excludes all forms of ALTER and DROP.
+ */
+char *
+deparse_utility_command(Oid objectId, Node *parsetree)
+{
+	char	   *command;
+
+	switch (nodeTag(parsetree))
+	{
+		case T_CreateSchemaStmt:
+			command = deparse_CreateSchemaStmt(objectId, parsetree);
+			break;
+
+		case T_CreateStmt:
+			command = deparse_CreateStmt(objectId, parsetree);
+			break;
+
+		case T_IndexStmt:
+			command = deparse_IndexStmt(objectId, parsetree);
+			break;
+
+		case T_ViewStmt:
+			command = deparse_ViewStmt(objectId, parsetree);
+			break;
+
+		case T_CreateSeqStmt:
+			command = deparse_CreateSeqStmt(objectId, parsetree);
+			break;
+
+		case T_AlterSeqStmt:
+			command = deparse_AlterSeqStmt(objectId, parsetree);
+			break;
+
+			/* creation of objects hanging off tables */
+		case T_RuleStmt:
+		case T_CreateTrigStmt:
+			command = NULL;
+			break;
+
+			/* FDW-related objects */
+		case T_CreateForeignTableStmt:
+		case T_CreateFdwStmt:
+		case T_CreateForeignServerStmt:
+		case T_CreateUserMappingStmt:
+
+			/* other local objects */
+		case T_DefineStmt:
+		case T_CreateExtensionStmt:
+		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
+		case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
+		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+		case T_CreateDomainStmt:
+		case T_CreateFunctionStmt:
+		case T_CreateTableAsStmt:
+		case T_CreatePLangStmt:
+		case T_CreateConversionStmt:
+		case T_CreateCastStmt:
+		case T_CreateOpClassStmt:
+		case T_CreateOpFamilyStmt:
+			command = NULL;
+			break;
+
+			/* matviews */
+		case T_RefreshMatViewStmt:
+			command = NULL;
+			break;
+
+		default:
+			command = NULL;
+			elog(LOG, "unrecognized node type: %d",
+				 (int) nodeTag(parsetree));
+	}
+
+	return command;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f4d25bd..5bac81e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -904,6 +904,7 @@ ProcessUtilitySlow(Node *parsetree,
 	bool		isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
 	bool		isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
 	bool		needCleanup;
+	Oid			objectId;
 
 	/* All event trigger calls are done only when isCompleteQuery is true */
 	needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
@@ -922,6 +923,10 @@ ProcessUtilitySlow(Node *parsetree,
 			case T_CreateSchemaStmt:
 				CreateSchemaCommand((CreateSchemaStmt *) parsetree,
 									queryString);
+				/*
+				 * CreateSchemaCommand calls EventTriggerStashCommand
+				 * internally, for reasons explained there.
+				 */
 				break;
 
 			case T_CreateStmt:
@@ -929,7 +934,6 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					List	   *stmts;
 					ListCell   *l;
-					Oid			relOid;
 
 					/* Run parse analysis ... */
 					stmts = transformCreateStmt((CreateStmt *) parsetree,
@@ -946,9 +950,11 @@ ProcessUtilitySlow(Node *parsetree,
 							static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_RELATION,
-													InvalidOid);
+							objectId = DefineRelation((CreateStmt *) stmt,
+													  RELKIND_RELATION,
+													  InvalidOid);
+							EventTriggerStashCommand(objectId, OBJECT_TABLE,
+													 stmt);
 
 							/*
 							 * Let AlterTableCreateToastTable decide if this
@@ -970,20 +976,27 @@ ProcessUtilitySlow(Node *parsetree,
 												   toast_options,
 												   true);
 
-							AlterTableCreateToastTable(relOid, toast_options);
+							AlterTableCreateToastTable(objectId, toast_options);
 						}
 						else if (IsA(stmt, CreateForeignTableStmt))
 						{
 							/* Create the table itself */
-							relOid = DefineRelation((CreateStmt *) stmt,
-													RELKIND_FOREIGN_TABLE,
-													InvalidOid);
+							objectId = DefineRelation((CreateStmt *) stmt,
+													  RELKIND_FOREIGN_TABLE,
+													  InvalidOid);
 							CreateForeignTable((CreateForeignTableStmt *) stmt,
-											   relOid);
+											   objectId);
+							EventTriggerStashCommand(objectId,
+													 OBJECT_FOREIGN_TABLE,
+													 stmt);
 						}
 						else
 						{
-							/* Recurse for anything else */
+							/*
+							 * Recurse for anything else.  Note the recursive
+							 * call will stash the objects so created into our
+							 * event trigger context.
+							 */
 							ProcessUtility(stmt,
 										   queryString,
 										   PROCESS_UTILITY_SUBCOMMAND,
@@ -1114,46 +1127,53 @@ ProcessUtilitySlow(Node *parsetree,
 					switch (stmt->kind)
 					{
 						case OBJECT_AGGREGATE:
-							DefineAggregate(stmt->defnames, stmt->args,
-											stmt->oldstyle, stmt->definition,
-											queryString);
+							objectId =
+								DefineAggregate(stmt->defnames, stmt->args,
+												stmt->oldstyle,
+												stmt->definition, queryString);
 							break;
 						case OBJECT_OPERATOR:
 							Assert(stmt->args == NIL);
-							DefineOperator(stmt->defnames, stmt->definition);
+							objectId = DefineOperator(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TYPE:
 							Assert(stmt->args == NIL);
-							DefineType(stmt->defnames, stmt->definition);
+							objectId = DefineType(stmt->defnames,
+												  stmt->definition);
 							break;
 						case OBJECT_TSPARSER:
 							Assert(stmt->args == NIL);
-							DefineTSParser(stmt->defnames, stmt->definition);
+							objectId = DefineTSParser(stmt->defnames,
+													  stmt->definition);
 							break;
 						case OBJECT_TSDICTIONARY:
 							Assert(stmt->args == NIL);
-							DefineTSDictionary(stmt->defnames,
-											   stmt->definition);
+							objectId = DefineTSDictionary(stmt->defnames,
+														  stmt->definition);
 							break;
 						case OBJECT_TSTEMPLATE:
 							Assert(stmt->args == NIL);
-							DefineTSTemplate(stmt->defnames,
-											 stmt->definition);
+							objectId = DefineTSTemplate(stmt->defnames,
+														stmt->definition);
 							break;
 						case OBJECT_TSCONFIGURATION:
 							Assert(stmt->args == NIL);
-							DefineTSConfiguration(stmt->defnames,
-												  stmt->definition);
+							objectId = DefineTSConfiguration(stmt->defnames,
+															 stmt->definition);
 							break;
 						case OBJECT_COLLATION:
 							Assert(stmt->args == NIL);
-							DefineCollation(stmt->defnames, stmt->definition);
+							objectId = DefineCollation(stmt->defnames,
+													   stmt->definition);
 							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
 								 (int) stmt->kind);
 							break;
 					}
+
+					EventTriggerStashCommand(objectId, stmt->kind, parsetree);
 				}
 				break;
 
@@ -1171,17 +1191,20 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(stmt, queryString);
 
 					/* ... and do it */
-					DefineIndex(stmt,
-								InvalidOid,		/* no predefined OID */
-								false,	/* is_alter_table */
-								true,	/* check_rights */
-								false,	/* skip_build */
-								false); /* quiet */
+					objectId = DefineIndex(stmt,
+										   InvalidOid,		/* no predefined OID */
+										   false,	/* is_alter_table */
+										   true,	/* check_rights */
+										   false,	/* skip_build */
+										   false); /* quiet */
+					EventTriggerStashCommand(objectId, OBJECT_INDEX,
+											 parsetree);
 				}
 				break;
 
 			case T_CreateExtensionStmt:
-				CreateExtension((CreateExtensionStmt *) parsetree);
+				objectId = CreateExtension((CreateExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_EXTENSION, parsetree);
 				break;
 
 			case T_AlterExtensionStmt:
@@ -1193,7 +1216,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateFdwStmt:
-				CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				objectId = CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_FDW, parsetree);
 				break;
 
 			case T_AlterFdwStmt:
@@ -1201,7 +1225,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateForeignServerStmt:
-				CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				objectId = CreateForeignServer((CreateForeignServerStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_FOREIGN_SERVER,
+										 parsetree);
 				break;
 
 			case T_AlterForeignServerStmt:
@@ -1209,7 +1235,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_CreateUserMappingStmt:
-				CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				objectId = CreateUserMapping((CreateUserMappingStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_USER_MAPPING,
+										 parsetree);
 				break;
 
 			case T_AlterUserMappingStmt:
@@ -1224,16 +1252,20 @@ ProcessUtilitySlow(Node *parsetree,
 				{
 					CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
-					DefineCompositeType(stmt->typevar, stmt->coldeflist);
+					objectId = DefineCompositeType(stmt->typevar,
+												   stmt->coldeflist);
+					EventTriggerStashCommand(objectId, OBJECT_TYPE, parsetree);
 				}
 				break;
 
 			case T_CreateEnumStmt:		/* CREATE TYPE AS ENUM */
-				DefineEnum((CreateEnumStmt *) parsetree);
+				objectId = DefineEnum((CreateEnumStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_CreateRangeStmt:		/* CREATE TYPE AS RANGE */
-				DefineRange((CreateRangeStmt *) parsetree);
+				objectId = DefineRange((CreateRangeStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
@@ -1241,66 +1273,80 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-				DefineView((ViewStmt *) parsetree, queryString);
+				objectId = DefineView((ViewStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, OBJECT_VIEW, parsetree);
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
-				CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				objectId = CreateFunction((CreateFunctionStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, OBJECT_FUNCTION, parsetree);
 				break;
 
 			case T_AlterFunctionStmt:	/* ALTER FUNCTION */
-				AlterFunction((AlterFunctionStmt *) parsetree);
+				objectId = AlterFunction((AlterFunctionStmt *) parsetree);
 				break;
 
 			case T_RuleStmt:	/* CREATE RULE */
-				DefineRule((RuleStmt *) parsetree, queryString);
+				objectId = DefineRule((RuleStmt *) parsetree, queryString);
+				EventTriggerStashCommand(objectId, OBJECT_RULE, parsetree);
 				break;
 
 			case T_CreateSeqStmt:
-				DefineSequence((CreateSeqStmt *) parsetree);
+				objectId = DefineSequence((CreateSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_AlterSeqStmt:
-				AlterSequence((AlterSeqStmt *) parsetree);
+				objectId = AlterSequence((AlterSeqStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_SEQUENCE, parsetree);
 				break;
 
 			case T_CreateTableAsStmt:
-				ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+				objectId = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
 								  queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, OBJECT_TABLE, parsetree);
 				break;
 
 			case T_RefreshMatViewStmt:
-				ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+				objectId = ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
 								   queryString, params, completionTag);
+				EventTriggerStashCommand(objectId, OBJECT_MATVIEW, parsetree);
 				break;
 
 			case T_CreateTrigStmt:
-				(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
+				objectId = CreateTrigger((CreateTrigStmt *) parsetree, queryString,
 									 InvalidOid, InvalidOid, false);
+				EventTriggerStashCommand(objectId, OBJECT_TRIGGER, parsetree);
 				break;
 
 			case T_CreatePLangStmt:
-				CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				objectId = CreateProceduralLanguage((CreatePLangStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_LANGUAGE, parsetree);
 				break;
 
 			case T_CreateDomainStmt:
-				DefineDomain((CreateDomainStmt *) parsetree);
+				objectId = DefineDomain((CreateDomainStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_DOMAIN, parsetree);
 				break;
 
 			case T_CreateConversionStmt:
-				CreateConversionCommand((CreateConversionStmt *) parsetree);
+				objectId = CreateConversionCommand((CreateConversionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_CONVERSION, parsetree);
 				break;
 
 			case T_CreateCastStmt:
-				CreateCast((CreateCastStmt *) parsetree);
+				objectId = CreateCast((CreateCastStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_CAST, parsetree);
 				break;
 
 			case T_CreateOpClassStmt:
-				DefineOpClass((CreateOpClassStmt *) parsetree);
+				objectId = DefineOpClass((CreateOpClassStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_OPCLASS, parsetree);
 				break;
 
 			case T_CreateOpFamilyStmt:
-				DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				objectId = DefineOpFamily((CreateOpFamilyStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_OPFAMILY, parsetree);
 				break;
 
 			case T_AlterOpFamilyStmt:
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 5b75d34..b0d77aa 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -285,7 +288,8 @@ format_type_internal(Oid type_oid, int32 typemod,
 
 		case VARCHAROID:
 			if (with_typemod)
-				buf = printTypmod("character varying", typemod, typeform->typmodout);
+				buf = printTypmod("character varying", typemod,
+								  typeform->typmodout);
 			else
 				buf = pstrdup("character varying");
 			break;
@@ -323,6 +327,72 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspname is the schema name, without quotes.
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - is_array indicates whether []s must be added
+ *
+ * Also, we don't try to decode type names to their standard-mandated names.
+ *
+ * XXX there is a lot of code duplication between this routine and
+ * format_type_internal.  (One thing that doesn't quite match is the whole
+ * allow_invalid business.)
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 char **nspname, char **typname, char **typemodstr,
+					 bool *is_array)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	if (array_base_type != InvalidOid &&
+		typeform->typstorage != 'p')
+	{
+		/* Switch our attention to the array element type */
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+		*is_array = true;
+	}
+	else
+		*is_array = false;
+
+	*nspname = get_namespace_name(typeform->typnamespace);
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod > 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");	/* XXX ?? */
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +408,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,13 +420,15 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
 }
 
-
 /*
  * type_maximum_size --- determine maximum width of a variable-width column
  *
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index e1d8aae..11b1e00 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -226,13 +226,13 @@ typedef struct PopulateRecordsetState
 } PopulateRecordsetState;
 
 /*
- * SQL function json_object-keys
+ * SQL function json_object_keys
  *
  * Returns the set of keys for the object argument.
  *
  * This SRF operates in value-per-call mode. It processes the
  * object during the first call, and the keys are simply stashed
- * in an array, whise size is expanded as necessary. This is probably
+ * in an array, whose size is expanded as necessary. This is probably
  * safe enough for a list of keys of a single object, since they are
  * limited in size to NAMEDATALEN and the number of keys is unlikely to
  * be so huge that it has major memory implications.
@@ -1193,7 +1193,7 @@ elements_array_element_end(void *state, bool isnull)
 	text	   *val;
 	HeapTuple	tuple;
 	Datum		values[1];
-	bool nulls[1] = {false};
+	bool		nulls[1] = {false};
 
 	/* skip over nested objects */
 	if (_state->lex->lex_level != 1)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index add5cd1..21ca679 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -587,6 +587,13 @@ pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
 }
 
+char *
+pg_get_viewdef_internal(Oid viewoid)
+{
+	return pg_get_viewdef_worker(viewoid, 0, WRAP_COLUMN_DEFAULT);
+}
+
+
 /*
  * Common code for by-OID and by-name variants of pg_get_viewdef
  */
@@ -966,6 +973,8 @@ pg_get_indexdef_columns(Oid indexrelid, bool pretty)
  *
  * This is now used for exclusion constraints as well: if excludeOps is not
  * NULL then it points to an array of exclusion operator OIDs.
+ *
+ * XXX if you change this function, see pg_get_indexdef_detailed too.
  */
 static char *
 pg_get_indexdef_worker(Oid indexrelid, int colno,
@@ -1245,6 +1254,245 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	return buf.data;
 }
 
+/*
+ * Return an index definition, split in several pieces.
+ *
+ * There is a huge lot of code that's a dupe of pg_get_indexdef_worker, but
+ * control flow is different enough that it doesn't seem worth keeping them
+ * together.
+ */
+void
+pg_get_indexdef_detailed(Oid indexrelid,
+						 char **index_am,
+						 char **definition,
+						 char **reloptions,
+						 char **tablespace,
+						 char **whereClause)
+{
+	HeapTuple	ht_idx;
+	HeapTuple	ht_idxrel;
+	HeapTuple	ht_am;
+	Form_pg_index idxrec;
+	Form_pg_class idxrelrec;
+	Form_pg_am	amrec;
+	List	   *indexprs;
+	ListCell   *indexpr_item;
+	List	   *context;
+	Oid			indrelid;
+	int			keyno;
+	Datum		indcollDatum;
+	Datum		indclassDatum;
+	Datum		indoptionDatum;
+	bool		isnull;
+	oidvector  *indcollation;
+	oidvector  *indclass;
+	int2vector *indoption;
+	StringInfoData definitionBuf;
+	char	   *sep;
+
+	/*
+	 * Fetch the pg_index tuple by the Oid of the index
+	 */
+	ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
+	if (!HeapTupleIsValid(ht_idx))
+		elog(ERROR, "cache lookup failed for index %u", indexrelid);
+	idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
+
+	indrelid = idxrec->indrelid;
+	Assert(indexrelid == idxrec->indexrelid);
+
+	/* Must get indcollation, indclass, and indoption the hard way */
+	indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+								   Anum_pg_index_indcollation, &isnull);
+	Assert(!isnull);
+	indcollation = (oidvector *) DatumGetPointer(indcollDatum);
+
+	indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									Anum_pg_index_indclass, &isnull);
+	Assert(!isnull);
+	indclass = (oidvector *) DatumGetPointer(indclassDatum);
+
+	indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									 Anum_pg_index_indoption, &isnull);
+	Assert(!isnull);
+	indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+
+	/*
+	 * Fetch the pg_class tuple of the index relation
+	 */
+	ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
+	if (!HeapTupleIsValid(ht_idxrel))
+		elog(ERROR, "cache lookup failed for relation %u", indexrelid);
+	idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
+
+	/*
+	 * Fetch the pg_am tuple of the index' access method
+	 */
+	ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
+	if (!HeapTupleIsValid(ht_am))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 idxrelrec->relam);
+	amrec = (Form_pg_am) GETSTRUCT(ht_am);
+
+	/*
+	 * Get the index expressions, if any.  (NOTE: we do not use the relcache
+	 * versions of the expressions and predicate, because we want to display
+	 * non-const-folded expressions.)
+	 */
+	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									 Anum_pg_index_indexprs, &isnull);
+		Assert(!isnull);
+		exprsString = TextDatumGetCString(exprsDatum);
+		indexprs = (List *) stringToNode(exprsString);
+		pfree(exprsString);
+	}
+	else
+		indexprs = NIL;
+
+	indexpr_item = list_head(indexprs);
+
+	context = deparse_context_for(get_relation_name(indrelid), indrelid);
+
+	initStringInfo(&definitionBuf);
+
+	/* output index AM */
+	*index_am = pstrdup(quote_identifier(NameStr(amrec->amname)));
+
+	/*
+	 * Output index definition.  Note the outer parens must be supplied by
+	 * caller.
+	 */
+	sep = "";
+	for (keyno = 0; keyno < idxrec->indnatts; keyno++)
+	{
+		AttrNumber	attnum = idxrec->indkey.values[keyno];
+		int16		opt = indoption->values[keyno];
+		Oid			keycoltype;
+		Oid			keycolcollation;
+		Oid			indcoll;
+
+		appendStringInfoString(&definitionBuf, sep);
+		sep = ", ";
+
+		if (attnum != 0)
+		{
+			/* Simple index column */
+			char	   *attname;
+			int32		keycoltypmod;
+
+			attname = get_relid_attribute_name(indrelid, attnum);
+			appendStringInfoString(&definitionBuf, quote_identifier(attname));
+			get_atttypetypmodcoll(indrelid, attnum,
+								  &keycoltype, &keycoltypmod,
+								  &keycolcollation);
+		}
+		else
+		{
+			/* expressional index */
+			Node	   *indexkey;
+			char	   *str;
+
+			if (indexpr_item == NULL)
+				elog(ERROR, "too few entries in indexprs list");
+			indexkey = (Node *) lfirst(indexpr_item);
+			indexpr_item = lnext(indexpr_item);
+			/* Deparse */
+			str = deparse_expression_pretty(indexkey, context, false, false,
+											0, 0);
+
+			/* Need parens if it's not a bare function call */
+			if (indexkey && IsA(indexkey, FuncExpr) &&
+				((FuncExpr *) indexkey)->funcformat == COERCE_EXPLICIT_CALL)
+				appendStringInfoString(&definitionBuf, str);
+			else
+				appendStringInfo(&definitionBuf, "(%s)", str);
+
+			keycoltype = exprType(indexkey);
+			keycolcollation = exprCollation(indexkey);
+		}
+
+		/* Add collation, even if default */
+		indcoll = indcollation->values[keyno];
+		if (OidIsValid(indcoll))
+			appendStringInfo(&definitionBuf, " COLLATE %s",
+							 generate_collation_name((indcoll)));
+
+		/* Add the operator class name, even if default */
+		get_opclass_name(indclass->values[keyno], InvalidOid, &definitionBuf);
+
+		/* Add options if relevant */
+		if (amrec->amcanorder)
+		{
+			/* if it supports sort ordering, report DESC and NULLS opts */
+			if (opt & INDOPTION_DESC)
+			{
+				appendStringInfoString(&definitionBuf, " DESC");
+				/* NULLS FIRST is the default in this case */
+				if (!(opt & INDOPTION_NULLS_FIRST))
+					appendStringInfoString(&definitionBuf, " NULLS LAST");
+			}
+			else
+			{
+				if (opt & INDOPTION_NULLS_FIRST)
+					appendStringInfoString(&definitionBuf, " NULLS FIRST");
+			}
+		}
+
+		/* XXX excludeOps thingy was here; do we need anything? */
+	}
+	*definition = definitionBuf.data;
+
+	/* output reloptions */
+	*reloptions = flatten_reloptions(indexrelid);
+
+	/* output tablespace */
+	{
+		Oid			tblspc;
+
+		tblspc = get_rel_tablespace(indexrelid);
+		if (OidIsValid(tblspc))
+			*tablespace = pstrdup(quote_identifier(get_tablespace_name(tblspc)));
+		else
+			*tablespace = NULL;
+	}
+
+	/* report index predicate, if any */
+	if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+	{
+		Node	   *node;
+		Datum		predDatum;
+		bool		isnull;
+		char	   *predString;
+
+		/* Convert text string to node tree */
+		predDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+									Anum_pg_index_indpred, &isnull);
+		Assert(!isnull);
+		predString = TextDatumGetCString(predDatum);
+		node = (Node *) stringToNode(predString);
+		pfree(predString);
+
+		/* Deparse */
+		*whereClause =
+			deparse_expression_pretty(node, context, false, false,
+									  0, 0);
+	}
+	else
+		*whereClause = NULL;
+
+	/* Clean up */
+	ReleaseSysCache(ht_idx);
+	ReleaseSysCache(ht_idxrel);
+	ReleaseSysCache(ht_am);
+
+	/* all done */
+}
 
 /*
  * pg_get_constraintdef
@@ -1279,9 +1527,9 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
 
 /* Internal version that returns a palloc'd C string; no pretty-printing */
 char *
-pg_get_constraintdef_string(Oid constraintId)
+pg_get_constraintdef_string(Oid constraintId, bool fullCommand)
 {
-	return pg_get_constraintdef_worker(constraintId, true, 0);
+	return pg_get_constraintdef_worker(constraintId, fullCommand, 0);
 }
 
 static char *
@@ -2368,8 +2616,10 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
 
 	proc = (Form_pg_proc) GETSTRUCT(proctup);
 
-	/* Calculate index into proargdefaults: proargdefaults corresponds to the
-	 * last N input arguments, where N = pronargdefaults. */
+	/*
+	 * Calculate index into proargdefaults: proargdefaults corresponds to the
+	 * last N input arguments, where N = pronargdefaults.
+	 */
 	nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults);
 
 	if (nth_default < 0 || nth_default >= list_length(argdefaults))
@@ -4156,6 +4406,20 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
 	}
 }
 
+char *
+pg_get_viewstmt_definition(Query *viewParse)
+{
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+
+	get_query_def(viewParse, &buf, NIL, NULL, 0,
+				  WRAP_COLUMN_DEFAULT, 1);
+
+	return buf.data;
+}
+
+
 /* ----------
  * get_values_def			- Parse back a VALUES list
  * ----------
@@ -9096,3 +9360,21 @@ flatten_reloptions(Oid relid)
 
 	return result;
 }
+
+/*
+ * Obtain the deparsed default value for the given column of the given table.
+ *
+ * Caller must have set a correct deparse context.
+ */
+char *
+RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
+{
+	Node *defval;
+	char *defstr;
+
+	defval = build_column_default(rel, attno);
+	defstr = deparse_expression_pretty(defval, dpcontext, false, false,
+									   0, 0);
+
+	return defstr;
+}
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 2a9431d..fd20291 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -32,6 +32,8 @@ extern ObjectAddress get_object_address(ObjectType objtype, List *objname,
 				   List *objargs, Relation *relp,
 				   LOCKMODE lockmode, bool missing_ok);
 
+extern Oid get_objtype_catalog_oid(ObjectType objtype);
+
 extern void check_object_ownership(Oid roleid,
 					   ObjectType objtype, ObjectAddress address,
 					   List *objname, List *objargs, Relation relation);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e6713a6..66ab6f1 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4793,6 +4793,10 @@ DESCR("information about replication slots currently in use");
 /* event triggers */
 DATA(insert OID = 3566 (  pg_event_trigger_dropped_objects		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
 DESCR("list objects dropped by the current command");
+DATA(insert OID = 3567 (  pg_event_trigger_get_creation_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{25,114}" "{o,o}" "{identity,command}" _null_ pg_event_trigger_get_creation_commands _null_ _null_ _null_ ));
+DESCR("list objects created by the current command");
+DATA(insert OID = 3568 (  pg_event_trigger_expand_command PGNSP PGUID 12 10 1 0 0 f f f f t f s 1 0 25 "114" _null_ _null_ _null_ _null_ pg_event_trigger_expand_command _null_ _null_ _null_ ));
+DESCR("format JSON message");
 
 /* generic transition functions for ordered-set aggregates */
 DATA(insert OID = 3970 ( ordered_set_transition			PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/alter_table.h b/src/include/commands/alter_table.h
new file mode 100644
index 0000000..0974098
--- /dev/null
+++ b/src/include/commands/alter_table.h
@@ -0,0 +1,53 @@
+#ifndef ALTER_TABLE_H
+#define ALTER_TABLE_H
+
+/*
+ * State information for ALTER TABLE
+ *
+ * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
+ * structs, one for each table modified by the operation (the named table
+ * plus any child tables that are affected).  We save lists of subcommands
+ * to apply to this table (possibly modified by parse transformation steps);
+ * these lists will be executed in Phase 2.  If a Phase 3 step is needed,
+ * necessary information is stored in the constraints and newvals lists.
+ *
+ * Phase 2 is divided into multiple passes; subcommands are executed in
+ * a pass determined by subcommand type.
+ */
+
+#define AT_PASS_UNSET			-1		/* UNSET will cause ERROR */
+#define AT_PASS_DROP			0		/* DROP (all flavors) */
+#define AT_PASS_ALTER_TYPE		1		/* ALTER COLUMN TYPE */
+#define AT_PASS_OLD_INDEX		2		/* re-add existing indexes */
+#define AT_PASS_OLD_CONSTR		3		/* re-add existing constraints */
+#define AT_PASS_COL_ATTRS		4		/* set other column attributes */
+/* We could support a RENAME COLUMN pass here, but not currently used */
+#define AT_PASS_ADD_COL			5		/* ADD COLUMN */
+#define AT_PASS_ADD_INDEX		6		/* ADD indexes */
+#define AT_PASS_ADD_CONSTR		7		/* ADD constraints, defaults */
+#define AT_PASS_MISC			8		/* other stuff */
+#define AT_NUM_PASSES			9
+
+typedef struct AlteredTableInfo
+{
+	/* Information saved before any work commences: */
+	Oid			relid;			/* Relation to work on */
+	char		relkind;		/* Its relkind */
+	TupleDesc	oldDesc;		/* Pre-modification tuple descriptor */
+	/* Information saved by Phase 1 for Phase 2: */
+	List	   *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
+	/* Information saved by Phases 1/2 for Phase 3: */
+	List	   *constraints;	/* List of NewConstraint */
+	List	   *newvals;		/* List of NewColumnValue */
+	bool		new_notnull;	/* T if we added new NOT NULL constraints */
+	bool		rewrite;		/* T if a rewrite is forced */
+	Oid			newTableSpace;	/* new tablespace; 0 means no change */
+	/* Objects to rebuild after completing ALTER TYPE operations */
+	List	   *changedConstraintOids;	/* OIDs of constraints to rebuild */
+	List	   *changedConstraintDefs;	/* string definitions of same */
+	List	   *changedIndexOids;		/* OIDs of indexes to rebuild */
+	List	   *changedIndexDefs;		/* string definitions of same */
+} AlteredTableInfo;
+
+
+#endif	/* ALTER_TABLE_H */
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index c17d829..477339d 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -19,7 +19,7 @@
 #include "tcop/dest.h"
 
 
-extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+extern Oid	ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 0233f4c..3c38af3 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -49,6 +49,8 @@ extern void EventTriggerSQLDrop(Node *parsetree);
 
 extern bool EventTriggerBeginCompleteQuery(void);
 extern void EventTriggerEndCompleteQuery(void);
+extern void EventTriggerStashCommand(Oid objectId, ObjectType objtype,
+						 Node *parsetree);
 extern bool trackDroppedObjectsNeeded(void);
 extern void EventTriggerSQLDropAddObject(ObjectAddress *object);
 
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index 476b285..2c468b2 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -22,7 +22,7 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
-extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
+extern Oid ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag);
 
 extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7d8a370..b7a5db4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -70,6 +70,7 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS);
 extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
+extern Form_pg_sequence get_sequence_values(Oid sequenceId);
 
 extern Oid	DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ad58b39..6f87e53 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1221,6 +1221,7 @@ typedef enum ObjectType
 	OBJECT_TSPARSER,
 	OBJECT_TSTEMPLATE,
 	OBJECT_TYPE,
+	OBJECT_USER_MAPPING,
 	OBJECT_VIEW
 } ObjectType;
 
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
new file mode 100644
index 0000000..7e63f52
--- /dev/null
+++ b/src/include/tcop/deparse_utility.h
@@ -0,0 +1,17 @@
+/*-------------------------------------------------------------------------
+ *
+ * deparse_utility.h
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/tcop/deparse_utility.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DEPARSE_UTILITY_H
+#define DEPARSE_UTILITY_H
+
+extern char *deparse_utility_command(Oid objectId, Node *parsetree);
+
+#endif	/* DEPARSE_UTILITY_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b90d88d..8fd829d 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -663,15 +663,24 @@ extern Datum pg_get_viewdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
+extern char *pg_get_viewdef_internal(Oid viewoid);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
 extern char *pg_get_indexdef_string(Oid indexrelid);
 extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+extern void pg_get_indexdef_detailed(Oid indexrelid,
+						 char **index_am,
+						 char **definition,
+						 char **reloptions,
+						 char **tablespace,
+						 char **whereClause);
+
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_constraintdef_string(Oid constraintId);
+extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern char *pg_get_viewstmt_definition(Query *viewParse);
 extern Datum pg_get_expr(PG_FUNCTION_ARGS);
 extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
@@ -1057,6 +1066,9 @@ extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern Datum oidvectortypes(PG_FUNCTION_ARGS);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
+extern void format_type_detailed(Oid type_oid, int32 typemod,
+					 char **nspname, char **typname,
+					 char **typemodstr, bool *is_array);
 
 /* quote.c */
 extern Datum quote_ident(PG_FUNCTION_ARGS);
@@ -1177,6 +1189,8 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 
 /* commands/event_trigger.c */
 extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_expand_command(PG_FUNCTION_ARGS);
 
 /* commands/extension.c */
 extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 31f4878..4501134 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -41,6 +41,9 @@ extern List *RelationGetIndexList(Relation relation);
 extern Oid	RelationGetOidIndex(Relation relation);
 extern List *RelationGetIndexExpressions(Relation relation);
 extern List *RelationGetIndexPredicate(Relation relation);
+/* in ruleutils.c */
+extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
+						 List *dpcontext);
 
 typedef enum IndexAttrBitmapKind
 {
#47Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#46)
Re: Add CREATE support to event triggers

Alvaro Herrera escribi�:

I also fixed the sequence OWNED BY problem simply by adding support for
ALTER SEQUENCE. Of course, the intention is that all forms of CREATE
and ALTER are supported, but this one seems reasonable standalone
because CREATE TABLE uses it internally.

I have been hacking on this on and off. This afternoon I discovered
that interval typmod output can also be pretty unusual. Example:

create table a (a interval year to month);

For the column, we get this type spec (note the typmod):

"coltype": {
"is_array": false,
"schemaname": "pg_catalog",
"typename": "interval",
"typmod": " year to month"
},

so the whole command output ends up being this:

NOTICE: expanded: CREATE TABLE public.a (a pg_catalog."interval" year to month ) WITH (oids=OFF)

However, this is not accepted on input:

alvherre=# CREATE TABLE public.a (a pg_catalog."interval" year to month ) WITH (oids=OFF);
ERROR: syntax error at or near "year"
L�NEA 1: CREATE TABLE public.a (a pg_catalog."interval" year to mon...
^

I'm not too sure what to do about this yet. I checked the catalogs and
gram.y, and it seems that interval is the only type that allows such
strange games to be played. I would hate to be forced to add a kludge
specific to type interval, but that seems to be the only option. (This
would involve checking the OID of the type in deparse_utility.c, and if
it's INTERVALOID, then omit the schema qualification and quoting on the
type name).

I have also been working on adding ALTER TABLE support. So far it's
pretty simple; here is an example. Note I run a single command which
includes a SERIAL column, and on output I get three commands (just like
a serial column on create table).

alvherre=# alter table tt add column b numeric, add column c serial, alter column a set default extract(epoch from now());
NOTICE: JSON blob: {
"definition": [
{
"clause": "cache",
"fmt": "CACHE %{value}s",
"value": "1"
},
{
"clause": "cycle",
"fmt": "%{no}s CYCLE",
"no": "NO"
},
{
"clause": "increment_by",
"fmt": "INCREMENT BY %{value}s",
"value": "1"
},
{
"clause": "minvalue",
"fmt": "MINVALUE %{value}s",
"value": "1"
},
{
"clause": "maxvalue",
"fmt": "MAXVALUE %{value}s",
"value": "9223372036854775807"
},
{
"clause": "start",
"fmt": "START WITH %{value}s",
"value": "1"
},
{
"clause": "restart",
"fmt": "RESTART %{value}s",
"value": "1"
}
],
"fmt": "CREATE %{persistence}s SEQUENCE %{identity}D %{definition: }s",
"identity": {
"objname": "tt_c_seq",
"schemaname": "public"
},
"persistence": ""
}
NOTICE: expanded: CREATE SEQUENCE public.tt_c_seq CACHE 1 NO CYCLE INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 RESTART 1
NOTICE: JSON blob: {
"fmt": "ALTER TABLE %{identity}D %{subcmds:, }s",
"identity": {
"objname": "tt",
"schemaname": "public"
},
"subcmds": [
{
"definition": {
"collation": {
"fmt": "COLLATE %{name}D",
"present": false
},
"coltype": {
"is_array": false,
"schemaname": "pg_catalog",
"typename": "numeric",
"typmod": ""
},
"default": {
"fmt": "DEFAULT %{default}s",
"present": false
},
"fmt": "%{name}I %{coltype}T %{default}s %{not_null}s %{collation}s",
"name": "b",
"not_null": "",
"type": "column"
},
"fmt": "ADD COLUMN %{definition}s",
"type": "add column"
},
{
"definition": {
"collation": {
"fmt": "COLLATE %{name}D",
"present": false
},
"coltype": {
"is_array": false,
"schemaname": "pg_catalog",
"typename": "int4",
"typmod": ""
},
"default": {
"default": "pg_catalog.nextval('public.tt_c_seq'::pg_catalog.regclass)",
"fmt": "DEFAULT %{default}s"
},
"fmt": "%{name}I %{coltype}T %{default}s %{not_null}s %{collation}s",
"name": "c",
"not_null": "",
"type": "column"
},
"fmt": "ADD COLUMN %{definition}s",
"type": "add column"
},
{
"column": "a",
"definition": "pg_catalog.date_part('epoch'::pg_catalog.text, pg_catalog.now())",
"fmt": "ALTER COLUMN %{column}I SET DEFAULT %{definition}s",
"type": "set default"
}
]
}
NOTICE: expanded: ALTER TABLE public.tt ADD COLUMN b pg_catalog."numeric" , ADD COLUMN c pg_catalog.int4 DEFAULT pg_catalog.nextval('public.tt_c_seq'::pg_catalog.regclass) , ALTER COLUMN a SET DEFAULT pg_catalog.date_part('epoch'::pg_catalog.text, pg_catalog.now())
NOTICE: JSON blob: {
"definition": [
{
"clause": "owned",
"fmt": "OWNED BY %{owner}D",
"owner": {
"attrname": "c",
"objname": "tt",
"schemaname": "public"
}
}
],
"fmt": "ALTER SEQUENCE %{identity}D %{definition: }s",
"identity": {
"objname": "tt_c_seq",
"schemaname": "public"
}
}
NOTICE: expanded: ALTER SEQUENCE public.tt_c_seq OWNED BY public.tt.c
ALTER TABLE

Each subcommand is represented separately in a JSON array. Each element
in the array has a "type" element indicating (broadly) what it's doing;
the "fmt" element has all the details. So things like replication
systems might decide to replicate some part of the ALTER or not,
depending on the specific type. (And, of course, they can easily decide
that replica XYZ must not replay the command because the table is not
supposed to exist there; or perhaps it belongs to a replication set that
is not the one the current node is origin for.)

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#47)
Re: Add CREATE support to event triggers

On Thu, Mar 13, 2014 at 5:06 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Alvaro Herrera escribió:

I also fixed the sequence OWNED BY problem simply by adding support for
ALTER SEQUENCE. Of course, the intention is that all forms of CREATE
and ALTER are supported, but this one seems reasonable standalone
because CREATE TABLE uses it internally.

I have been hacking on this on and off. This afternoon I discovered
that interval typmod output can also be pretty unusual. Example:

create table a (a interval year to month);

For the column, we get this type spec (note the typmod):

"coltype": {
"is_array": false,
"schemaname": "pg_catalog",
"typename": "interval",
"typmod": " year to month"
},

so the whole command output ends up being this:

NOTICE: expanded: CREATE TABLE public.a (a pg_catalog."interval" year to month ) WITH (oids=OFF)

However, this is not accepted on input:

alvherre=# CREATE TABLE public.a (a pg_catalog."interval" year to month ) WITH (oids=OFF);
ERROR: syntax error at or near "year"
LÍNEA 1: CREATE TABLE public.a (a pg_catalog."interval" year to mon...
^

I'm not too sure what to do about this yet. I checked the catalogs and
gram.y, and it seems that interval is the only type that allows such
strange games to be played. I would hate to be forced to add a kludge
specific to type interval, but that seems to be the only option. (This
would involve checking the OID of the type in deparse_utility.c, and if
it's INTERVALOID, then omit the schema qualification and quoting on the
type name).

I have also been working on adding ALTER TABLE support. So far it's
pretty simple; here is an example. Note I run a single command which
includes a SERIAL column, and on output I get three commands (just like
a serial column on create table).

alvherre=# alter table tt add column b numeric, add column c serial, alter column a set default extract(epoch from now());
NOTICE: JSON blob: {
"definition": [
{
"clause": "cache",
"fmt": "CACHE %{value}s",
"value": "1"
},
{
"clause": "cycle",
"fmt": "%{no}s CYCLE",
"no": "NO"
},
{
"clause": "increment_by",
"fmt": "INCREMENT BY %{value}s",
"value": "1"
},
{
"clause": "minvalue",
"fmt": "MINVALUE %{value}s",
"value": "1"
},
{
"clause": "maxvalue",
"fmt": "MAXVALUE %{value}s",
"value": "9223372036854775807"
},
{
"clause": "start",
"fmt": "START WITH %{value}s",
"value": "1"
},
{
"clause": "restart",
"fmt": "RESTART %{value}s",
"value": "1"
}
],
"fmt": "CREATE %{persistence}s SEQUENCE %{identity}D %{definition: }s",
"identity": {
"objname": "tt_c_seq",
"schemaname": "public"
},
"persistence": ""
}

What does the colon-space in %{definition: }s mean?

In general, it seems like you're making good progress here, and I'm
definitely happier with this than with previous approaches, but I'm
still concerned about how maintainable it's going to be.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#46)
35 attachment(s)
Re: Add CREATE support to event triggers

Here's a refreshed version of this patch. I have split it up in a
largish number of pieces, which hopefully makes it easier to understand
what is going on. First there are a number of trivial fixups which
mostly shouldn't require much of an explanation, or add functionality to
ruleutils that later patches need. I also split builtins.h to create a
new ruleutils.h header, in which there are several prototypes for
functions in ruleutils.c. (The reason for this is that a later patch
adds a bunch more prototypes which require other headers to be #included
by ruleutils.h, and it didn't seem a good idea to include those in
builtins.h. This doesn't affect much -- only 7 .c files had to #include
the new file.)

The first really interesting patch is 0014, which introduces the concept
of a "command queue" in event triggers, where DDL commands are stored as
parse nodes plus supporting data. At ddl_command_end, the user code can
call the new pg_event_trigger_get_creation_commands() function, which
causes the parse node to be deparsed and returned as a JSON blob. This
patch also introduces a function to decode the JSON blob back into a
text string, possibly after being tweaked by the user code. A bunch of
DDL commands are implemented here, such as CREATE SCHEMA, CREATE TABLE,
and the like -- the basics.

Then we have a bunch more patches which add support for more commands.
In these patches you can see how the deparsing code for a certain
command can be written. Most are pretty simple, but interesting
examples such as CREATE FUNCTION are also included. We also have some
commands that are not merely create, such as ALTER..RENAME.

Patch 23 is the next interesting one -- it adds support for ALTER TABLE.
This one is a lot more involved, because deparsing ALTER TABLE
subcommands requires adding a call to collect each subcommand in
tablecmds.c itself. In order to implement this, I modified most
subcommand functions so that they return the OID of the affected object,
or the attribute number of the affected column. For example if you add
a constraint, the OID of the constraint is returned. This OID is
collected and added to the list of subcommands. At deparse time, the
collected commands can be deparsed back into JSON blobs as usual. Since
certain commands execute AlterTable internally, I had to add
"bracketing" calls to them, which set up a pseudo-ALTER TABLE context
onto which the subcommands can be added. For instance, CREATE INDEX,
CREATE VIEW and CREATE TABLESPACE MOVE are known to do this.

... then a bunch more boring commands are implemented ...

Finally, patch 36 adds support for GRANT and REVOKE deparsing. This one
is interesting because it's completely unlike the others. Here we don't
have a single OID of an affected object. I chose to support this by
exposing the InternalGrantStmt struct (in a new aclchk.h file), which we
can deparse easily into the familiar JSON format. (Of course, in an
patch earlier in the series I also had to modify event triggers so that
grant/revoke fire them as well, which currently they do not.)

There are no docs yet. This has been tested in the bidirectional
replication suite -- it works by saving the deparsed (SQL, not JSON)
command in a replicated table; the remote side executes the command when
it receives the tuple. Commands that fail to deparse sanely cause very
visible errors.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0032-deparse-Initial-support-for-CREATE-TEXT-SEARCH-CONFI.patchtext/x-diff; charset=utf-8Download
>From 94137ba6c7ef0a05c1bc0df7966ce44cbaeffe36 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Tue, 6 May 2014 11:53:45 +0530
Subject: [PATCH 32/36] deparse: Initial support for CREATE TEXT SEARCH
 CONFIGURATION

Since there's no way to create token-dict mappings with this CREATE
command, we search for them and refuse to deparse the configuration
if they exist.
---
 src/backend/tcop/deparse_utility.c | 86 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 85 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 8c2fa9e..bfe0eee 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -47,6 +47,8 @@
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_config.h"
+#include "catalog/pg_ts_config_map.h"
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
@@ -757,6 +759,85 @@ deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define)
 }
 
 static ObjTree *
+deparse_DefineStmt_TSConfig(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   tscTup;
+	HeapTuple   tspTup;
+	ObjTree	   *stmt;
+	Form_pg_ts_config tscForm;
+	Form_pg_ts_parser tspForm;
+
+	tscTup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tscTup))
+		elog(ERROR, "cache lookup failed for text search configuration "
+			 "with OID %u", objectId);
+	tscForm = (Form_pg_ts_config) GETSTRUCT(tscTup);
+
+	tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(tscForm->cfgparser));
+	if (!HeapTupleIsValid(tspTup))
+		elog(ERROR, "cache lookup failed for text search parser with OID %u",
+			 tscForm->cfgparser);
+	tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH CONFIGURATION %{identity}D "
+						  "(PARSER=%{parser}s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tscForm->cfgnamespace,
+												  NameStr(tscForm->cfgname)));
+	append_object_object(stmt, "parser",
+						 new_objtree_for_qualname(tspForm->prsnamespace,
+												  NameStr(tspForm->prsname)));
+
+	/*
+	 * If this text search configuration was created by copying another
+	 * one with "CREATE TEXT SEARCH CONFIGURATION x (COPY=y)", then y's
+	 * PARSER selection is copied along with its mappings of tokens to
+	 * dictionaries (created with ALTER … ADD MAPPING …).
+	 *
+	 * Unfortunately, there's no way to define these mappings in the
+	 * CREATE command, so if they exist for the configuration we're
+	 * deparsing, we must detect them and fail.
+	 */
+
+	{
+		ScanKeyData skey;
+		SysScanDesc scan;
+		HeapTuple	tup;
+		Relation	map;
+		bool		has_mapping;
+
+		map = heap_open(TSConfigMapRelationId, AccessShareLock);
+
+		ScanKeyInit(&skey,
+					Anum_pg_ts_config_map_mapcfg,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(objectId));
+
+		scan = systable_beginscan(map, TSConfigMapIndexId, true,
+								  NULL, 1, &skey);
+
+		while (HeapTupleIsValid((tup = systable_getnext(scan))))
+		{
+			has_mapping = true;
+			break;
+		}
+
+		systable_endscan(scan);
+		heap_close(map, AccessShareLock);
+
+		if (has_mapping)
+			ereport(ERROR,
+					(errmsg("can't recreate text search configuration with mappings")));
+	}
+
+	ReleaseSysCache(tspTup);
+	ReleaseSysCache(tscTup);
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define)
 {
 	HeapTuple   tspTup;
@@ -1137,6 +1218,10 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 			defStmt = deparse_DefineStmt_Operator(objectId, define);
 			break;
 
+		case OBJECT_TSCONFIGURATION:
+			defStmt = deparse_DefineStmt_TSConfig(objectId, define);
+			break;
+
 		case OBJECT_TSPARSER:
 			defStmt = deparse_DefineStmt_TSParser(objectId, define);
 			break;
@@ -1155,7 +1240,6 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 
 		default:
 		case OBJECT_AGGREGATE:
-		case OBJECT_TSCONFIGURATION:
 			elog(ERROR, "unsupported object kind");
 			return NULL;
 	}
-- 
1.9.1

0033-deparse-Support-CREATE-AGGREGATE.patchtext/x-diff; charset=us-asciiDownload
>From b765cadf59111ecfafcb73b73035ddac41b0516d Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Tue, 6 May 2014 13:46:01 +0530
Subject: [PATCH 33/36] deparse: Support CREATE AGGREGATE

---
 src/backend/tcop/deparse_utility.c | 241 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 240 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index bfe0eee..ec457ca 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -638,6 +638,242 @@ get_persistence_str(char persistence)
 }
 
 static ObjTree *
+deparse_DefineStmt_Aggregate(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   aggTup;
+	HeapTuple   procTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	Datum		initval;
+	bool		isnull;
+	Form_pg_aggregate agg;
+	Form_pg_proc proc;
+
+	aggTup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(aggTup))
+		elog(ERROR, "cache lookup failed for aggregate with OID %u", objectId);
+	agg = (Form_pg_aggregate) GETSTRUCT(aggTup);
+
+	procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(agg->aggfnoid));
+	if (!HeapTupleIsValid(procTup))
+		elog(ERROR, "cache lookup failed for procedure with OID %u",
+			 agg->aggfnoid);
+	proc = (Form_pg_proc) GETSTRUCT(procTup);
+
+	stmt = new_objtree_VA("CREATE AGGREGATE %{identity}D (%{types:, }s) "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(proc->pronamespace,
+												  NameStr(proc->proname)));
+
+	list = NIL;
+
+	/*
+	 * An aggregate may have no arguments, in which case its signature
+	 * is (*), to match count(*). If it's not an ordered-set aggregate,
+	 * it may have a non-zero number of arguments. Otherwise it may have
+	 * zero or more direct arguments and zero or more ordered arguments.
+	 * There are no defaults or table parameters, and the only mode that
+	 * we need to consider is VARIADIC.
+	 */
+
+	if (proc->pronargs == 0)
+		list = lappend(list, new_object_object(NULL, new_objtree_VA("*", 0)));
+	else
+	{
+		int			i;
+		int			nargs;
+		Oid		   *types;
+		char	   *modes;
+		char	  **names;
+		int			insertorderbyat = -1;
+
+		nargs = get_func_arg_info(procTup, &types, &names, &modes);
+
+		if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
+			insertorderbyat = agg->aggnumdirectargs;
+
+		for (i = 0; i < nargs; i++)
+		{
+			tmp = new_objtree_VA("%{order}s%{mode}s%{name}s%{type}T", 0);
+
+			if (i == insertorderbyat)
+				append_string_object(tmp, "order", "ORDER BY ");
+			else
+				append_string_object(tmp, "order", "");
+
+			if (modes)
+				append_string_object(tmp, "mode",
+									 modes[i] == 'v' ? "VARIADIC " : "");
+			else
+				append_string_object(tmp, "mode", "");
+
+			if (names)
+				append_string_object(tmp, "name", names[i]);
+			else
+				append_string_object(tmp, "name", " ");
+
+			append_object_object(tmp, "type",
+								 new_objtree_for_type(types[i], -1));
+
+			list = lappend(list, new_object_object(NULL, tmp));
+
+			/*
+			 * For variadic ordered-set aggregates, we have to repeat
+			 * the last argument. This nasty hack is copied from
+			 * print_function_arguments in ruleutils.c
+			 */
+			if (i == insertorderbyat && i == nargs-1)
+				list = lappend(list, new_object_object(NULL, tmp));
+		}
+	}
+
+	append_array_object(stmt, "types", list);
+
+	list = NIL;
+
+	tmp = new_objtree_VA("SFUNC=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 agg->aggtransfn));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("STYPE=%{type}T", 0);
+	append_object_object(tmp, "type",
+						 new_objtree_for_type(agg->aggtranstype, -1));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	if (agg->aggtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}s", 1,
+							 "space", ObjTypeString,
+							 psprintf("%d", agg->aggtransspace));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggfinalfn))
+	{
+		tmp = new_objtree_VA("FINALFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggfinalfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggfinalextra)
+	{
+		tmp = new_objtree_VA("FINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_agginitval,
+							  &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("INITCOND=%{initval}L",
+							 1, "initval", ObjTypeString,
+							 TextDatumGetCString(initval));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggmtransfn))
+	{
+		tmp = new_objtree_VA("MSFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmtransfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggmtranstype))
+	{
+		tmp = new_objtree_VA("MSTYPE=%{type}T", 0);
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(agg->aggmtranstype, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggmtransspace != 0)
+	{
+		tmp = new_objtree_VA("SSPACE=%{space}s", 1,
+							 "space", ObjTypeString,
+							 psprintf("%d", agg->aggmtransspace));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggminvtransfn))
+	{
+		tmp = new_objtree_VA("MINVFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggminvtransfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggmfinalfn))
+	{
+		tmp = new_objtree_VA("MFINALFUNC=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 agg->aggmfinalfn));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggmfinalextra)
+	{
+		tmp = new_objtree_VA("MFINALFUNC_EXTRA=true", 0);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	initval = SysCacheGetAttr(AGGFNOID, aggTup,
+							  Anum_pg_aggregate_aggminitval,
+							  &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("MINITCOND=%{initval}L",
+							 1, "initval", ObjTypeString,
+							 TextDatumGetCString(initval));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (agg->aggkind == AGGKIND_HYPOTHETICAL)
+	{
+		tmp = new_objtree_VA("HYPOTHETICAL=true", 0);
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(agg->aggsortop))
+	{
+		Oid sortop = agg->aggsortop;
+		Form_pg_operator op;
+		HeapTuple tup;
+
+		tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop));
+		if (!HeapTupleIsValid(tup))
+			elog(ERROR, "cache lookup failed for operator with OID %u", sortop);
+		op = (Form_pg_operator) GETSTRUCT(tup);
+
+		tmp = new_objtree_VA("SORTOP=%{operator}O", 0);
+		append_object_object(tmp, "operator",
+							 new_objtree_for_qualname(op->oprnamespace,
+													  NameStr(op->oprname)));
+		list = lappend(list, new_object_object(NULL, tmp));
+
+		ReleaseSysCache(tup);
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(procTup);
+	ReleaseSysCache(aggTup);
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define)
 {
 	HeapTuple   colTup;
@@ -1210,6 +1446,10 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 
 	switch (define->kind)
 	{
+		case OBJECT_AGGREGATE:
+			defStmt = deparse_DefineStmt_Aggregate(objectId, define);
+			break;
+
 		case OBJECT_COLLATION:
 			defStmt = deparse_DefineStmt_Collation(objectId, define);
 			break;
@@ -1239,7 +1479,6 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 			break;
 
 		default:
-		case OBJECT_AGGREGATE:
 			elog(ERROR, "unsupported object kind");
 			return NULL;
 	}
-- 
1.9.1

0034-deparse-support-ALTER-THING-OWNER-TO.patchtext/x-diff; charset=us-asciiDownload
>From f3a4b84c0989dceb25a97cbe0b8c6180ac5e4e7a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 6 May 2014 17:22:13 -0400
Subject: [PATCH 34/36] deparse: support ALTER THING OWNER TO

---
 src/backend/tcop/deparse_utility.c | 31 +++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c         |  5 ++++-
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index ec457ca..b7bb4da 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -3706,6 +3706,33 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_AlterOwnerStmt(Oid objectId, Node *parsetree)
+{
+	AlterOwnerStmt *node = (AlterOwnerStmt *) parsetree;
+	ObjTree	   *ownerStmt;
+	ObjectAddress addr;
+	char	   *fmt;
+	char	   *command;
+
+	fmt = psprintf("ALTER %s %%{identity}s OWNER TO %%{newname}I",
+				   stringify_objtype(node->objectType));
+	ownerStmt = new_objtree_VA(fmt, 0);
+	append_string_object(ownerStmt, "newname", node->newowner);
+
+	addr.classId = get_objtype_catalog_oid(node->objectType);
+	addr.objectId = objectId;
+	addr.objectSubId = 0;
+
+	append_string_object(ownerStmt, "identity",
+						 getObjectIdentity(&addr));
+
+	command = jsonize_objtree(ownerStmt);
+	free_objtree(ownerStmt);
+
+	return command;
+}
+
+static char *
 deparse_CreateConversion(Oid objectId, Node *parsetree)
 {
 	HeapTuple   conTup;
@@ -4297,6 +4324,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_AlterEnumStmt(objectId, parsetree);
 			break;
 
+		case T_AlterOwnerStmt:
+			command = deparse_AlterOwnerStmt(objectId, parsetree);
+			break;
+
 		default:
 			command = NULL;
 			elog(LOG, "unrecognized node type: %d",
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 03547a6..c9c5499 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1404,7 +1404,10 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterOwnerStmt:
-				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				objectId = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
+				EventTriggerStashCommand(objectId,
+										 ((AlterOwnerStmt *) parsetree)->objectType,
+										 parsetree);
 				break;
 
 			case T_GrantStmt:
-- 
1.9.1

0035-deparse-Support-ALTER-EXTENSION-UPDATE-TO.patchtext/x-diff; charset=us-asciiDownload
>From 7bf25a8b78720c14035add9402138b3693c63936 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Thu, 8 May 2014 15:35:58 +0530
Subject: [PATCH 35/36] deparse: Support ALTER EXTENSION / UPDATE TO

---
 src/backend/tcop/deparse_utility.c | 50 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index b7bb4da..44c1071 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1568,6 +1568,52 @@ deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+static char *
+deparse_AlterExtensionStmt(Oid objectId, Node *parsetree)
+{
+	AlterExtensionStmt *node = (AlterExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *stmt;
+	char	   *command;
+	char	   *version = NULL;
+	ListCell   *cell;
+
+	pg_extension = heap_open(ExtensionRelationId, AccessShareLock);
+	extTup = get_catalog_object_by_oid(pg_extension, objectId);
+	if (!HeapTupleIsValid(extTup))
+		elog(ERROR, "cache lookup failed for extension with OID %u",
+			 objectId);
+	extForm = (Form_pg_extension) GETSTRUCT(extTup);
+
+	stmt = new_objtree_VA("ALTER EXTENSION %{identity}I UPDATE%{to}s", 1,
+						  "identity", ObjTypeString,
+						  NameStr(extForm->extname));
+
+	foreach(cell, node->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(cell);
+
+		if (strcmp(opt->defname, "new_version") == 0)
+			version = defGetString(opt);
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	if (version)
+		append_string_object(stmt, "to", psprintf(" TO '%s'", version));
+	else
+		append_string_object(stmt, "to", "");
+
+	heap_close(pg_extension, AccessShareLock);
+
+	command = jsonize_objtree(stmt);
+	free_objtree(stmt);
+
+	return command;
+}
+
 /*
  * deparse_ViewStmt
  *		deparse a ViewStmt
@@ -4272,6 +4318,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_CreateExtensionStmt(objectId, parsetree);
 			break;
 
+		case T_AlterExtensionStmt:
+			command = deparse_AlterExtensionStmt(objectId, parsetree);
+			break;
+
 		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
 			command = deparse_CompositeTypeStmt(objectId, parsetree);
 			break;
-- 
1.9.1

0036-deparse-support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From 9e5c6e5ad9e5721b7f3dbafdeaff8328a49a5321 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 12 Jun 2014 18:34:53 -0400
Subject: [PATCH 36/36] deparse: support GRANT/REVOKE

---
 src/backend/catalog/aclchk.c         |  36 ++----
 src/backend/commands/event_trigger.c |  65 ++++++++++
 src/backend/tcop/deparse_utility.c   | 224 +++++++++++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |   2 +
 src/include/tcop/deparse_utility.h   |  10 +-
 src/include/utils/aclchk.h           |  45 +++++++
 6 files changed, 355 insertions(+), 27 deletions(-)
 create mode 100644 src/include/utils/aclchk.h

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d9745ca..b2e2312 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -602,6 +578,14 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	EventTriggerStashGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 74fe67c..5cca1cd 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1449,6 +1449,48 @@ EventTriggerComplexCmdEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+/*
+ * EventTriggerStashGrant
+ * 		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerStashGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... XXX should we consider
+	 * changing InternalGrant into a full-fledged node instead?
+	 */
+	icopy = palloc(sizeof(InternalGrant));
+	memcpy(icopy, istmt, sizeof(InternalGrant));
+	icopy->objects = list_copy(istmt->objects);
+	icopy->grantees = list_copy(istmt->grantees);
+	icopy->col_privs = NIL;
+	foreach(cell, istmt->col_privs)
+		icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
+
+	stashed = palloc(sizeof(StashedCommand));
+	stashed->type = SCT_Grant;
+	stashed->in_extension = currentEventTriggerState->in_extension;
+
+	stashed->d.grant.istmt = icopy;
+	stashed->parsetree = NULL;
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1613,6 +1655,29 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				/* command */
 				values[i++] = CStringGetTextDatum(command);
 			}
+			else
+			{
+				Assert(cmd->type == SCT_Grant);
+
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum("GRANT");	/* XXX maybe REVOKE or something else */
+				/* object_type */
+				values[i++] = CStringGetTextDatum("TABLE"); /* XXX maybe something else */
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
 
 			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 		}
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 44c1071..e47a2fe 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -32,14 +32,19 @@
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
+#include "catalog/pg_foreign_data_wrapper.h"
+#include "catalog/pg_foreign_server.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_language.h"
+#include "catalog/pg_largeobject.h"
+#include "catalog/pg_namespace.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
@@ -3855,6 +3860,218 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_GrantStmt(StashedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	char	   *command;
+	char	   *fmt;
+	char	   *objtype;
+	List	   *list;
+	ListCell   *cell;
+	Oid			classId;
+	ObjTree	   *tmp;
+
+	istmt = cmd->d.grant.istmt;
+
+	switch (istmt->objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+		case ACL_OBJECT_RELATION:
+			objtype = "TABLE";
+			classId = RelationRelationId;
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			objtype = "SEQUENCE";
+			classId = RelationRelationId;
+			break;
+		case ACL_OBJECT_DOMAIN:
+			objtype = "DOMAIN";
+			classId = TypeRelationId;
+			break;
+		case ACL_OBJECT_FDW:
+			objtype = "FOREIGN DATA WRAPPER";
+			classId = ForeignDataWrapperRelationId;
+			break;
+		case ACL_OBJECT_FOREIGN_SERVER:
+			objtype = "SERVER";
+			classId = ForeignServerRelationId;
+			break;
+		case ACL_OBJECT_FUNCTION:
+			objtype = "FUNCTION";
+			classId = ProcedureRelationId;
+			break;
+		case ACL_OBJECT_LANGUAGE:
+			objtype = "LANGUAGE";
+			classId = LanguageRelationId;
+			break;
+		case ACL_OBJECT_LARGEOBJECT:
+			objtype = "LARGE OBJECT";
+			classId = LargeObjectRelationId;
+			break;
+		case ACL_OBJECT_NAMESPACE:
+			objtype = "SCHEMA";
+			classId = NamespaceRelationId;
+			break;
+		case ACL_OBJECT_TYPE:
+			objtype = "TYPE";
+			classId = TypeRelationId;
+			break;
+		case ACL_OBJECT_DATABASE:
+		case ACL_OBJECT_TABLESPACE:
+			objtype = "";
+			classId = InvalidOid;
+			elog(ERROR, "global objects not supported");
+		default:
+			elog(ERROR, "invalid ACL_OBJECT value %d", istmt->objtype);
+	}
+
+	/* GRANT TO or REVOKE FROM */
+	if (istmt->is_grant)
+		fmt = psprintf("GRANT %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "TO %%{grantees:, }s %%{grant_option}s",
+					   objtype);
+	else
+		fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "FROM %%{grantees:, }s %%{cascade}s",
+					   objtype);
+
+	grantStmt = new_objtree_VA(fmt, 0);
+
+	/* build list of privileges to grant/revoke */
+	if (istmt->all_privs)
+	{
+		tmp = new_objtree_VA("ALL PRIVILEGES", 0);
+		list = list_make1(new_object_object(NULL, tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object(NULL, "INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object(NULL, "SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object(NULL, "UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object(NULL, "DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object(NULL, "TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object(NULL, "REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object(NULL, "TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object(NULL, "EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object(NULL, "USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object(NULL, "CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object(NULL, "TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object(NULL, "CONNECT"));
+
+		if (istmt->col_privs != NIL)
+		{
+			ListCell   *ocell;
+
+			foreach(ocell, istmt->col_privs)
+			{
+				AccessPriv *priv = lfirst(ocell);
+				List   *cols = NIL;
+
+				tmp = new_objtree_VA("%{priv}s (%{cols:, }I)", 0);
+				foreach(cell, priv->cols)
+				{
+					Value *colname = lfirst(cell);
+
+					cols = lappend(cols,
+								   new_string_object(NULL,
+													 strVal(colname)));
+				}
+				append_array_object(tmp, "cols", cols);
+				if (priv->priv_name == NULL)
+					append_string_object(tmp, "priv", "ALL PRIVILEGES");
+				else
+					append_string_object(tmp, "priv", priv->priv_name);
+
+				list = lappend(list, new_object_object(NULL, tmp));
+			}
+		}
+	}
+	append_array_object(grantStmt, "privileges", list);
+
+	/* target objects.  We use object identities here */
+	list = NIL;
+	foreach(cell, istmt->objects)
+	{
+		Oid		objid = lfirst_oid(cell);
+		ObjectAddress addr;
+
+		addr.classId = classId;
+		addr.objectId = objid;
+		addr.objectSubId = 0;
+
+		tmp = new_objtree_VA("%{identity}s", 0);
+		append_string_object(tmp, "identity",
+							 getObjectIdentity(&addr));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+	append_array_object(grantStmt, "privtarget", list);
+
+	/* list of grantees */
+	list = NIL;
+	foreach(cell, istmt->grantees)
+	{
+		Oid		grantee = lfirst_oid(cell);
+
+		if (grantee == ACL_ID_PUBLIC)
+			tmp = new_objtree_VA("PUBLIC", 0);
+		else
+		{
+			HeapTuple	roltup;
+			char	   *rolname;
+
+			roltup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(grantee));
+			if (!HeapTupleIsValid(roltup))
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("role with OID %u does not exist", grantee)));
+
+			tmp = new_objtree_VA("%{name}I", 0);
+			rolname = NameStr(((Form_pg_authid) GETSTRUCT(roltup))->rolname);
+			append_string_object(tmp, "name", pstrdup(rolname));
+			ReleaseSysCache(roltup);
+		}
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+	append_array_object(grantStmt, "grantees", list);
+
+	/* the wording of the grant option is variable ... */
+	if (istmt->is_grant)
+		append_string_object(grantStmt, "grant_option",
+							 istmt->grant_option ?  "WITH GRANT OPTION" : "");
+	else
+		append_string_object(grantStmt, "grant_option",
+							 istmt->grant_option ?  "GRANT OPTION FOR" : "");
+
+	if (!istmt->is_grant)
+	{
+		if (istmt->behavior == DROP_CASCADE)
+			append_string_object(grantStmt, "cascade", "CASCADE");
+		else
+			append_string_object(grantStmt, "cascade", "");
+	}
+
+	command = jsonize_objtree(grantStmt);
+	free_objtree(grantStmt);
+
+	return command;
+}
+
+static char *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -4378,6 +4595,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_AlterOwnerStmt(objectId, parsetree);
 			break;
 
+		case T_GrantStmt:
+			elog(ERROR, "unexpected node type T_GrantStmt");
+			break;
+
 		default:
 			command = NULL;
 			elog(LOG, "unrecognized node type: %d",
@@ -4433,6 +4654,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterTable:
 			command = deparse_parsenode_cmd(cmd);
 			break;
+		case SCT_Grant:
+			command = deparse_GrantStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 3e84cf7..8141244 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -17,6 +17,7 @@
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
+#include "utils/aclchk.h"
 
 typedef struct EventTriggerData
 {
@@ -56,6 +57,7 @@ extern void EventTriggerStashExtensionStart(void);
 extern void EventTriggerStashExtensionStop(void);
 extern void EventTriggerStashCommand(Oid objectId, ObjectType objtype,
 						 Node *parsetree);
+extern void EventTriggerStashGrant(InternalGrant *istmt);
 extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype);
 extern void EventTriggerComplexCmdSetOid(Oid objectId);
 extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid,
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 37b38f0..61ad728 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -14,6 +14,8 @@
 
 #include "access/attnum.h"
 #include "nodes/nodes.h"
+#include "utils/aclchk.h"
+
 
 /*
  * Support for keeping track of a command to deparse.
@@ -26,7 +28,8 @@
 typedef enum StashedCommandType
 {
 	SCT_Basic,
-	SCT_AlterTable
+	SCT_AlterTable,
+	SCT_Grant
 } StashedCommandType;
 
 /*
@@ -60,6 +63,11 @@ typedef struct StashedCommand
 			ObjectType objtype;
 			List   *subcmds;
 		} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+		} grant;
 	} d;
 } StashedCommand;
 
diff --git a/src/include/utils/aclchk.h b/src/include/utils/aclchk.h
new file mode 100644
index 0000000..1ca7095
--- /dev/null
+++ b/src/include/utils/aclchk.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_H
+#define ACLCHK_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_H */
-- 
1.9.1

0031-deparse-Support-CREATE-TYPE-via-DefineStmt.patchtext/x-diff; charset=us-asciiDownload
>From 1f6ced7d954ea533f2b0df4daea3f29b5743fd97 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 5 May 2014 16:20:40 +0530
Subject: [PATCH 31/36] deparse: Support CREATE TYPE via DefineStmt

---
 src/backend/tcop/deparse_utility.c | 210 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 209 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 9a325ac..8c2fa9e 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -915,6 +915,211 @@ deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define)
 	return stmt;
 }
 
+static ObjTree *
+deparse_DefineStmt_Type(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   typTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	char	   *str;
+	Datum		dflt;
+	bool		isnull;
+	Form_pg_type typForm;
+
+	typTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(typTup))
+		elog(ERROR, "cache lookup failed for type with OID %u", objectId);
+	typForm = (Form_pg_type) GETSTRUCT(typTup);
+
+	/* Shortcut processing for shell types. */
+	if (!typForm->typisdefined)
+	{
+		stmt = new_objtree_VA("CREATE TYPE %{identity}D", 0);
+		append_object_object(stmt, "identity",
+							 new_objtree_for_qualname(typForm->typnamespace,
+													  NameStr(typForm->typname)));
+		ReleaseSysCache(typTup);
+		return stmt;
+	}
+
+	stmt = new_objtree_VA("CREATE TYPE %{identity}D (%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(typForm->typnamespace,
+												  NameStr(typForm->typname)));
+
+	list = NIL;
+
+	/* INPUT */
+	tmp = new_objtree_VA("INPUT=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typinput));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* OUTPUT */
+	tmp = new_objtree_VA("OUTPUT=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 typForm->typoutput));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* RECEIVE */
+	if (OidIsValid(typForm->typreceive))
+	{
+		tmp = new_objtree_VA("RECEIVE=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typreceive));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* SEND */
+	if (OidIsValid(typForm->typsend))
+	{
+		tmp = new_objtree_VA("SEND=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typsend));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* TYPMOD_IN */
+	if (OidIsValid(typForm->typmodin))
+	{
+		tmp = new_objtree_VA("TYPMOD_IN=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodin));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* TYPMOD_OUT */
+	if (OidIsValid(typForm->typmodout))
+	{
+		tmp = new_objtree_VA("TYPMOD_OUT=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typmodout));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* ANALYZE */
+	if (OidIsValid(typForm->typanalyze))
+	{
+		tmp = new_objtree_VA("ANALYZE=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 typForm->typanalyze));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* INTERNALLENGTH */
+	tmp = new_objtree_VA("INTERNALLENGTH=%{typlen}s", 0);
+	if (typForm->typlen == -1)
+		append_string_object(tmp, "typlen", "VARIABLE");
+	else
+		append_string_object(tmp, "typlen",
+							 psprintf("%d", typForm->typlen));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* PASSEDBYVALUE */
+	if (typForm->typbyval)
+		list = lappend(list, new_object_object(NULL, new_objtree_VA("PASSEDBYVALUE", 0)));
+
+	/* ALIGNMENT */
+	tmp = new_objtree_VA("ALIGNMENT=%{align}s", 0);
+	switch (typForm->typalign)
+	{
+		case 'd':
+			str = "pg_catalog.float8";
+			break;
+		case 'i':
+			str = "pg_catalog.int4";
+			break;
+		case 's':
+			str = "pg_catalog.int2";
+			break;
+		case 'c':
+			str = "pg_catalog.bpchar";
+			break;
+		default:
+			elog(ERROR, "invalid alignment %c", typForm->typalign);
+	}
+	append_string_object(tmp, "align", str);
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("STORAGE=%{storage}s", 0);
+	switch (typForm->typstorage)
+	{
+		case 'p':
+			str = "plain";
+			break;
+		case 'e':
+			str = "external";
+			break;
+		case 'x':
+			str = "extended";
+			break;
+		case 'm':
+			str = "main";
+			break;
+		default:
+			elog(ERROR, "invalid storage specifier %c", typForm->typstorage);
+	}
+	append_string_object(tmp, "storage", str);
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* CATEGORY */
+	tmp = new_objtree_VA("CATEGORY=%{category}L", 0);
+	append_string_object(tmp, "category",
+						 psprintf("%c", typForm->typcategory));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* PREFERRED */
+	if (typForm->typispreferred)
+		list = lappend(list, new_object_object(NULL, new_objtree_VA("PREFERRED=true", 0)));
+
+	/* DEFAULT */
+	dflt = SysCacheGetAttr(TYPEOID, typTup,
+						   Anum_pg_type_typdefault,
+						   &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("DEFAULT=%{default}L", 0);
+		append_string_object(tmp, "default", TextDatumGetCString(dflt));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* ELEMENT */
+	if (OidIsValid(typForm->typelem))
+	{
+		tmp = new_objtree_VA("ELEMENT=%{elem}T", 0);
+		append_object_object(tmp, "elem",
+							 new_objtree_for_type(typForm->typelem, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	/* DELIMITER */
+	tmp = new_objtree_VA("DELIMITER=%{delim}L", 0);
+	append_string_object(tmp, "delim",
+						 psprintf("%c", typForm->typdelim));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	/* COLLATABLE */
+	if (OidIsValid(typForm->typcollation))
+		list = lappend(list,
+					   new_object_object(NULL,
+										 new_objtree_VA("COLLATABLE=true", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(typTup);
+
+	return stmt;
+}
+
 static char *
 deparse_DefineStmt(Oid objectId, Node *parsetree)
 {
@@ -944,9 +1149,12 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 			defStmt = deparse_DefineStmt_TSTemplate(objectId, define);
 			break;
 
+		case OBJECT_TYPE:
+			defStmt = deparse_DefineStmt_Type(objectId, define);
+			break;
+
 		default:
 		case OBJECT_AGGREGATE:
-		case OBJECT_TYPE:
 		case OBJECT_TSCONFIGURATION:
 			elog(ERROR, "unsupported object kind");
 			return NULL;
-- 
1.9.1

0001-gram.y-more-psprintf.patchtext/x-diff; charset=us-asciiDownload
>From 069e3d2d4fe62751eb021eb9aaa0afbb029c16a1 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 7 Feb 2014 16:42:37 -0300
Subject: [PATCH 01/36] gram.y: more psprintf()

---
 src/backend/parser/gram.y | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7b9895d..df9ee31 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4469,9 +4469,7 @@ TriggerFuncArgs:
 TriggerFuncArg:
 			Iconst
 				{
-					char buf[64];
-					snprintf(buf, sizeof(buf), "%d", $1);
-					$$ = makeString(pstrdup(buf));
+					$$ = makeString(psprintf("%d", $1));
 				}
 			| FCONST								{ $$ = makeString($1); }
 			| Sconst								{ $$ = makeString($1); }
-- 
1.9.1

0002-core-use-PG_FUNCNAME_MACRO-to-avoid-stale-name.patchtext/x-diff; charset=us-asciiDownload
>From 7838472617ddb1a093d86a5d31c917b748f7eac3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 25 Apr 2014 14:33:11 -0300
Subject: [PATCH 02/36] core: use PG_FUNCNAME_MACRO to avoid stale name

---
 src/backend/commands/event_trigger.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 6d4e091..1285fd0 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1217,7 +1217,7 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 		 errmsg("%s can only be called in a sql_drop event trigger function",
-				"pg_event_trigger_dropped_objects()")));
+				PG_FUNCNAME_MACRO)));
 
 	/* check to see if caller supports us returning a tuplestore */
 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
-- 
1.9.1

0003-deparse-core-return-OID-in-CREATE-TABLE-AS.patchtext/x-diff; charset=us-asciiDownload
>From 3c99d9a65e8acf9fde01684bdc042cb6dce1a83e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 7 Jan 2013 12:35:48 -0300
Subject: [PATCH 03/36] deparse/core: return OID in CREATE TABLE AS

---
 src/backend/commands/createas.c | 18 ++++++++++++++++--
 src/include/commands/createas.h |  2 +-
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 96806ee..5245171 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -55,6 +55,9 @@ typedef struct
 	BulkInsertState bistate;	/* bulk insert state */
 } DR_intorel;
 
+/* the OID of the created table, for ExecCreateTableAs consumption */
+static Oid	CreateAsRelid = InvalidOid;
+
 static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
 static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
@@ -64,7 +67,7 @@ static void intorel_destroy(DestReceiver *self);
 /*
  * ExecCreateTableAs -- execute a CREATE TABLE AS command
  */
-void
+Oid
 ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag)
 {
@@ -75,6 +78,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 	Oid			save_userid = InvalidOid;
 	int			save_sec_context = 0;
 	int			save_nestlevel = 0;
+	Oid			relOid;
 	List	   *rewritten;
 	PlannedStmt *plan;
 	QueryDesc  *queryDesc;
@@ -98,7 +102,9 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		Assert(!is_matview);	/* excluded by syntax */
 		ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
 
-		return;
+		relOid = CreateAsRelid;
+		CreateAsRelid = InvalidOid;
+		return relOid;
 	}
 	Assert(query->commandType == CMD_SELECT);
 
@@ -190,6 +196,11 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 		/* Restore userid and security context */
 		SetUserIdAndSecContext(save_userid, save_sec_context);
 	}
+
+	relOid = CreateAsRelid;
+	CreateAsRelid = InvalidOid;
+
+	return relOid;
 }
 
 /*
@@ -421,6 +432,9 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	myState->rel = intoRelationDesc;
 	myState->output_cid = GetCurrentCommandId(true);
 
+	/* and remember the new relation's OID for ExecCreateTableAs */
+	CreateAsRelid = RelationGetRelid(myState->rel);
+
 	/*
 	 * We can skip WAL-logging the insertions, unless PITR or streaming
 	 * replication is in use. We can skip the FSM in any case.
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
index c17d829..477339d 100644
--- a/src/include/commands/createas.h
+++ b/src/include/commands/createas.h
@@ -19,7 +19,7 @@
 #include "tcop/dest.h"
 
 
-extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+extern Oid	ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 				  ParamListInfo params, char *completionTag);
 
 extern int	GetIntoRelEFlags(IntoClause *intoClause);
-- 
1.9.1

0004-deparse-core-return-OID-of-matview-in-REFRESH.patchtext/x-diff; charset=us-asciiDownload
>From 16d9a659b9bdf29f1fead13d99085697ba83c6f3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 7 Nov 2013 09:46:11 -0300
Subject: [PATCH 04/36] deparse/core: return OID of matview in REFRESH

For future consumption by event triggers
---
 src/backend/commands/matview.c | 4 +++-
 src/include/commands/matview.h | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 5130d51..1e26fa9 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -132,7 +132,7 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
  * The matview's "populated" state is changed based on whether the contents
  * reflect the result set of the materialized view's query.
  */
-void
+Oid
 ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag)
 {
@@ -274,6 +274,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 	}
 	else
 		refresh_by_heap_swap(matviewOid, OIDNewHeap);
+
+	return matviewOid;
 }
 
 /*
diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h
index 476b285..2c468b2 100644
--- a/src/include/commands/matview.h
+++ b/src/include/commands/matview.h
@@ -22,7 +22,7 @@
 
 extern void SetMatViewPopulatedState(Relation relation, bool newstate);
 
-extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
+extern Oid ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 				   ParamListInfo params, char *completionTag);
 
 extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
-- 
1.9.1

0005-deparse-core-split-builtins.h-to-new-ruleutils.h.patchtext/x-diff; charset=us-asciiDownload
>From 396ed63d4b08eac863c3ae379ab288e0e6e378e3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 11 Feb 2014 15:58:56 -0300
Subject: [PATCH 05/36] deparse/core: split builtins.h to new ruleutils.h

This contains many prototypes for functions in ruleutils.c that are not
exposed to the SQL level.
---
 src/backend/access/index/genam.c  |  1 +
 src/backend/catalog/heap.c        |  1 +
 src/backend/commands/explain.c    |  1 +
 src/backend/commands/tablecmds.c  |  1 +
 src/backend/commands/typecmds.c   |  1 +
 src/backend/utils/adt/misc.c      |  1 +
 src/backend/utils/adt/ruleutils.c |  1 +
 src/include/utils/builtins.h      | 11 -----------
 src/include/utils/ruleutils.h     | 34 ++++++++++++++++++++++++++++++++++
 9 files changed, 41 insertions(+), 11 deletions(-)
 create mode 100644 src/include/utils/ruleutils.h

diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 850008b..8849c08 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -28,6 +28,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tqual.h"
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 33eef9f..3cf616c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -69,6 +69,7 @@
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0d9663c..6411c5a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -28,6 +28,7 @@
 #include "utils/json.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tuplesort.h"
 #include "utils/xml.h"
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 341262b..062f3e6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -83,6 +83,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/relcache.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index f377c19..4606b9b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -72,6 +72,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 4eeb631..67539ec 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -35,6 +35,7 @@
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a30d8fe..9744b91 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -55,6 +55,7 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index bbb5d39..26f998d 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -675,13 +675,10 @@ extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_indexdef_string(Oid indexrelid);
-extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
 extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
-extern char *pg_get_constraintdef_string(Oid constraintId);
 extern Datum pg_get_expr(PG_FUNCTION_ARGS);
 extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
@@ -691,17 +688,9 @@ extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
 extern Datum pg_get_function_arg_default(PG_FUNCTION_ARGS);
-extern char *deparse_expression(Node *expr, List *dpcontext,
-				   bool forceprefix, bool showimplicit);
-extern List *deparse_context_for(const char *aliasname, Oid relid);
-extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
-							  List *rtable, List *rtable_names);
-extern List *select_rtable_names_for_explain(List *rtable,
-								Bitmapset *rels_used);
 extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 						   const char *ident);
-extern char *generate_collation_name(Oid collid);
 
 
 /* tid.c */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
new file mode 100644
index 0000000..520b066
--- /dev/null
+++ b/src/include/utils/ruleutils.h
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * ruleutils.h
+ *		Declarations for ruleutils.c
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/ruleutils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RULEUTILS_H
+#define RULEUTILS_H
+
+#include "nodes/nodes.h"
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+
+extern char *pg_get_indexdef_string(Oid indexrelid);
+extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
+
+extern char *pg_get_constraintdef_string(Oid constraintId);
+extern char *deparse_expression(Node *expr, List *dpcontext,
+				   bool forceprefix, bool showimplicit);
+extern List *deparse_context_for(const char *aliasname, Oid relid);
+extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
+							  List *rtable, List *rtable_names);
+extern List *select_rtable_names_for_explain(List *rtable,
+								Bitmapset *rels_used);
+extern char *generate_collation_name(Oid collid);
+
+#endif	/* RULEUTILS_H */
-- 
1.9.1

0006-deparse-core-return-the-base-type-OID-not-the-array-.patchtext/x-diff; charset=us-asciiDownload
>From e5ac47ac81310e4c9de15c4efe6a0c9180b75385 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 5 May 2014 17:49:54 -0400
Subject: [PATCH 06/36] deparse/core: return the base type OID, not the array's

DefineType should return the OID of the base type just created, not the
subsidiary array's.
---
 src/backend/commands/typecmds.c | 62 ++++++++++++++++++++---------------------
 1 file changed, 31 insertions(+), 31 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 4606b9b..6c9f06d 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -597,37 +597,37 @@ DefineType(List *names, List *parameters)
 	/* alignment must be 'i' or 'd' for arrays */
 	alignment = (alignment == 'd') ? 'd' : 'i';
 
-	typoid = TypeCreate(array_oid,		/* force assignment of this type OID */
-						array_type,		/* type name */
-						typeNamespace,	/* namespace */
-						InvalidOid,		/* relation oid (n/a here) */
-						0,		/* relation kind (ditto) */
-						GetUserId(),	/* owner's ID */
-						-1,		/* internal size (always varlena) */
-						TYPTYPE_BASE,	/* type-type (base type) */
-						TYPCATEGORY_ARRAY,		/* type-category (array) */
-						false,	/* array types are never preferred */
-						delimiter,		/* array element delimiter */
-						F_ARRAY_IN,		/* input procedure */
-						F_ARRAY_OUT,	/* output procedure */
-						F_ARRAY_RECV,	/* receive procedure */
-						F_ARRAY_SEND,	/* send procedure */
-						typmodinOid,	/* typmodin procedure */
-						typmodoutOid,	/* typmodout procedure */
-						F_ARRAY_TYPANALYZE,		/* analyze procedure */
-						typoid, /* element type ID */
-						true,	/* yes this is an array type */
-						InvalidOid,		/* no further array type */
-						InvalidOid,		/* base type ID */
-						NULL,	/* never a default type value */
-						NULL,	/* binary default isn't sent either */
-						false,	/* never passed by value */
-						alignment,		/* see above */
-						'x',	/* ARRAY is always toastable */
-						-1,		/* typMod (Domains only) */
-						0,		/* Array dimensions of typbasetype */
-						false,	/* Type NOT NULL */
-						collation);		/* type's collation */
+	TypeCreate(array_oid,		/* force assignment of this type OID */
+			   array_type,		/* type name */
+			   typeNamespace,	/* namespace */
+			   InvalidOid,		/* relation oid (n/a here) */
+			   0,		/* relation kind (ditto) */
+			   GetUserId(),	/* owner's ID */
+			   -1,		/* internal size (always varlena) */
+			   TYPTYPE_BASE,	/* type-type (base type) */
+			   TYPCATEGORY_ARRAY,		/* type-category (array) */
+			   false,	/* array types are never preferred */
+			   delimiter,		/* array element delimiter */
+			   F_ARRAY_IN,		/* input procedure */
+			   F_ARRAY_OUT,	/* output procedure */
+			   F_ARRAY_RECV,	/* receive procedure */
+			   F_ARRAY_SEND,	/* send procedure */
+			   typmodinOid,	/* typmodin procedure */
+			   typmodoutOid,	/* typmodout procedure */
+			   F_ARRAY_TYPANALYZE,		/* analyze procedure */
+			   typoid, /* element type ID */
+			   true,	/* yes this is an array type */
+			   InvalidOid,		/* no further array type */
+			   InvalidOid,		/* base type ID */
+			   NULL,	/* never a default type value */
+			   NULL,	/* binary default isn't sent either */
+			   false,	/* never passed by value */
+			   alignment,		/* see above */
+			   'x',	/* ARRAY is always toastable */
+			   -1,		/* typMod (Domains only) */
+			   0,		/* Array dimensions of typbasetype */
+			   false,	/* Type NOT NULL */
+			   collation);		/* type's collation */
 
 	pfree(array_type);
 
-- 
1.9.1

0008-deparse-core-have-ALTER-TABLE-return-OIDs-and-col-of.patchtext/x-diff; charset=us-asciiDownload
>From 4b475ce7b7ef8e8e972e22a5453be3fd12494abe Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 25 Apr 2014 17:15:49 -0300
Subject: [PATCH 08/36] deparse/core: have ALTER TABLE return OIDs and col# of
 affected objs

---
 src/backend/catalog/heap.c          |  40 ++++--
 src/backend/catalog/index.c         |   7 +-
 src/backend/catalog/pg_constraint.c |   2 +
 src/backend/commands/tablecmds.c    | 270 ++++++++++++++++++++++++------------
 src/include/catalog/heap.h          |   3 +-
 src/include/catalog/index.h         |   2 +-
 6 files changed, 216 insertions(+), 108 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3cf616c..96e3ee2 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -96,7 +96,7 @@ static Oid AddNewRelationType(const char *typeName,
 				   Oid new_row_type,
 				   Oid new_array_type);
 static void RelationRemoveInheritance(Oid relid);
-static void StoreRelCheck(Relation rel, char *ccname, Node *expr,
+static Oid StoreRelCheck(Relation rel, char *ccname, Node *expr,
 			  bool is_validated, bool is_local, int inhcount,
 			  bool is_no_inherit, bool is_internal);
 static void StoreConstraints(Relation rel, List *cooked_constraints,
@@ -1840,7 +1840,7 @@ heap_drop_with_catalog(Oid relid)
 /*
  * Store a default expression for column attnum of relation rel.
  */
-void
+Oid
 StoreAttrDefault(Relation rel, AttrNumber attnum,
 				 Node *expr, bool is_internal)
 {
@@ -1945,6 +1945,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	 */
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);
+
+	return attrdefOid;
 }
 
 /*
@@ -1952,8 +1954,10 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
  *
  * Caller is responsible for updating the count of constraints
  * in the pg_class entry for the relation.
+ *
+ * The OID of the new constraint is returned.
  */
-static void
+static Oid
 StoreRelCheck(Relation rel, char *ccname, Node *expr,
 			  bool is_validated, bool is_local, int inhcount,
 			  bool is_no_inherit, bool is_internal)
@@ -1963,6 +1967,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
+	Oid			constrOid;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2014,7 +2019,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 	/*
 	 * Create the Check Constraint
 	 */
-	CreateConstraintEntry(ccname,		/* Constraint Name */
+	constrOid = CreateConstraintEntry(ccname,		/* Constraint Name */
 						  RelationGetNamespace(rel),	/* namespace */
 						  CONSTRAINT_CHECK,		/* Constraint Type */
 						  false,	/* Is Deferrable */
@@ -2045,11 +2050,15 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
 
 	pfree(ccbin);
 	pfree(ccsrc);
+
+	return constrOid;
 }
 
 /*
  * Store defaults and constraints (passed as a list of CookedConstraint).
  *
+ * Each CookedConstraint struct is modified to store the new catalog tuple OID.
+ *
  * NOTE: only pre-cooked expressions will be passed this way, which is to
  * say expressions inherited from an existing relation.  Newly parsed
  * expressions can be added later, by direct calls to StoreAttrDefault
@@ -2061,7 +2070,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 	int			numchecks = 0;
 	ListCell   *lc;
 
-	if (!cooked_constraints)
+	if (list_length(cooked_constraints) == 0)
 		return;					/* nothing to do */
 
 	/*
@@ -2078,12 +2087,14 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 		switch (con->contype)
 		{
 			case CONSTR_DEFAULT:
-				StoreAttrDefault(rel, con->attnum, con->expr, is_internal);
+				con->conoid = StoreAttrDefault(rel, con->attnum, con->expr,
+											   is_internal);
 				break;
 			case CONSTR_CHECK:
-				StoreRelCheck(rel, con->name, con->expr, !con->skip_validation,
-							  con->is_local, con->inhcount,
-							  con->is_no_inherit, is_internal);
+				con->conoid =
+					StoreRelCheck(rel, con->name, con->expr,
+								  !con->skip_validation, con->is_local, con->inhcount,
+								  con->is_no_inherit, is_internal);
 				numchecks++;
 				break;
 			default:
@@ -2171,6 +2182,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		RawColumnDefault *colDef = (RawColumnDefault *) lfirst(cell);
 		Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1];
+		Oid		defOid;
 
 		expr = cookDefault(pstate, colDef->raw_default,
 						   atp->atttypid, atp->atttypmod,
@@ -2191,10 +2203,11 @@ AddRelationNewConstraints(Relation rel,
 			(IsA(expr, Const) &&((Const *) expr)->constisnull))
 			continue;
 
-		StoreAttrDefault(rel, colDef->attnum, expr, is_internal);
+		defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal);
 
 		cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
 		cooked->contype = CONSTR_DEFAULT;
+		cooked->conoid = defOid;
 		cooked->name = NULL;
 		cooked->attnum = colDef->attnum;
 		cooked->expr = expr;
@@ -2214,6 +2227,7 @@ AddRelationNewConstraints(Relation rel,
 	{
 		Constraint *cdef = (Constraint *) lfirst(cell);
 		char	   *ccname;
+		Oid			constrOid;
 
 		if (cdef->contype != CONSTR_CHECK)
 			continue;
@@ -2316,13 +2330,15 @@ AddRelationNewConstraints(Relation rel,
 		/*
 		 * OK, store it.
 		 */
-		StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, is_local,
-					  is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
+		constrOid =
+			StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, is_local,
+						  is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
 
 		numchecks++;
 
 		cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
 		cooked->contype = CONSTR_CHECK;
+		cooked->conoid = constrOid;
 		cooked->name = ccname;
 		cooked->attnum = 0;
 		cooked->expr = expr;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a5a204e..e95d772 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1095,7 +1095,8 @@ index_create(Relation heapRelation,
 /*
  * index_constraint_create
  *
- * Set up a constraint associated with an index
+ * Set up a constraint associated with an index.  Return the new constraint's
+ * OID.
  *
  * heapRelation: table owning the index (must be suitably locked by caller)
  * indexRelationId: OID of the index
@@ -1112,7 +1113,7 @@ index_create(Relation heapRelation,
  * allow_system_table_mods: allow table to be a system catalog
  * is_internal: index is constructed due to internal process
  */
-void
+Oid
 index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
@@ -1300,6 +1301,8 @@ index_constraint_create(Relation heapRelation,
 		heap_freetuple(indexTuple);
 		heap_close(pg_index, RowExclusiveLock);
 	}
+
+	return conOid;
 }
 
 /*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 041f5ad..f04de42 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -40,6 +40,8 @@
  * Subsidiary records (such as triggers or indexes to implement the
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
+ *
+ * The new constraint's OID is returned.
  */
 Oid
 CreateConstraintEntry(const char *constraintName,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 062f3e6..c5e5c82 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/event_trigger.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -276,9 +277,9 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel,
 static void AlterSeqNamespaces(Relation classRel, Relation rel,
 				   Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
 				   LOCKMODE lockmode);
-static void ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
+static Oid ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 					  bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATExecValidateConstraint(Relation rel, char *constrName,
+static Oid ATExecValidateConstraint(Relation rel, char *constrName,
 						 bool recurse, bool recursing, LOCKMODE lockmode);
 static int transformColumnNameList(Oid relId, List *colList,
 						int16 *attnums, Oid *atttypids);
@@ -318,26 +319,26 @@ static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
 							  DropBehavior behavior);
 static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 				AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
-				ColumnDef *colDef, bool isOid,
+static AttrNumber ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
+				Relation rel, ColumnDef *colDef, bool isOid,
 				bool recurse, bool recursing, LOCKMODE lockmode);
 static void check_for_column_name_collision(Relation rel, const char *colname);
 static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
 static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
 static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
 			  AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
-static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
+static AttrNumber ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static AttrNumber ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode);
-static void ATExecColumnDefault(Relation rel, const char *colName,
+static AttrNumber ATExecColumnDefault(Relation rel, const char *colName,
 					Node *newDefault, LOCKMODE lockmode);
 static void ATPrepSetStatistics(Relation rel, const char *colName,
 					Node *newValue, LOCKMODE lockmode);
-static void ATExecSetStatistics(Relation rel, const char *colName,
+static AttrNumber ATExecSetStatistics(Relation rel, const char *colName,
 					Node *newValue, LOCKMODE lockmode);
-static void ATExecSetOptions(Relation rel, const char *colName,
+static AttrNumber ATExecSetOptions(Relation rel, const char *colName,
 				 Node *options, bool isReset, LOCKMODE lockmode);
-static void ATExecSetStorage(Relation rel, const char *colName,
+static AttrNumber ATExecSetStorage(Relation rel, const char *colName,
 				 Node *newValue, LOCKMODE lockmode);
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 				 AlterTableCmd *cmd, LOCKMODE lockmode);
@@ -345,20 +346,20 @@ static void ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode);
-static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
+static Oid ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
-static void ATExecAddConstraint(List **wqueue,
+static Oid ATExecAddConstraint(List **wqueue,
 					AlteredTableInfo *tab, Relation rel,
 					Constraint *newConstraint, bool recurse, bool is_readd,
 					LOCKMODE lockmode);
-static void ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
+static Oid ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 						 IndexStmt *stmt, LOCKMODE lockmode);
-static void ATAddCheckConstraint(List **wqueue,
+static Oid ATAddCheckConstraint(List **wqueue,
 					 AlteredTableInfo *tab, Relation rel,
 					 Constraint *constr,
 					 bool recurse, bool recursing, bool is_readd,
 					 LOCKMODE lockmode);
-static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
+static Oid ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 						  Constraint *fkconstraint, LOCKMODE lockmode);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 					 DropBehavior behavior,
@@ -369,9 +370,9 @@ static void ATPrepAlterColumnType(List **wqueue,
 					  bool recurse, bool recursing,
 					  AlterTableCmd *cmd, LOCKMODE lockmode);
 static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
-static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
+static AttrNumber ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
+static AttrNumber ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
 								List *options, LOCKMODE lockmode);
 static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode);
 static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
@@ -383,7 +384,7 @@ static void change_owner_fix_column_acls(Oid relationOid,
 							 Oid oldOwnerId, Oid newOwnerId);
 static void change_owner_recurse_to_sequences(Oid relationOid,
 								  Oid newOwnerId, LOCKMODE lockmode);
-static void ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode);
+static Oid ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode);
 static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
 static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
 					char *tablespacename, LOCKMODE lockmode);
@@ -609,6 +610,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 
 			cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
 			cooked->contype = CONSTR_DEFAULT;
+			cooked->conoid = InvalidOid;
 			cooked->name = NULL;
 			cooked->attnum = attnum;
 			cooked->expr = colDef->cooked_default;
@@ -1720,6 +1722,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 					cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
 					cooked->contype = CONSTR_CHECK;
+					cooked->conoid = InvalidOid;
 					cooked->name = pstrdup(name);
 					cooked->attnum = 0; /* not used for constraints */
 					cooked->expr = expr;
@@ -3330,78 +3333,91 @@ static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
+	AttrNumber colno = InvalidAttrNumber;
+	Oid			newoid = InvalidOid;
+
 	switch (cmd->subtype)
 	{
 		case AT_AddColumn:		/* ADD COLUMN */
 		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
 										 * VIEW */
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, false, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, false, false, lockmode);
 			break;
 		case AT_AddColumnRecurse:
-			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
-							false, true, false, lockmode);
+			colno = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+									false, true, false, lockmode);
 			break;
 		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
-			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
-			ATExecDropNotNull(rel, cmd->name, lockmode);
+			colno = ATExecDropNotNull(rel, cmd->name, lockmode);
 			break;
 		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
-			ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+			colno = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
 			break;
 		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
-			ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
 			break;
 		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
-			ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
+			colno = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
 			break;
 		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
-			ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
+			colno = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					 cmd->behavior, false, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, false, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_DropColumnRecurse:		/* DROP COLUMN with recursion */
 			ATExecDropColumn(wqueue, rel, cmd->name,
-					  cmd->behavior, true, false, cmd->missing_ok, lockmode);
+							 cmd->behavior, true, false,
+							 cmd->missing_ok, lockmode);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
+									lockmode);
 			break;
 		case AT_ReAddIndex:		/* ADD INDEX */
-			ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode);
+			newoid = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
+									lockmode);
 			break;
 		case AT_AddConstraint:	/* ADD CONSTRAINT */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, false, lockmode);
 			break;
 		case AT_AddConstraintRecurse:	/* ADD CONSTRAINT with recursion */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								true, false, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									true, false, lockmode);
 			break;
 		case AT_ReAddConstraint:		/* Re-add pre-existing check
 										 * constraint */
-			ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-								false, true, lockmode);
+			newoid =
+				ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+									false, true, lockmode);
 			break;
 		case AT_AddIndexConstraint:		/* ADD CONSTRAINT USING INDEX */
-			ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
+			newoid = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
+											  lockmode);
 			break;
 		case AT_AlterConstraint:		/* ALTER CONSTRAINT */
-			ATExecAlterConstraint(rel, cmd, false, false, lockmode);
+			newoid = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
 			break;
 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
-			ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, false, false,
+											  lockmode);
 			break;
 		case AT_ValidateConstraintRecurse:		/* VALIDATE CONSTRAINT with
 												 * recursion */
-			ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
+			newoid = ATExecValidateConstraint(rel, cmd->name, true, false,
+											  lockmode);
 			break;
 		case AT_DropConstraint:	/* DROP CONSTRAINT */
 			ATExecDropConstraint(rel, cmd->name, cmd->behavior,
@@ -3414,10 +3430,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 								 cmd->missing_ok, lockmode);
 			break;
 		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
-			ATExecAlterColumnType(tab, rel, cmd, lockmode);
+			colno = ATExecAlterColumnType(tab, rel, cmd, lockmode);
 			break;
 		case AT_AlterColumnGenericOptions:		/* ALTER COLUMN OPTIONS */
-			ATExecAlterColumnGenericOptions(rel, cmd->name, (List *) cmd->def, lockmode);
+			colno =
+				ATExecAlterColumnGenericOptions(rel, cmd->name,
+												(List *) cmd->def, lockmode);
 			break;
 		case AT_ChangeOwner:	/* ALTER OWNER */
 			ATExecChangeOwner(RelationGetRelid(rel),
@@ -3425,7 +3443,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							  false, lockmode);
 			break;
 		case AT_ClusterOn:		/* CLUSTER ON */
-			ATExecClusterOn(rel, cmd->name, lockmode);
+			newoid = ATExecClusterOn(rel, cmd->name, lockmode);
 			break;
 		case AT_DropCluster:	/* SET WITHOUT CLUSTER */
 			ATExecDropCluster(rel, lockmode);
@@ -3523,7 +3541,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			ATExecDropOf(rel, lockmode);
 			break;
 		case AT_ReplicaIdentity:
-			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
+			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def,
+								  lockmode);
 			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
@@ -4470,7 +4489,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 		cmd->subtype = AT_AddColumnRecurse;
 }
 
-static void
+static AttrNumber
 ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				ColumnDef *colDef, bool isOid,
 				bool recurse, bool recursing, LOCKMODE lockmode)
@@ -4555,7 +4574,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					  colDef->colname, RelationGetRelationName(rel))));
 
 			heap_close(attrdesc, RowExclusiveLock);
-			return;
+			return InvalidAttrNumber;
 		}
 	}
 
@@ -4801,6 +4820,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return newattnum;
 }
 
 /*
@@ -4915,7 +4936,7 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
 /*
  * ALTER TABLE ALTER COLUMN DROP NOT NULL
  */
-static void
+static AttrNumber
 ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
@@ -5000,18 +5021,22 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
 		/* keep the system catalog indexes current */
 		CatalogUpdateIndexes(attr_rel, tuple);
+
+		/* XXX should we return InvalidAttrNumber if there was no change? */
 	}
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel), attnum);
 
 	heap_close(attr_rel, RowExclusiveLock);
+
+	return attnum;
 }
 
 /*
  * ALTER TABLE ALTER COLUMN SET NOT NULL
  */
-static void
+static AttrNumber
 ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 				 const char *colName, LOCKMODE lockmode)
 {
@@ -5055,18 +5080,22 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
 
 		/* Tell Phase 3 it needs to test the constraint */
 		tab->new_notnull = true;
+
+		/* XXX should we return InvalidAttrNumber if there was no change? */
 	}
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel), attnum);
 
 	heap_close(attr_rel, RowExclusiveLock);
+
+	return attnum;
 }
 
 /*
  * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
  */
-static void
+static AttrNumber
 ATExecColumnDefault(Relation rel, const char *colName,
 					Node *newDefault, LOCKMODE lockmode)
 {
@@ -5117,6 +5146,8 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
 								  false, true, false);
 	}
+
+	return attnum;
 }
 
 /*
@@ -5146,13 +5177,14 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 					   RelationGetRelationName(rel));
 }
 
-static void
+static AttrNumber
 ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
 {
 	int			newtarget;
 	Relation	attrelation;
 	HeapTuple	tuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 
 	Assert(IsA(newValue, Integer));
 	newtarget = intVal(newValue);
@@ -5187,7 +5219,8 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 						colName, RelationGetRelationName(rel))));
 	attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
 
-	if (attrtuple->attnum <= 0)
+	attnum = attrtuple->attnum;
+	if (attnum <= 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"",
@@ -5206,9 +5239,11 @@ ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 bool isReset, LOCKMODE lockmode)
 {
@@ -5216,6 +5251,7 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	HeapTuple	tuple,
 				newtuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 	Datum		datum,
 				newOptions;
 	bool		isnull;
@@ -5234,7 +5270,8 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 						colName, RelationGetRelationName(rel))));
 	attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
 
-	if (attrtuple->attnum <= 0)
+	attnum = attrtuple->attnum;
+	if (attnum <= 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"",
@@ -5273,12 +5310,14 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 	ReleaseSysCache(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 /*
  * ALTER TABLE ALTER COLUMN SET STORAGE
  */
-static void
+static AttrNumber
 ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
 {
 	char	   *storagemode;
@@ -5286,6 +5325,7 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	Relation	attrelation;
 	HeapTuple	tuple;
 	Form_pg_attribute attrtuple;
+	AttrNumber	attnum;
 
 	Assert(IsA(newValue, String));
 	storagemode = strVal(newValue);
@@ -5318,7 +5358,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 						colName, RelationGetRelationName(rel))));
 	attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
 
-	if (attrtuple->attnum <= 0)
+	attnum = attrtuple->attnum;
+	if (attnum <= 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"",
@@ -5348,6 +5389,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	heap_freetuple(tuple);
 
 	heap_close(attrelation, RowExclusiveLock);
+
+	return attnum;
 }
 
 
@@ -5573,7 +5616,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
  */
-static void
+static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 			   IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
 {
@@ -5615,12 +5658,14 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 		RelationPreserveStorage(irel->rd_node, true);
 		index_close(irel, NoLock);
 	}
+
+	return new_index;
 }
 
 /*
  * ALTER TABLE ADD CONSTRAINT USING INDEX
  */
-static void
+static Oid
 ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 						 IndexStmt *stmt, LOCKMODE lockmode)
 {
@@ -5630,6 +5675,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 	IndexInfo  *indexInfo;
 	char	   *constraintName;
 	char		constraintType;
+	Oid			conOid;
 
 	Assert(IsA(stmt, IndexStmt));
 	Assert(OidIsValid(index_oid));
@@ -5674,30 +5720,34 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
 		constraintType = CONSTRAINT_UNIQUE;
 
 	/* Create the catalog entries for the constraint */
-	index_constraint_create(rel,
-							index_oid,
-							indexInfo,
-							constraintName,
-							constraintType,
-							stmt->deferrable,
-							stmt->initdeferred,
-							stmt->primary,
-							true,		/* update pg_index */
-							true,		/* remove old dependencies */
-							allowSystemTableMods,
-							false);		/* is_internal */
+	conOid = index_constraint_create(rel,
+									 index_oid,
+									 indexInfo,
+									 constraintName,
+									 constraintType,
+									 stmt->deferrable,
+									 stmt->initdeferred,
+									 stmt->primary,
+									 true,		/* update pg_index */
+									 true,		/* remove old dependencies */
+									 allowSystemTableMods,
+									 false);		/* is_internal */
 
 	index_close(indexRel, NoLock);
+
+	return conOid;
 }
 
 /*
  * ALTER TABLE ADD CONSTRAINT
  */
-static void
+static Oid
 ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					Constraint *newConstraint, bool recurse, bool is_readd,
 					LOCKMODE lockmode)
 {
+	Oid		constrOid = InvalidOid;
+
 	Assert(IsA(newConstraint, Constraint));
 
 	/*
@@ -5708,9 +5758,10 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	switch (newConstraint->contype)
 	{
 		case CONSTR_CHECK:
-			ATAddCheckConstraint(wqueue, tab, rel,
-								 newConstraint, recurse, false, is_readd,
-								 lockmode);
+			constrOid =
+				ATAddCheckConstraint(wqueue, tab, rel,
+									 newConstraint, recurse, false, is_readd,
+									 lockmode);
 			break;
 
 		case CONSTR_FOREIGN:
@@ -5741,17 +5792,22 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 										 RelationGetNamespace(rel),
 										 NIL);
 
-			ATAddForeignKeyConstraint(tab, rel, newConstraint, lockmode);
+			constrOid = ATAddForeignKeyConstraint(tab, rel, newConstraint,
+												  lockmode);
 			break;
 
 		default:
 			elog(ERROR, "unrecognized constraint type: %d",
 				 (int) newConstraint->contype);
 	}
+
+	return constrOid;
 }
 
 /*
- * Add a check constraint to a single table and its children
+ * Add a check constraint to a single table and its children.  Returns the
+ * OID of the constraint added to the parent relation, if one gets added,
+ * or InvalidOid otherwise.
  *
  * Subroutine for ATExecAddConstraint.
  *
@@ -5770,7 +5826,7 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  * "is_readd" flag for that; just setting recurse=false would result in an
  * error if there are child tables.
  */
-static void
+static Oid
 ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 					 Constraint *constr, bool recurse, bool recursing,
 					 bool is_readd, LOCKMODE lockmode)
@@ -5779,6 +5835,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	ListCell   *lcon;
 	List	   *children;
 	ListCell   *child;
+	Oid			constrOid = InvalidOid;
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -5821,6 +5878,13 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		/* Save the actually assigned name if it was defaulted */
 		if (constr->conname == NULL)
 			constr->conname = ccon->name;
+
+		/*
+		 * Save our return value. Note we don't expect more than one element in
+		 * this list.
+		 */
+		Assert(constrOid == InvalidOid);
+		constrOid = ccon->conoid;
 	}
 
 	/* At this point we must have a locked-down name to use */
@@ -5836,7 +5900,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * incorrect value for coninhcount.
 	 */
 	if (newcons == NIL)
-		return;
+		return InvalidOid;
 
 	/*
 	 * If adding a NO INHERIT constraint, no need to find our children.
@@ -5844,7 +5908,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * handled at higher levels).
 	 */
 	if (constr->is_no_inherit || is_readd)
-		return;
+		return constrOid;
 
 	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
@@ -5882,16 +5946,19 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 		heap_close(childrel, NoLock);
 	}
+
+	return constrOid;
 }
 
 /*
- * Add a foreign-key constraint to a single table
+ * Add a foreign-key constraint to a single table; return the new constraint's
+ * OID.
  *
  * Subroutine for ATExecAddConstraint.  Must already hold exclusive
  * lock on the rel, and have done appropriate validity checks for it.
  * We do permissions checks here, however.
  */
-static void
+static Oid
 ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 						  Constraint *fkconstraint, LOCKMODE lockmode)
 {
@@ -6290,6 +6357,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 * Close pk table, but keep lock until we've committed.
 	 */
 	heap_close(pkrel, NoLock);
+
+	return constrOid;
 }
 
 /*
@@ -6302,7 +6371,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  * recursion bit here, but we keep the API the same for when
  * other constraint types are supported.
  */
-static void
+static Oid
 ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 					  bool recurse, bool recursing, LOCKMODE lockmode)
 {
@@ -6313,6 +6382,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	Form_pg_constraint currcon = NULL;
 	Constraint *cmdcon = NULL;
 	bool		found = false;
+	Oid			constrOid = InvalidOid;
 
 	Assert(IsA(cmd->def, Constraint));
 	cmdcon = (Constraint *) cmd->def;
@@ -6372,6 +6442,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 		simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
 		CatalogUpdateIndexes(conrel, copyTuple);
 
+		constrOid = HeapTupleGetOid(contuple);
+
 		InvokeObjectPostAlterHook(ConstraintRelationId,
 								  HeapTupleGetOid(contuple), 0);
 
@@ -6419,6 +6491,8 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 /*
@@ -6429,7 +6503,7 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
  * no need to lock children in that case, yet we wouldn't be able to avoid
  * doing so at that level.
  */
-static void
+static Oid
 ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 						 bool recursing, LOCKMODE lockmode)
 {
@@ -6439,6 +6513,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	HeapTuple	tuple;
 	Form_pg_constraint con = NULL;
 	bool		found = false;
+	Oid			constrOid = InvalidOid;
 
 	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -6480,9 +6555,10 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 		HeapTuple	copyTuple;
 		Form_pg_constraint copy_con;
 
+		constrOid = HeapTupleGetOid(tuple);
+
 		if (con->contype == CONSTRAINT_FOREIGN)
 		{
-			Oid			conid = HeapTupleGetOid(tuple);
 			Relation	refrel;
 
 			/*
@@ -6497,7 +6573,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 conid);
+										 constrOid);
 			heap_close(refrel, NoLock);
 
 			/*
@@ -6578,6 +6654,8 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 	systable_endscan(scan);
 
 	heap_close(conrel, RowExclusiveLock);
+
+	return constrOid;
 }
 
 
@@ -7657,7 +7735,7 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
 	}
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					  AlterTableCmd *cmd, LOCKMODE lockmode)
 {
@@ -8033,9 +8111,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
 	/* Cleanup */
 	heap_freetuple(heapTup);
+
+	return attnum;
 }
 
-static void
+static AttrNumber
 ATExecAlterColumnGenericOptions(Relation rel,
 								const char *colName,
 								List *options,
@@ -8054,9 +8134,10 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	Datum		datum;
 	Form_pg_foreign_table fttableform;
 	Form_pg_attribute atttableform;
+	AttrNumber	attnum;
 
 	if (options == NIL)
-		return;
+		return InvalidAttrNumber;
 
 	/* First, determine FDW validator associated to the foreign table. */
 	ftrel = heap_open(ForeignTableRelationId, AccessShareLock);
@@ -8083,7 +8164,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 
 	/* Prevent them from altering a system attribute */
 	atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
-	if (atttableform->attnum <= 0)
+	attnum = atttableform->attnum;
+	if (attnum <= 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
@@ -8132,6 +8214,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 	heap_close(attrel, RowExclusiveLock);
 
 	heap_freetuple(newtuple);
+
+	return attnum;
 }
 
 /*
@@ -8773,7 +8857,7 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lock
  *
  * The only thing we have to do is to change the indisclustered bits.
  */
-static void
+static Oid
 ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 {
 	Oid			indexOid;
@@ -8791,6 +8875,8 @@ ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
 
 	/* And do the work */
 	mark_index_clustered(rel, indexOid, false);
+
+	return indexOid;
 }
 
 /*
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e690e27..075c238 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -27,6 +27,7 @@ typedef struct RawColumnDefault
 typedef struct CookedConstraint
 {
 	ConstrType	contype;		/* CONSTR_DEFAULT or CONSTR_CHECK */
+	Oid			conoid;			/* OID of the new element */
 	char	   *name;			/* name, or NULL if none */
 	AttrNumber	attnum;			/* which attr (only for DEFAULT) */
 	Node	   *expr;			/* transformed default or check expr */
@@ -99,7 +100,7 @@ extern List *AddRelationNewConstraints(Relation rel,
 						  bool is_local,
 						  bool is_internal);
 
-extern void StoreAttrDefault(Relation rel, AttrNumber attnum,
+extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum,
 				 Node *expr, bool is_internal);
 
 extern Node *cookDefault(ParseState *pstate,
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 006b180..acde583 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -62,7 +62,7 @@ extern Oid index_create(Relation heapRelation,
 			 bool concurrent,
 			 bool is_internal);
 
-extern void index_constraint_create(Relation heapRelation,
+extern Oid index_constraint_create(Relation heapRelation,
 						Oid indexRelationId,
 						IndexInfo *indexInfo,
 						const char *constraintName,
-- 
1.9.1

0009-core-support-ALTER-TABLESPACE-MOVE-in-event-trigs.patchtext/x-diff; charset=us-asciiDownload
>From 421bd1080a9ad5aa05e26f3c2cc32c5e4a163f1b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 29 Apr 2014 15:51:19 -0400
Subject: [PATCH 09/36] core: support ALTER TABLESPACE MOVE in event trigs

This operation actually affects tables, not tablespaces, so it doesn't
make sense to avoid support for it.
---
 src/backend/commands/event_trigger.c |  1 +
 src/backend/tcop/utility.c           | 11 +++++------
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 1285fd0..bfab1d7 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -250,6 +250,7 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 ||
 		pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
 		pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 ||
+		pg_strcasecmp(tag, "ALTER TABLESPACE MOVE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0)
 		return EVENT_TRIGGER_COMMAND_TAG_OK;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3423898..5553221 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -506,11 +506,6 @@ standard_ProcessUtility(Node *parsetree,
 			AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree);
 			break;
 
-		case T_AlterTableSpaceMoveStmt:
-			/* no event triggers for global objects */
-			AlterTableSpaceMove((AlterTableSpaceMoveStmt *) parsetree);
-			break;
-
 		case T_TruncateStmt:
 			ExecuteTruncate((TruncateStmt *) parsetree);
 			break;
@@ -1304,6 +1299,10 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
 				break;
 
+			case T_AlterTableSpaceMoveStmt:
+				AlterTableSpaceMove((AlterTableSpaceMoveStmt *) parsetree);
+				break;
+
 			case T_AlterOwnerStmt:
 				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
@@ -1806,7 +1805,7 @@ CreateCommandTag(Node *parsetree)
 			break;
 
 		case T_AlterTableSpaceMoveStmt:
-			tag = "ALTER TABLESPACE";
+			tag = "ALTER TABLESPACE MOVE";
 			break;
 
 		case T_CreateExtensionStmt:
-- 
1.9.1

0010-deparse-core-event-triggers-support-GRANT-REVOKE.patchtext/x-diff; charset=us-asciiDownload
>From b8ad699774b9eaaa064afe37a54ab6eb7a702b1b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 9 May 2014 18:32:23 -0400
Subject: [PATCH 10/36] deparse/core: event triggers support GRANT/REVOKE

---
 src/backend/commands/event_trigger.c |  2 ++
 src/backend/tcop/utility.c           | 10 +++++-----
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index bfab1d7..02b4e45 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -251,6 +251,8 @@ check_ddl_tag(const char *tag)
 		pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
 		pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 ||
 		pg_strcasecmp(tag, "ALTER TABLESPACE MOVE") == 0 ||
+		pg_strcasecmp(tag, "GRANT") == 0 ||
+		pg_strcasecmp(tag, "REVOKE") == 0 ||
 		pg_strcasecmp(tag, "DROP OWNED") == 0)
 		return EVENT_TRIGGER_COMMAND_TAG_OK;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5553221..15feead 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -545,11 +545,6 @@ standard_ProcessUtility(Node *parsetree,
 			DeallocateQuery((DeallocateStmt *) parsetree);
 			break;
 
-		case T_GrantStmt:
-			/* no event triggers for global objects */
-			ExecuteGrantStmt((GrantStmt *) parsetree);
-			break;
-
 		case T_GrantRoleStmt:
 			/* no event triggers for global objects */
 			GrantRole((GrantRoleStmt *) parsetree);
@@ -1307,6 +1302,11 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
 				break;
 
+			case T_GrantStmt:
+				/* command is stashed in ExecuteGrantStmt_oids */
+				ExecuteGrantStmt((GrantStmt *) parsetree);
+				break;
+
 			case T_DropOwnedStmt:
 				DropOwnedObjects((DropOwnedStmt *) parsetree);
 				break;
-- 
1.9.1

0011-deparse-core-add-format_type_detailed.patchtext/x-diff; charset=us-asciiDownload
>From 3fb5d2e2ed7065f04b5c18e1e9cb42c287c6e284 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 12 Jun 2014 16:43:07 -0400
Subject: [PATCH 11/36] deparse/core: add format_type_detailed()

---
 src/backend/utils/adt/format_type.c | 99 ++++++++++++++++++++++++++++++++++++-
 src/include/utils/builtins.h        |  3 ++
 2 files changed, 100 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index e1763a3..7639d49 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type_be(Oid type_oid)
 	return format_type_internal(type_oid, -1, false, false, false);
 }
 
+/*
+ * This version returns a name which is always qualified.
+ */
 char *
 format_type_be_qualified(Oid type_oid)
 {
@@ -323,6 +326,92 @@ format_type_internal(Oid type_oid, int32 typemod,
 	return buf;
 }
 
+/*
+ * Similar to format_type_internal, except we return each bit of information
+ * separately:
+ *
+ * - nspid is the schema OID
+ *
+ * - typename is set to the type name, without quotes
+ *
+ * - typmod is set to the typemod, if any, as a string with parens
+ *
+ * - is_array indicates whether []s must be added
+ *
+ * Also, we don't try to decode type names to their standard-mandated names.
+ *
+ * XXX there is a lot of code duplication between this routine and
+ * format_type_internal.  (One thing that doesn't quite match is the whole
+ * allow_invalid business.)
+ */
+void
+format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname, char **typemodstr,
+					 bool *is_array)
+{
+	HeapTuple	tuple;
+	Form_pg_type typeform;
+	Oid			array_base_type;
+
+	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+	typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+	/*
+	 * Special-case crock for INTERVAL.  The typmod here is so awful that
+	 * there's no way to cope with the regular code path.
+	 */
+	if (type_oid == INTERVALOID)
+	{
+		*typname = pstrdup("INTERVAL");
+		*nspid = InvalidOid;
+
+		if (typemod > 0)
+			*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+		else
+			*typemodstr = pstrdup("");	/* XXX ?? */
+
+		*is_array = false;
+
+		ReleaseSysCache(tuple);
+		return;
+	}
+
+	/*
+	 * Check if it's a regular (variable length) array type.  As above,
+	 * fixed-length array types such as "name" shouldn't get deconstructed.
+	 */
+	array_base_type = typeform->typelem;
+
+	if (array_base_type != InvalidOid &&
+		typeform->typstorage != 'p')
+	{
+		/* Switch our attention to the array element type */
+		ReleaseSysCache(tuple);
+		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for type %u", type_oid);
+
+		typeform = (Form_pg_type) GETSTRUCT(tuple);
+		type_oid = array_base_type;
+		*is_array = true;
+	}
+	else
+		*is_array = false;
+
+	*nspid = typeform->typnamespace;
+	*typname = pstrdup(NameStr(typeform->typname));
+
+	if (typemod > 0)
+		*typemodstr = printTypmod(NULL, typemod, typeform->typmodout);
+	else
+		*typemodstr = pstrdup("");	/* XXX ?? */
+
+	ReleaseSysCache(tuple);
+}
+
 
 /*
  * Add typmod decoration to the basic type name
@@ -338,7 +427,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 	if (typmodout == InvalidOid)
 	{
 		/* Default behavior: just print the integer typmod with parens */
-		res = psprintf("%s(%d)", typname, (int) typmod);
+		if (typname == NULL)
+			res = psprintf("(%d)", (int) typmod);
+		else
+			res = psprintf("%s(%d)", typname, (int) typmod);
 	}
 	else
 	{
@@ -347,7 +439,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout)
 
 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
 												 Int32GetDatum(typmod)));
-		res = psprintf("%s%s", typname, tmstr);
+		if (typname == NULL)
+			res = psprintf("%s", tmstr);
+		else
+			res = psprintf("%s%s", typname, tmstr);
 	}
 
 	return res;
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 26f998d..58c813b 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1064,6 +1064,9 @@ extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern Datum oidvectortypes(PG_FUNCTION_ARGS);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
+extern void format_type_detailed(Oid type_oid, int32 typemod,
+					 Oid *nspid, char **typname,
+					 char **typemodstr, bool *is_array);
 
 /* quote.c */
 extern Datum quote_ident(PG_FUNCTION_ARGS);
-- 
1.9.1

0012-deparse-core-add-get_sequence_values.patchtext/x-diff; charset=us-asciiDownload
>From 9745fef80fb7d348d496d71bf2ecefac20444a2a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 12 Jun 2014 16:44:47 -0400
Subject: [PATCH 12/36] deparse/core: add get_sequence_values()

---
 src/backend/commands/sequence.c | 34 ++++++++++++++++++++++++++++++++++
 src/include/commands/sequence.h |  1 +
 2 files changed, 35 insertions(+)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e608420..f459b4e 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1474,6 +1474,40 @@ process_owned_by(Relation seqrel, List *owned_by)
 		relation_close(tablerel, NoLock);
 }
 
+/*
+ * Return sequence parameters, detailed
+ */
+Form_pg_sequence
+get_sequence_values(Oid sequenceId)
+{
+	Buffer		buf;
+	SeqTable	elm;
+	Relation	seqrel;
+	HeapTupleData seqtuple;
+	Form_pg_sequence seq;
+	Form_pg_sequence retSeq;
+
+	retSeq = palloc(sizeof(FormData_pg_sequence));
+
+	/* open and AccessShareLock sequence */
+	init_sequence(sequenceId, &elm, &seqrel);
+
+	if (pg_class_aclcheck(sequenceId, GetUserId(),
+						  ACL_SELECT | ACL_UPDATE | ACL_USAGE) != ACLCHECK_OK)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for sequence %s",
+						RelationGetRelationName(seqrel))));
+
+	seq = read_seq_tuple(elm, seqrel, &buf, &seqtuple);
+
+	memcpy(retSeq, seq, sizeof(FormData_pg_sequence));
+
+	UnlockReleaseBuffer(buf);
+	relation_close(seqrel, NoLock);
+
+	return retSeq;
+}
 
 /*
  * Return sequence parameters, for use by information schema
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7d8a370..b7a5db4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -70,6 +70,7 @@ extern Datum setval3_oid(PG_FUNCTION_ARGS);
 extern Datum lastval(PG_FUNCTION_ARGS);
 
 extern Datum pg_sequence_parameters(PG_FUNCTION_ARGS);
+extern Form_pg_sequence get_sequence_values(Oid sequenceId);
 
 extern Oid	DefineSequence(CreateSeqStmt *stmt);
 extern Oid	AlterSequence(AlterSeqStmt *stmt);
-- 
1.9.1

0013-deparse-core-PGDLLIMPORT-creating_extension.patchtext/x-diff; charset=us-asciiDownload
>From bbb67644f1ff5fc691fbe80004f761e51a2d4568 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 1 May 2014 19:10:57 +0200
Subject: [PATCH 13/36] deparse/core: PGDLLIMPORT creating_extension

---
 src/include/commands/extension.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 2cf784b..6e53320 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -23,7 +23,7 @@
  * on the current pg_extension object for each SQL object created by its
  * installation script.
  */
-extern bool creating_extension;
+extern PGDLLIMPORT bool creating_extension;
 extern Oid	CurrentExtensionObject;
 
 
-- 
1.9.1

0014-deparse-Initial-support-for-JSON-command-deparsing.patch.gzapplication/octet-streamDownload
0015-deparse-support-CREATE-TYPE-AS-RANGE.patchtext/x-diff; charset=us-asciiDownload
>From c82cf1dbe5a262f73758901e38834693e162bb7d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 14 Feb 2014 19:04:08 -0300
Subject: [PATCH 15/36] deparse: support CREATE TYPE AS RANGE

---
 src/backend/tcop/deparse_utility.c | 107 +++++++++++++++++++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index fb97d53..f01d912 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -37,7 +37,9 @@
 #include "catalog/pg_depend.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_range.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -1479,6 +1481,108 @@ deparse_CreateEnumStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+static char *
+deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *range;
+	ObjTree	   *tmp;
+	List	   *definition = NIL;
+	Relation	pg_range;
+	HeapTuple	rangeTup;
+	Form_pg_range rangeForm;
+	ScanKeyData key[1];
+	SysScanDesc scan;
+	char	   *command;
+
+	pg_range = heap_open(RangeRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_range_rngtypid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(objectId));
+
+	scan = systable_beginscan(pg_range, RangeTypidIndexId, true,
+							  NULL, 1, key);
+
+	rangeTup = systable_getnext(scan);
+	if (!HeapTupleIsValid(rangeTup))
+		elog(ERROR, "cache lookup failed for range with type oid %u",
+			 objectId);
+
+	rangeForm = (Form_pg_range) GETSTRUCT(rangeTup);
+
+	range = new_objtree_VA("CREATE TYPE %{identity}D AS RANGE (%{definition:, }s)", 0);
+	tmp = new_objtree_for_qualname_id(TypeRelationId, objectId);
+	append_object_object(range, "identity", tmp);
+
+	/* SUBTYPE */
+	tmp = new_objtree_for_qualname_id(TypeRelationId,
+									  rangeForm->rngsubtype);
+	tmp = new_objtree_VA("SUBTYPE = %{type}D",
+						 2,
+						 "clause", ObjTypeString, "subtype",
+						 "type", ObjTypeObject, tmp);
+	definition = lappend(definition, new_object_object(NULL, tmp));
+
+	/* SUBTYPE_OPCLASS */
+	if (OidIsValid(rangeForm->rngsubopc))
+	{
+		tmp = new_objtree_for_qualname_id(OperatorClassRelationId,
+										  rangeForm->rngsubopc);
+		tmp = new_objtree_VA("SUBTYPE_OPCLASS = %{opclass}D",
+							 2,
+							 "clause", ObjTypeString, "opclass",
+							 "opclass", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* COLLATION */
+	if (OidIsValid(rangeForm->rngcollation))
+	{
+		tmp = new_objtree_for_qualname_id(CollationRelationId,
+										  rangeForm->rngcollation);
+		tmp = new_objtree_VA("COLLATION = %{collation}D",
+							 2,
+							 "clause", ObjTypeString, "collation",
+							 "collation", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* CANONICAL */
+	if (OidIsValid(rangeForm->rngcanonical))
+	{
+		tmp = new_objtree_for_qualname_id(ProcedureRelationId,
+										  rangeForm->rngcanonical);
+		tmp = new_objtree_VA("CANONICAL = %{canonical}D",
+							 2,
+							 "clause", ObjTypeString, "canonical",
+							 "canonical", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	/* SUBTYPE_DIFF */
+	if (OidIsValid(rangeForm->rngsubdiff))
+	{
+		tmp = new_objtree_for_qualname_id(ProcedureRelationId,
+										  rangeForm->rngsubdiff);
+		tmp = new_objtree_VA("SUBTYPE_DIFF = %{subtype_diff}D",
+							 2,
+							 "clause", ObjTypeString, "subtype_diff",
+							 "subtype_diff", ObjTypeObject, tmp);
+		definition = lappend(definition, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(range, "definition", definition);
+
+	systable_endscan(scan);
+	heap_close(pg_range, RowExclusiveLock);
+
+	command = jsonize_objtree(range);
+	free_objtree(range);
+
+	return command;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -1962,6 +2066,9 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateRangeStmt:	/* CREATE TYPE AS RANGE */
+			command = deparse_CreateRangeStmt(objectId, parsetree);
+			break;
+
 		case T_CreateDomainStmt:
 		case T_CreateFunctionStmt:
 		case T_CreateTableAsStmt:
-- 
1.9.1

0016-deparse-Add-support-for-CREATE-EXTENSION.patchtext/x-diff; charset=us-asciiDownload
>From d0be0f321f917e83e1a273ec1ca24337eca2f7b9 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 21 Feb 2014 18:11:35 -0300
Subject: [PATCH 16/36] deparse: Add support for CREATE EXTENSION

---
 src/backend/tcop/deparse_utility.c | 85 +++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  3 +-
 2 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index f01d912..ffe6326 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
+#include "catalog/pg_extension.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opclass.h"
@@ -626,6 +627,85 @@ get_persistence_str(char persistence)
 }
 
 /*
+ * deparse_CreateExtensionStmt
+ * 		deparse a CreateExtensionStmt
+ *
+ * Given an extension OID and the parsetree that created it, return the JSON
+ * blob representing the creation command.
+ *
+ * XXX the current representation makes the output command dependant on the
+ * installed versions of the extension.  Is this a problem?
+ */
+static char *
+deparse_CreateExtensionStmt(Oid objectId, Node *parsetree)
+{
+	CreateExtensionStmt *node = (CreateExtensionStmt *) parsetree;
+	Relation    pg_extension;
+	HeapTuple   extTup;
+	Form_pg_extension extForm;
+	ObjTree	   *extStmt;
+	ObjTree	   *tmp;
+	char	   *command;
+	List	   *list;
+	ListCell   *cell;
+
+	pg_extension = heap_open(ExtensionRelationId, AccessShareLock);
+	extTup = get_catalog_object_by_oid(pg_extension, objectId);
+	if (!HeapTupleIsValid(extTup))
+		elog(ERROR, "cache lookup failed for extension with OID %u",
+			 objectId);
+	extForm = (Form_pg_extension) GETSTRUCT(extTup);
+
+	extStmt = new_objtree_VA("CREATE EXTENSION %{if_not_exists}s %{identity}I "
+							 "%{options: }s",
+							 1, "identity", ObjTypeString, node->extname);
+	append_string_object(extStmt, "if_not_exists",
+						 node->if_not_exists ? "IF NOT EXISTS" : "");
+	list = NIL;
+	foreach(cell, node->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(cell);
+
+		if (strcmp(opt->defname, "schema") == 0)
+		{
+			/* skip this one; we add one unconditionally below */
+			continue;
+		}
+		else if (strcmp(opt->defname, "new_version") == 0)
+		{
+			tmp = new_objtree_VA("VERSION %{version}L", 2,
+								 "type", ObjTypeString, "version",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(NULL, tmp));
+		}
+		else if (strcmp(opt->defname, "old_version") == 0)
+		{
+			tmp = new_objtree_VA("FROM %{version}L", 2,
+								 "type", ObjTypeString, "from",
+								 "version", ObjTypeString, defGetString(opt));
+			list = lappend(list, new_object_object(NULL, tmp));
+		}
+		else
+			elog(ERROR, "unsupported option %s", opt->defname);
+	}
+
+	tmp = new_objtree_VA("SCHEMA %{schema}I",
+						 2, "type", ObjTypeString, "schema",
+						 "schema", ObjTypeString,
+						 get_namespace_name(extForm->extnamespace));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	append_array_object(extStmt, "options", list);
+
+	heap_close(pg_extension, AccessShareLock);
+
+	command = jsonize_objtree(extStmt);
+	free_objtree(extStmt);
+
+	return command;
+}
+
+/*
  * deparse_ViewStmt
  *		deparse a ViewStmt
  *
@@ -2053,10 +2133,13 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-		case T_CreateExtensionStmt:
 			command = NULL;
 			break;
 
+		case T_CreateExtensionStmt:
+			command = deparse_CreateExtensionStmt(objectId, parsetree);
+			break;
+
 		case T_CompositeTypeStmt:		/* CREATE TYPE (composite) */
 			command = deparse_CompositeTypeStmt(objectId, parsetree);
 			break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 0528c78..63fba50 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1178,7 +1178,8 @@ ProcessUtilitySlow(Node *parsetree,
 
 			case T_AlterExtensionStmt:
 				EventTriggerStashExtensionStart();
-				ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				objectId = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
+				EventTriggerStashCommand(objectId, OBJECT_EXTENSION, parsetree);
 				EventTriggerStashExtensionStop();
 				break;
 
-- 
1.9.1

0017-deparse-add-support-for-CREATE-RULE.patchtext/x-diff; charset=us-asciiDownload
>From 29efc0daed3f09ea06e6a35924575b45aaa9fcac Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 26 Feb 2014 17:26:55 -0300
Subject: [PATCH 17/36] deparse: add support for CREATE RULE

---
 src/backend/tcop/deparse_utility.c | 97 +++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  | 71 ++++++++++++++++++++++++++++
 src/include/utils/ruleutils.h      |  2 +
 3 files changed, 169 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index ffe6326..677e7dc 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -27,6 +27,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/sysattr.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
@@ -41,6 +42,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
+#include "catalog/pg_rewrite.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -2027,6 +2029,99 @@ deparse_IndexStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+static char *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	char	   *command;
+	Relation	pg_rewrite;
+	Form_pg_rewrite rewrForm;
+	HeapTuple	rewrTup;
+	SysScanDesc	scan;
+	ScanKeyData	key;
+	Datum		ev_qual;
+	Datum		ev_actions;
+	bool		isnull;
+	char	   *qual;
+	List	   *actions;
+	List	   *list;
+	ListCell   *cell;
+
+	pg_rewrite = heap_open(RewriteRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(objectId));
+
+	scan = systable_beginscan(pg_rewrite, RewriteOidIndexId, true,
+							  NULL, 1, &key);
+	rewrTup = systable_getnext(scan);
+	if (!HeapTupleIsValid(rewrTup))
+		elog(ERROR, "cache lookup failed for rewrite rule with oid %u",
+			 objectId);
+
+	rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+	ruleStmt =
+		new_objtree_VA("CREATE %{or_replace}s RULE %{identity}I "
+					   "AS ON %{event}s TO %{table}D %{where_clause}s "
+					   "DO %{instead}s (%{actions:; }s)", 2,
+					   "identity", ObjTypeString, node->rulename,
+					   "or_replace", ObjTypeString,
+					   node->replace ? "OR REPLACE" : "");
+	append_string_object(ruleStmt, "event",
+						 node->event == CMD_SELECT ? "SELECT" :
+						 node->event == CMD_UPDATE ? "UPDATE" :
+						 node->event == CMD_DELETE ? "DELETE" :
+						 node->event == CMD_INSERT ? "INSERT" : "XXX");
+	append_object_object(ruleStmt, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 rewrForm->ev_class));
+
+	append_string_object(ruleStmt, "instead",
+						 node->instead ? "INSTEAD" : "ALSO");
+
+	ev_qual = heap_getattr(rewrTup, Anum_pg_rewrite_ev_qual,
+						   RelationGetDescr(pg_rewrite), &isnull);
+	ev_actions = heap_getattr(rewrTup, Anum_pg_rewrite_ev_action,
+							  RelationGetDescr(pg_rewrite), &isnull);
+
+	pg_get_ruledef_details(ev_qual, ev_actions, &qual, &actions);
+
+	tmp = new_objtree_VA("WHERE %{clause}s", 0);
+
+	if (qual)
+		append_string_object(tmp, "clause", qual);
+	else
+	{
+		append_null_object(tmp, "clause");
+		append_bool_object(tmp, "present", false);
+	}
+
+	append_object_object(ruleStmt, "where_clause", tmp);
+
+	list = NIL;
+	foreach(cell, actions)
+	{
+		char *action = lfirst(cell);
+
+		list = lappend(list, new_string_object(NULL, action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	command = jsonize_objtree(ruleStmt);
+	free_objtree(ruleStmt);
+
+	return command;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2120,7 +2215,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_RuleStmt:
-			command = NULL;
+			command = deparse_RuleStmt(objectId, parsetree);
 			break;
 
 			/* FDW-related objects */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2cdb60e..522ccf4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -448,6 +448,77 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags)));
 }
 
+/*
+ * Given a pair of Datum corresponding to a rule's pg_rewrite.ev_qual and
+ * ev_action columns, return their text representation; ev_qual as a single
+ * string in whereClause and ev_action as a List of strings (which might be
+ * NIL, signalling NOTHING) in actions.
+ */
+void
+pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions)
+{
+	int prettyFlags = 0;
+	char *qualstr = TextDatumGetCString(ev_qual);
+	char *actionstr = TextDatumGetCString(ev_action);
+	List *actionNodeList = (List *) stringToNode(actionstr);
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+	if (strlen(qualstr) > 0 && strcmp(qualstr, "<>") != 0)
+	{
+		Node	   *qual;
+		Query	   *query;
+		deparse_context context;
+		deparse_namespace dpns;
+
+		qual = stringToNode(qualstr);
+
+		query = (Query *) linitial(actionNodeList);
+		query = getInsertSelectQuery(query, NULL);
+
+		AcquireRewriteLocks(query, false, false);
+
+		context.buf = &buf;
+		context.namespaces = list_make1(&dpns);
+		context.windowClause = NIL;
+		context.windowTList = NIL;
+		context.varprefix = (list_length(query->rtable) != 1);
+		context.prettyFlags = prettyFlags;
+		context.wrapColumn = WRAP_COLUMN_DEFAULT;
+		context.indentLevel = PRETTYINDENT_STD;
+
+		set_deparse_for_query(&dpns, query, NIL);
+
+		get_rule_expr(qual, &context, false);
+
+		*whereClause = pstrdup(buf.data);
+	}
+	else
+		*whereClause = NULL;
+
+	if (list_length(actionNodeList) == 0)
+		*actions = NIL;
+	else
+	{
+		ListCell *cell;
+		List	*output = NIL;
+
+		foreach(cell, actionNodeList)
+		{
+			Query	*query = (Query *) lfirst(cell);
+
+			if (query->commandType == CMD_NOTHING)
+				continue;
+
+			resetStringInfo(&buf);
+			get_query_def(query, &buf, NIL, NULL,
+						  prettyFlags, WRAP_COLUMN_DEFAULT, 0);
+			output = lappend(output, pstrdup(buf.data));
+		}
+		*actions = output;
+	}
+}
 
 static char *
 pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 492d940..657069d 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -32,6 +32,8 @@ extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
 extern char *pg_get_viewstmt_definition(Query *viewParse);
 extern char *pg_get_viewdef_internal(Oid viewoid);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
1.9.1

0018-deparse-support-ALTER-TYPE-ADD-VALUE-for-enums.patchtext/x-diff; charset=us-asciiDownload
>From 80f2004a06040033fa3a6fbd7360f59b6811484d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 21 Mar 2014 16:33:14 -0300
Subject: [PATCH 18/36] deparse: support ALTER TYPE / ADD VALUE (for enums)

---
 src/backend/tcop/deparse_utility.c | 45 ++++++++++++++++++++++++++++++++++++++
 src/backend/tcop/utility.c         |  3 ++-
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 677e7dc..691c78a 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2166,6 +2166,43 @@ deparse_CreateSchemaStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
+{
+	AlterEnumStmt *node = (AlterEnumStmt *) parsetree;
+	ObjTree	   *alterEnum;
+	ObjTree	   *tmp;
+	char	   *command;
+
+	alterEnum =
+		new_objtree_VA("ALTER TYPE %{identity}D ADD VALUE %{if_not_exists}s %{value}L %{position}s",
+					   0);
+
+	append_string_object(alterEnum, "if_not_exists",
+						 node->skipIfExists ? "IF NOT EXISTS" : "");
+	append_object_object(alterEnum, "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	append_string_object(alterEnum, "value", node->newVal);
+	tmp = new_objtree_VA("%{after_or_before}s %{neighbour}L", 0);
+	if (node->newValNeighbor)
+	{
+		append_string_object(tmp, "after_or_before",
+							 node->newValIsAfter ? "AFTER" : "BEFORE");
+		append_string_object(tmp, "neighbour", node->newValNeighbor);
+	}
+	else
+	{
+		append_bool_object(tmp, "present", false);
+	}
+	append_object_object(alterEnum, "position", tmp);
+
+	command = jsonize_objtree(alterEnum);
+	free_objtree(alterEnum);
+
+	return command;
+}
+
+static char *
 deparse_parsenode_cmd(StashedCommand *cmd)
 {
 	Oid			objectId;
@@ -2263,6 +2300,14 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = NULL;
 			break;
 
+		case T_AlterTableStmt:
+			command = NULL;
+			break;
+
+		case T_AlterEnumStmt:
+			command = deparse_AlterEnumStmt(objectId, parsetree);
+			break;
+
 		default:
 			command = NULL;
 			elog(LOG, "unrecognized node type: %d",
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 63fba50..b606cb8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1244,7 +1244,8 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterEnumStmt:		/* ALTER TYPE (enum) */
-				AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				objectId = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel);
+				EventTriggerStashCommand(objectId, OBJECT_TYPE, parsetree);
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
-- 
1.9.1

0019-deparse-add-support-for-ALTER-THING-RENAME.patchtext/x-diff; charset=us-asciiDownload
>From 29ef698f7eda99312a6faafb7de76220d712567d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 25 Apr 2014 16:43:53 -0300
Subject: [PATCH 19/36] deparse: add support for ALTER THING RENAME

It supports everything but functions, aggregates, operator classes and
operator families.
---
 src/backend/tcop/deparse_utility.c | 276 ++++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c         |  28 +++-
 2 files changed, 302 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 691c78a..682a7f4 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -630,7 +630,7 @@ get_persistence_str(char persistence)
 
 /*
  * deparse_CreateExtensionStmt
- * 		deparse a CreateExtensionStmt
+ *		deparse a CreateExtensionStmt
  *
  * Given an extension OID and the parsetree that created it, return the JSON
  * blob representing the creation command.
@@ -1665,6 +1665,276 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+/*
+ * Return the given object type as a string.
+ */
+static const char *
+stringify_objtype(ObjectType objtype)
+{
+	switch (objtype)
+	{
+		case OBJECT_AGGREGATE:
+			return "AGGREGATE";
+		case OBJECT_DOMAIN:
+			return "DOMAIN";
+		case OBJECT_COLLATION:
+			return "COLLATION";
+		case OBJECT_CONVERSION:
+			return "CONVERSION";
+		case OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case OBJECT_FOREIGN_SERVER:
+			return "SERVER";
+		case OBJECT_FOREIGN_TABLE:
+			return "FOREIGN TABLE";
+		case OBJECT_FUNCTION:
+			return "FUNCTION";
+		case OBJECT_INDEX:
+			return "INDEX";
+		case OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case OBJECT_MATVIEW:
+			return "MATERIALIZED VIEW";
+		case OBJECT_OPERATOR:
+			return "OPERATOR";
+		case OBJECT_OPCLASS:
+			return "OPERATOR CLASS";
+		case OBJECT_OPFAMILY:
+			return "OPERATOR FAMILY";
+		case OBJECT_TABLE:
+			return "TABLE";
+		case OBJECT_TSCONFIGURATION:
+			return "TEXT SEARCH CONFIGURATION";
+		case OBJECT_TSDICTIONARY:
+			return "TEXT SEARCH DICTIONARY";
+		case OBJECT_TSPARSER:
+			return "TEXT SEARCH PARSER";
+		case OBJECT_TSTEMPLATE:
+			return "TEXT SEARCH TEMPLATE";
+		case OBJECT_TYPE:
+			return "TYPE";
+		case OBJECT_SCHEMA:
+			return "SCHEMA";
+		case OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case OBJECT_VIEW:
+			return "VIEW";
+
+		default:
+			elog(ERROR, "unsupported objtype %d", objtype);
+	}
+}
+
+static char *
+deparse_RenameStmt(Oid objectId, Node *parsetree)
+{
+	RenameStmt *node = (RenameStmt *) parsetree;
+	ObjTree	   *renameStmt;
+	char	   *command;
+	char	   *fmtstr;
+	Relation	relation;
+	Oid			schemaId;
+	const char *subthing;
+
+	/*
+	 * FIXME --- this code is missing support for inheritance behavioral flags,
+	 * i.e. the "*" and ONLY elements.
+	 */
+
+	/*
+	 * In a ALTER .. RENAME command, we don't have the original name of the
+	 * object in system catalogs: since we inspect them after the command has
+	 * executed, the old name is already gone.  Therefore, we extract it from
+	 * the parse node.  Note we still extract the schema name from the catalog
+	 * (it might not be present in the parse node); it cannot possibly have
+	 * changed anyway.
+	 *
+	 * XXX what if there's another event trigger running concurrently that
+	 * renames the schema or moves the object to another schema?  Seems
+	 * pretty far-fetched, but possible nonetheless.
+	 */
+	switch (node->renameType)
+	{
+		case OBJECT_TABLE:
+		case OBJECT_SEQUENCE:
+		case OBJECT_VIEW:
+		case OBJECT_MATVIEW:
+		case OBJECT_INDEX:
+		case OBJECT_FOREIGN_TABLE:
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME TO %%{newname}I",
+							  stringify_objtype(node->renameType));
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_COLUMN:
+		case OBJECT_ATTRIBUTE:
+			relation = relation_open(objectId, AccessShareLock);
+			schemaId = RelationGetNamespace(relation);
+
+			if (node->renameType == OBJECT_COLUMN)
+				subthing = "COLUMN";
+			else
+				subthing = "ATTRIBUTE";
+
+			fmtstr = psprintf("ALTER %s %%{if_exists}s %%{identity}D RENAME %s %%{colname}I TO %%{newname}I",
+							  stringify_objtype(node->relationType),
+							  subthing);
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_object_object(renameStmt, "identity",
+								 new_objtree_for_qualname(schemaId,
+														  node->relation->relname));
+			append_string_object(renameStmt, "colname", node->subname);
+			append_string_object(renameStmt, "if_exists",
+								 node->missing_ok ? "IF EXISTS" : "");
+			relation_close(relation, AccessShareLock);
+			break;
+
+		case OBJECT_SCHEMA:
+		case OBJECT_FDW:
+		case OBJECT_LANGUAGE:
+		case OBJECT_FOREIGN_SERVER:
+			fmtstr = psprintf("ALTER %s %%{identity}I RENAME TO %%{newname}I",
+							  stringify_objtype(node->relationType));
+			renameStmt = new_objtree_VA(fmtstr, 0);
+			append_string_object(renameStmt, "identity",
+								 node->subname);
+			break;
+
+		case OBJECT_COLLATION:
+		case OBJECT_CONVERSION:
+		case OBJECT_DOMAIN:
+		case OBJECT_TSDICTIONARY:
+		case OBJECT_TSPARSER:
+		case OBJECT_TSTEMPLATE:
+		case OBJECT_TSCONFIGURATION:
+		case OBJECT_TYPE:
+			{
+				ObjTree    *ident;
+				HeapTuple	objTup;
+				Oid			catalogId;
+				Relation	catalog;
+				bool		isnull;
+				AttrNumber	nspnum;
+
+				catalogId = get_objtype_catalog_oid(node->renameType);
+				catalog = heap_open(catalogId, AccessShareLock);
+				objTup = get_catalog_object_by_oid(catalog, objectId);
+				nspnum = get_object_attnum_namespace(catalogId);
+
+				schemaId = DatumGetObjectId(heap_getattr(objTup,
+														 nspnum,
+														 RelationGetDescr(catalog),
+														 &isnull));
+
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME TO %%{newname}I",
+								  stringify_objtype(node->renameType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+				ident = new_objtree_for_qualname(schemaId,
+												 strVal(llast(node->object)));
+				append_object_object(renameStmt, "identity", ident);
+				relation_close(catalog, AccessShareLock);
+
+			}
+			break;
+
+		case OBJECT_AGGREGATE:
+		case OBJECT_FUNCTION:
+			elog(ERROR, "renaming of functions and aggregates is not supported yet");
+
+		case OBJECT_CONSTRAINT:
+			{
+				HeapTuple		conTup;
+				Form_pg_constraint	constrForm;
+				ObjTree		   *ident;
+
+				conTup = SearchSysCache1(CONSTROID, objectId);
+				constrForm = (Form_pg_constraint) GETSTRUCT(conTup);
+				fmtstr = psprintf("ALTER %s %%{identity}D RENAME CONSTRAINT %%{conname}I TO %%{newname}I",
+								  stringify_objtype(node->relationType));
+				renameStmt = new_objtree_VA(fmtstr, 0);
+
+				if (node->relationType == OBJECT_DOMAIN)
+					ident = new_objtree_for_qualname_id(TypeRelationId,
+														constrForm->contypid);
+				else if (node->relationType == OBJECT_TABLE)
+					ident = new_objtree_for_qualname_id(RelationRelationId,
+														constrForm->conrelid);
+				else
+					elog(ERROR, "invalid relation type %d", node->relationType);
+
+				append_string_object(renameStmt, "conname", node->subname);
+				append_object_object(renameStmt, "identity", ident);
+				ReleaseSysCache(conTup);
+			}
+			break;
+
+		case OBJECT_OPCLASS:
+		case OBJECT_OPFAMILY:
+			ereport(ERROR,
+					(errmsg("renaming of operator classes and families is not supported")));
+			break;
+
+		case OBJECT_RULE:
+			{
+				HeapTuple	rewrTup;
+				Form_pg_rewrite rewrForm;
+				Relation	pg_rewrite;
+
+				pg_rewrite = relation_open(RewriteRelationId, AccessShareLock);
+				rewrTup = get_catalog_object_by_oid(pg_rewrite, objectId);
+				rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+				renameStmt = new_objtree_VA("ALTER RULE %{rulename}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "rulename", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 rewrForm->ev_class));
+				relation_close(pg_rewrite, AccessShareLock);
+			}
+			break;
+
+		case OBJECT_TRIGGER:
+			{
+				HeapTuple	trigTup;
+				Form_pg_trigger trigForm;
+				Relation	pg_trigger;
+
+				pg_trigger = relation_open(TriggerRelationId, AccessShareLock);
+				trigTup = get_catalog_object_by_oid(pg_trigger, objectId);
+				trigForm = (Form_pg_trigger) GETSTRUCT(trigTup);
+
+				renameStmt = new_objtree_VA("ALTER TRIGGER %{triggername}I ON %{identity}D RENAME TO %{newname}I",
+											0);
+				append_string_object(renameStmt, "triggername", node->subname);
+				append_object_object(renameStmt, "identity",
+									 new_objtree_for_qualname_id(RelationRelationId,
+																 trigForm->tgrelid));
+				relation_close(pg_trigger, AccessShareLock);
+			}
+			break;
+		default:
+			elog(ERROR, "unsupported object type %d", node->renameType);
+	}
+
+	append_string_object(renameStmt, "newname", node->newname);
+
+	command = jsonize_objtree(renameStmt);
+	free_objtree(renameStmt);
+
+	return command;
+}
+
 static inline ObjElem *
 deparse_Seq_Cache(ObjTree *parent, Form_pg_sequence seqdata)
 {
@@ -2284,6 +2554,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			command = deparse_CreateRangeStmt(objectId, parsetree);
 			break;
 
+		case T_RenameStmt:		/* ALTER .. RENAME */
+			command = deparse_RenameStmt(objectId, parsetree);
+			break;
+
 		case T_CreateDomainStmt:
 		case T_CreateFunctionStmt:
 		case T_CreateTableAsStmt:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b606cb8..beedb01 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1343,7 +1343,33 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_RenameStmt:
-				ExecRenameStmt((RenameStmt *) parsetree);
+				{
+					ObjectType	objtype;
+
+					objectId = ExecRenameStmt((RenameStmt *) parsetree);
+
+					/*
+					 * Kludge alert: the event trigger machinery as a whole
+					 * doesn't support OBJECT_COLUMN nor OBJECT_ATTRIBUTE, so
+					 * fool it by using the relation type instead.  In certain
+					 * cases this is actually incorrect (for example we might
+					 * be renaming a type attribute or a view column), but the
+					 * deparsing functions will cope with this by actually
+					 * looking at the renameType directly.  The object type and
+					 * identity as reported by
+					 * pg_event_trigger_get_creation_commands might be
+					 * misleading, though.
+					 *
+					 * To support this better we ought to have the attribute
+					 * number for the column or attribute here.  Maybe have
+					 * ExecRenameStmt pass it back?
+					 */
+					objtype = ((RenameStmt *) parsetree)->renameType;
+					if (objtype == OBJECT_COLUMN ||
+						objtype == OBJECT_ATTRIBUTE)
+						objtype = ((RenameStmt *) parsetree)->relationType;
+					EventTriggerStashCommand(objectId, objtype, parsetree);
+				}
 				break;
 
 			case T_AlterObjectSchemaStmt:
-- 
1.9.1

0020-deparse-support-CREATE-DOMAIN.patchtext/x-diff; charset=us-asciiDownload
>From b9f18b7e0687a4cbc6a30de296ef1b38bf5cf70a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 25 Apr 2014 17:54:19 -0300
Subject: [PATCH 20/36] deparse: support CREATE DOMAIN

---
 src/backend/tcop/deparse_utility.c | 60 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 682a7f4..0281b4e 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -1665,6 +1665,63 @@ deparse_CreateRangeStmt(Oid objectId, Node *parsetree)
 	return command;
 }
 
+static char *
+deparse_CreateDomain(Oid objectId, Node *parsetree)
+{
+	ObjTree	   *createDomain;
+	ObjTree	   *tmp;
+	char	   *command;
+	HeapTuple	typTup;
+	Form_pg_type typForm;
+	List	   *constraints;
+
+	typTup = SearchSysCache1(TYPEOID,
+							 objectId);
+	if (!HeapTupleIsValid(typTup))
+		elog(ERROR, "cache lookup failed for domain with OID %u", objectId);
+	typForm = (Form_pg_type) GETSTRUCT(typTup);
+
+	createDomain = new_objtree_VA("CREATE DOMAIN %{identity}D AS %{type}D %{not_null}s %{constraints}s %{collation}s",
+								  0);
+
+	append_object_object(createDomain,
+						 "identity",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 objectId));
+	append_object_object(createDomain,
+						 "type",
+						 new_objtree_for_qualname_id(TypeRelationId,
+													 typForm->typbasetype));
+
+	if (typForm->typnotnull)
+		append_string_object(createDomain, "not_null", "NOT NULL");
+	else
+		append_string_object(createDomain, "not_null", "");
+
+	constraints = obtainConstraints(NIL, InvalidOid, objectId);
+	tmp = new_objtree_VA("%{elements: }s", 0);
+	if (constraints == NIL)
+		append_bool_object(tmp, "present", false);
+	else
+		append_array_object(tmp, "elements", constraints);
+	append_object_object(createDomain, "constraints", tmp);
+
+	tmp = new_objtree_VA("COLLATE %{collation}D", 0);
+	if (OidIsValid(typForm->typcollation))
+		append_object_object(tmp, "collation",
+							 new_objtree_for_qualname_id(CollationRelationId,
+														 typForm->typcollation));
+	else
+		append_bool_object(tmp, "present", false);
+	append_object_object(createDomain, "collation", tmp);
+
+	ReleaseSysCache(typTup);
+	command = jsonize_objtree(createDomain);
+	free_objtree(createDomain);
+
+	return command;
+}
+
 /*
  * Return the given object type as a string.
  */
@@ -2559,6 +2616,9 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateDomainStmt:
+			command = deparse_CreateDomain(objectId, parsetree);
+			break;
+
 		case T_CreateFunctionStmt:
 		case T_CreateTableAsStmt:
 		case T_CreatePLangStmt:
-- 
1.9.1

0021-deparse-core-enable-deparse-of-function-defaults-exp.patchtext/x-diff; charset=us-asciiDownload
>From 3bc186efd1c735828b280b24b3acd7b3ed5a9c8a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 15 Apr 2014 16:44:23 -0300
Subject: [PATCH 21/36] deparse/core: enable deparse of function defaults expr

---
 src/backend/utils/adt/ruleutils.c | 25 +++++++++++++++++++++++++
 src/include/utils/ruleutils.h     |  1 +
 2 files changed, 26 insertions(+)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 522ccf4..ac007ee 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9580,3 +9580,28 @@ RelationGetColumnDefault(Relation rel, AttrNumber attno, List *dpcontext)
 
 	return defstr;
 }
+
+/*
+ * Return the defaults values of arguments to a function, as a list of
+ * deparsed expressions.
+ */
+List *
+FunctionGetDefaults(text *proargdefaults)
+{
+	List   *nodedefs;
+	List   *strdefs = NIL;
+	ListCell *cell;
+
+	nodedefs = (List *) stringToNode(TextDatumGetCString(proargdefaults));
+	if (!IsA(nodedefs, List))
+		elog(ERROR, "proargdefaults is not a list");
+
+	foreach(cell, nodedefs)
+	{
+		Node   *onedef = lfirst(cell);
+
+		strdefs = lappend(strdefs, deparse_expression_pretty(onedef, NIL, false, false, 0, 0));
+	}
+
+	return strdefs;
+}
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 657069d..5648938 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -43,6 +43,7 @@ extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
 extern List *select_rtable_names_for_explain(List *rtable,
 								Bitmapset *rels_used);
 extern char *generate_collation_name(Oid collid);
+extern List *FunctionGetDefaults(text *proargdefaults);
 
 extern char *RelationGetColumnDefault(Relation rel, AttrNumber attno,
 						 List *dpcontext);
-- 
1.9.1

0022-deparse-deparse-CREATE-FUNCTION.patchtext/x-diff; charset=us-asciiDownload
>From 9baf2143155a72317edc09b76a1290ee127b9c10 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 15 Apr 2014 16:45:03 -0300
Subject: [PATCH 22/36] deparse: deparse CREATE FUNCTION

---
 src/backend/tcop/deparse_utility.c | 319 +++++++++++++++++++++++++++++++++++++
 1 file changed, 319 insertions(+)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 0281b4e..2412843 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -38,6 +38,7 @@
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_inherits.h"
+#include "catalog/pg_language.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
@@ -1723,6 +1724,321 @@ deparse_CreateDomain(Oid objectId, Node *parsetree)
 }
 
 /*
+ * deparse_CreateFunctionStmt
+ *		Deparse a CreateFunctionStmt (CREATE FUNCTION)
+ *
+ * Given a function OID and the parsetree that created it, return the JSON
+ * blob representing the creation command.
+ *
+ * XXX this is missing the per-function custom-GUC thing.
+ */
+static char *
+deparse_CreateFunction(Oid objectId, Node *parsetree)
+{
+	CreateFunctionStmt *node = (CreateFunctionStmt *) parsetree;
+	ObjTree	   *createFunc;
+	ObjTree	   *sign;
+	ObjTree	   *tmp;
+	Datum		tmpdatum;
+	char	   *fmt;
+	char	   *definition;
+	char	   *command;
+	char	   *source;
+	char	   *probin;
+	List	   *params;
+	List	   *defaults;
+	ListCell   *cell;
+	ListCell   *curdef;
+	ListCell   *table_params = NULL;
+	HeapTuple	procTup;
+	Form_pg_proc procForm;
+	HeapTuple	langTup;
+	Oid		   *typarray;
+	Form_pg_language langForm;
+	int			i;
+	int			typnum;
+	bool		isnull;
+
+	/* get the pg_proc tuple */
+	procTup = SearchSysCache1(PROCOID, objectId);
+	if (!HeapTupleIsValid(procTup))
+		elog(ERROR, "cache lookup failure for function with OID %u",
+			 objectId);
+	procForm = (Form_pg_proc) GETSTRUCT(procTup);
+
+	/* get the corresponding pg_language tuple */
+	langTup = SearchSysCache1(LANGOID, procForm->prolang);
+	if (!HeapTupleIsValid(langTup))
+		elog(ERROR, "cache lookup failure for language with OID %u",
+			 procForm->prolang);
+	langForm = (Form_pg_language) GETSTRUCT(langTup);
+
+	/*
+	 * Determine useful values for prosrc and probin.  We cope with probin
+	 * being either NULL or "-", but prosrc must have a valid value.
+	 */
+	tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+							   Anum_pg_proc_prosrc, &isnull);
+	if (isnull)
+		elog(ERROR, "null prosrc in function with OID %u", objectId);
+	source = TextDatumGetCString(tmpdatum);
+
+	/* Determine a useful value for probin */
+	tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+							   Anum_pg_proc_probin, &isnull);
+	if (isnull)
+		probin = NULL;
+	else
+	{
+		probin = TextDatumGetCString(tmpdatum);
+		if (probin[0] == '\0' || strcmp(probin, "-") == 0)
+		{
+			pfree(probin);
+			probin = NULL;
+		}
+	}
+
+	if (probin == NULL)
+		definition = "%{definition}L";
+	else
+		definition = "%{objfile}L, %{symbol}L";
+
+	fmt = psprintf("CREATE %%{or_replace}s FUNCTION %%{signature}s "
+				   "RETURNS %%{return_type}s LANGUAGE %%{language}I "
+				   "%%{window}s %%{volatility}s %%{leakproof}s "
+				   "%%{strict}s %%{security_definer}s %%{cost}s %%{rows}s "
+				   "%%{set_options: }s "
+				   "AS %s", definition);
+
+	createFunc = new_objtree_VA(fmt, 1,
+								"or_replace", ObjTypeString,
+								node->replace ? "OR REPLACE" : "");
+
+	sign = new_objtree_VA("%{identity}D(%{arguments:, }s)", 0);
+
+	/*
+	 * To construct the arguments array, extract the type OIDs from the
+	 * function's pg_proc entry.  If pronargs equals the parameter list length,
+	 * there are no OUT parameters and thus we can extract the type OID from
+	 * proargtypes; otherwise we need to decode proallargtypes, which is
+	 * a bit more involved.
+	 */
+	typarray = palloc(list_length(node->parameters) * sizeof(Oid));
+	if (list_length(node->parameters) > procForm->pronargs)
+	{
+		bool	isnull;
+		Datum	alltypes;
+		Datum  *values;
+		bool   *nulls;
+		int		nelems;
+
+		alltypes = SysCacheGetAttr(PROCOID, procTup,
+								   Anum_pg_proc_proallargtypes, &isnull);
+		if (isnull)
+			elog(ERROR, "NULL proallargtypes, but more parameters than args");
+		deconstruct_array(DatumGetArrayTypeP(alltypes),
+						  OIDOID, 4, 't', 'i',
+						  &values, &nulls, &nelems);
+		if (nelems != list_length(node->parameters))
+			elog(ERROR, "mismatched proallargatypes");
+		for (i = 0; i < list_length(node->parameters); i++)
+			typarray[i] = values[i];
+	}
+	else
+	{
+		for (i = 0; i < list_length(node->parameters); i++)
+			 typarray[i] = procForm->proargtypes.values[i];
+	}
+
+	/*
+	 * If there are any default expressions, we read the deparsed expression as
+	 * a list so that we can attach them to each argument.
+	 */
+	tmpdatum = SysCacheGetAttr(PROCOID, procTup,
+							   Anum_pg_proc_proargdefaults, &isnull);
+	if (!isnull)
+	{
+		defaults = FunctionGetDefaults(DatumGetTextP(tmpdatum));
+		curdef = list_head(defaults);
+	}
+	else
+	{
+		defaults = NIL;
+		curdef = NULL;
+	}
+
+	/*
+	 * Now iterate over each parameter in the parsetree to create the
+	 * parameters array.
+	 */
+	params = NIL;
+	typnum = 0;
+	foreach(cell, node->parameters)
+	{
+		FunctionParameter *param = (FunctionParameter *) lfirst(cell);
+		ObjTree	   *tmp2;
+		ObjTree	   *tmp3;
+
+		/*
+		 * A PARAM_TABLE parameter indicates end of input arguments; the
+		 * following parameters are part of the return type.  We ignore them
+		 * here, but keep track of the current position in the list so that
+		 * we can easily produce the return type below.
+		 */
+		if (param->mode == FUNC_PARAM_TABLE)
+		{
+			table_params = cell;
+			break;
+		}
+
+		/*
+		 * Note that %{name}s is a string here, not an identifier; the reason
+		 * for this is that an absent parameter name must produce an empty
+		 * string, not "", which is what would happen if we were to use
+		 * %{name}I here.  So we add another level of indirection to allow us
+		 * to inject a "present" parameter.
+		 */
+		tmp2 = new_objtree_VA("%{mode}s %{name}s %{type}T %{default}s", 0);
+		append_string_object(tmp2, "mode",
+							 param->mode == FUNC_PARAM_IN ? "IN" :
+							 param->mode == FUNC_PARAM_OUT ? "OUT" :
+							 param->mode == FUNC_PARAM_INOUT ? "INOUT" :
+							 param->mode == FUNC_PARAM_VARIADIC ? "VARIADIC" :
+							 "INVALID MODE");
+
+		/* optional wholesale suppression of "name" occurs here */
+		append_object_object(tmp2, "name",
+							 new_objtree_VA("%{name}I", 2,
+											"name", ObjTypeString,
+											param->name ? param->name : "NULL",
+											"present", ObjTypeBool,
+											param->name ? true : false));
+
+		tmp3 = new_objtree_VA("DEFAULT %{value}s", 0);
+		if (PointerIsValid(param->defexpr))
+		{
+			char *expr;
+
+			if (curdef == NULL)
+				elog(ERROR, "proargdefaults list too short");
+			expr = lfirst(curdef);
+
+			append_string_object(tmp3, "value", expr);
+			curdef = lnext(curdef);
+		}
+		else
+			append_bool_object(tmp3, "present", false);
+		append_object_object(tmp2, "default", tmp3);
+
+		append_object_object(tmp2, "type",
+							 new_objtree_for_type(typarray[typnum++], -1));
+
+		params = lappend(params,
+						 new_object_object(NULL, tmp2));
+	}
+	append_array_object(sign, "arguments", params);
+	append_object_object(sign, "identity",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 objectId));
+	append_object_object(createFunc, "signature", sign);
+
+	/*
+	 * A return type can adopt one of two forms: either a [SETOF] some_type, or
+	 * a TABLE(list-of-types).  We can tell the second form because we saw a
+	 * table param above while scanning the argument list.
+	 */
+	if (table_params == NULL)
+	{
+		tmp = new_objtree_VA("%{setof}s %{rettype}T", 0);
+		append_string_object(tmp, "setof",
+							 procForm->proretset ? "SETOF" : "");
+		append_object_object(tmp, "rettype",
+							 new_objtree_for_type(procForm->prorettype, -1));
+		append_string_object(tmp, "return_form", "plain");
+	}
+	else
+	{
+		List	   *rettypes = NIL;
+		ObjTree	   *tmp2;
+
+		tmp = new_objtree_VA("TABLE (%{rettypes:, }s)", 0);
+		for (; table_params != NULL; table_params = lnext(table_params))
+		{
+			FunctionParameter *param = lfirst(table_params);
+
+			tmp2 = new_objtree_VA("%{name}I %{type}T", 0);
+			append_string_object(tmp2, "name", param->name);
+			append_object_object(tmp2, "type",
+								 new_objtree_for_type(typarray[typnum++], -1));
+			rettypes = lappend(rettypes,
+							   new_object_object(NULL, tmp2));
+		}
+
+		append_array_object(tmp, "rettypes", rettypes);
+		append_string_object(tmp, "return_form", "table");
+	}
+
+	append_object_object(createFunc, "return_type", tmp);
+
+	append_string_object(createFunc, "language",
+						 NameStr(langForm->lanname));
+
+	append_string_object(createFunc, "window",
+						 procForm->proiswindow ? "WINDOW" : "");
+	append_string_object(createFunc, "volatility",
+						 procForm->provolatile == PROVOLATILE_VOLATILE ?
+						 "VOLATILE" :
+						 procForm->provolatile == PROVOLATILE_STABLE ?
+						 "STABLE" :
+						 procForm->provolatile == PROVOLATILE_IMMUTABLE ?
+						 "IMMUTABLE" : "INVALID VOLATILITY");
+
+	append_string_object(createFunc, "leakproof",
+						 procForm->proleakproof ? "LEAKPROOF" : "");
+	append_string_object(createFunc, "strict",
+						 procForm->proisstrict ?
+						 "RETURNS NULL ON NULL INPUT" :
+						 "CALLED ON NULL INPUT");
+
+	append_string_object(createFunc, "security_definer",
+						 procForm->prosecdef ?
+						 "SECURITY DEFINER" : "SECURITY INVOKER");
+
+	append_object_object(createFunc, "cost",
+						 new_objtree_VA("COST %{cost}s", 1,
+										"cost", ObjTypeString,
+										psprintf("%f", procForm->procost)));
+
+	tmp = new_objtree_VA("ROWS %{rows}s", 0);
+	if (procForm->prorows == 0)
+		append_bool_object(tmp, "present", false);
+	else
+		append_string_object(tmp, "rows",
+							 psprintf("%f", procForm->prorows));
+	append_object_object(createFunc, "rows", tmp);
+
+	append_array_object(createFunc, "set_options", NIL);
+
+	if (probin == NULL)
+	{
+		append_string_object(createFunc, "definition",
+							 source);
+	}
+	else
+	{
+		append_string_object(createFunc, "objfile", probin);
+		append_string_object(createFunc, "symbol", source);
+	}
+
+	ReleaseSysCache(langTup);
+	ReleaseSysCache(procTup);
+	command = jsonize_objtree(createFunc);
+	free_objtree(createFunc);
+
+	return command;
+}
+
+/*
  * Return the given object type as a string.
  */
 static const char *
@@ -2620,6 +2936,9 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_CreateFunctionStmt:
+			command = deparse_CreateFunction(objectId, parsetree);
+			break;
+
 		case T_CreateTableAsStmt:
 		case T_CreatePLangStmt:
 		case T_CreateConversionStmt:
-- 
1.9.1

0023-deparse-initial-support-for-ALTER-TABLE.patchtext/x-diff; charset=us-asciiDownload
>From a03ea44bcbd18dd86b9e54ea3c8a1454ed1e0d59 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 25 Apr 2014 16:32:20 -0300
Subject: [PATCH 23/36] deparse: initial support for ALTER TABLE

---
 src/backend/commands/event_trigger.c | 117 ++++++++++-
 src/backend/commands/tablecmds.c     |   7 +
 src/backend/tcop/deparse_utility.c   | 397 ++++++++++++++++++++++++++++++++++-
 src/backend/tcop/utility.c           |  25 ++-
 src/include/commands/event_trigger.h |   5 +
 src/include/tcop/deparse_utility.h   |   9 +
 6 files changed, 557 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index b351af8..74fe67c 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -50,6 +50,7 @@ typedef struct EventTriggerQueryState
 	slist_head	SQLDropList;
 	bool		in_sql_drop;
 	MemoryContext cxt;
+	StashedCommand *curcmd;
 	List	   *stash;		/* list of StashedCommand; see deparse_utility.h */
 	bool		in_extension;
 	struct EventTriggerQueryState *previous;
@@ -1041,6 +1042,7 @@ EventTriggerBeginCompleteQuery(void)
 	state->cxt = cxt;
 	slist_init(&(state->SQLDropList));
 	state->in_sql_drop = false;
+	state->curcmd = NULL;
 	state->stash = NIL;
 	state->in_extension = currentEventTriggerState ? currentEventTriggerState->in_extension : false;
 
@@ -1340,6 +1342,113 @@ EventTriggerStashExtensionStop(void)
 		currentEventTriggerState->in_extension = false;
 }
 
+/*
+ * EventTriggerStartRecordingSubcmds
+ * 		Prepare to receive data on a complex DDL command about to be executed
+ *
+ * Note we don't actually stash the object we create here into the "stashed"
+ * list; instead we keep it in curcmd, and only when we're done processing the
+ * subcommands we will add it to the actual stash.
+ *
+ * FIXME -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?
+ */
+void
+EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype)
+{
+	MemoryContext	oldcxt;
+	StashedCommand *stashed;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	stashed = palloc(sizeof(StashedCommand));
+
+	stashed->type = SCT_AlterTable;
+	stashed->in_extension = currentEventTriggerState->in_extension;
+
+	stashed->d.alterTable.objectId = InvalidOid;
+	stashed->d.alterTable.objtype = objtype;
+	stashed->d.alterTable.subcmds = NIL;
+	/* XXX is it necessary to have the whole parsetree? probably not ... */
+	stashed->parsetree = copyObject(parsetree);
+
+	currentEventTriggerState->curcmd = stashed;
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+void
+EventTriggerComplexCmdSetOid(Oid objectId)
+{
+	currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerRecordSubcmd
+ * 		Save data about a single part of a complex DDL command
+ *
+ * Right now we only support ALTER TABLE; there are no other DDL commands that
+ * require this.  (ALTER TYPE can also generate multiple subcommands, but it's
+ * actually parsed as ALTER TABLE, so there is no difference at this level.)
+ */
+void
+EventTriggerRecordSubcmd(Node *subcmd, Oid relid, AttrNumber attnum,
+						 Oid newoid)
+{
+	MemoryContext	oldcxt;
+	StashedATSubcmd *newsub;
+
+	Assert(IsA(subcmd, AlterTableCmd));
+	Assert(OidIsValid(currentEventTriggerState->curcmd->d.alterTable.objectId));
+
+	/*
+	 * If we receive a subcommand intended for a relation other than the one
+	 * we've started the complex command for, ignore it.  This is chiefly
+	 * concerned with inheritance situations: in such cases, alter table
+	 * would dispatch multiple copies of the same command for various things,
+	 * but we're only concerned with the one for the main table.
+	 */
+	if (relid != currentEventTriggerState->curcmd->d.alterTable.objectId)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	newsub = palloc(sizeof(StashedATSubcmd));
+	newsub->attnum = attnum;
+	newsub->oid = newoid;
+	newsub->parsetree = copyObject(subcmd);
+
+	currentEventTriggerState->curcmd->d.alterTable.subcmds =
+		lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerEndRecordingSubcmds
+ * 		Finish up saving a complex DDL command
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerComplexCmdEnd(void)
+{
+	/* If no subcommands, don't stash anything */
+	if (list_length(currentEventTriggerState->curcmd->d.alterTable.subcmds) != 0)
+	{
+		currentEventTriggerState->stash =
+			lappend(currentEventTriggerState->stash,
+					currentEventTriggerState->curcmd);
+	}
+	else
+		pfree(currentEventTriggerState->curcmd);
+
+	currentEventTriggerState->curcmd = NULL;
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1418,7 +1527,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
 			MemSet(nulls, 0, sizeof(nulls));
 
-			if (cmd->type == SCT_Basic)
+			if (cmd->type == SCT_Basic ||
+				cmd->type == SCT_AlterTable)
 			{
 				Oid			classId;
 				Oid			objId;
@@ -1432,6 +1542,11 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 					classId = get_objtype_catalog_oid(cmd->d.basic.objtype);
 					objId = cmd->d.basic.objectId;
 				}
+				else if (cmd->type == SCT_AlterTable)
+				{
+					classId = get_objtype_catalog_oid(cmd->d.alterTable.objtype);
+					objId = cmd->d.alterTable.objectId;
+				}
 
 				tag = CreateCommandTag(cmd->parsetree);
 				addr.classId = classId;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46dcf0e..cab0b0b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2728,6 +2728,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
 	rel = relation_open(relid, lockmode);
 
+	EventTriggerComplexCmdSetOid(relid);
+
 	ATController(rel, cmds, recurse, lockmode);
 }
 
@@ -3553,6 +3555,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			break;
 	}
 
+	EventTriggerRecordSubcmd((Node *) cmd, RelationGetRelid(rel),
+							 colno, newoid);
+
 	/*
 	 * Bump the command counter to ensure the next subcommand in the sequence
 	 * can see the changes so far
@@ -5615,6 +5620,8 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
  * There is no such command in the grammar, but parse_utilcmd.c converts
  * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
  * us schedule creation of the index at the appropriate time during ALTER.
+ *
+ * Return value is the OID of the new index.
  */
 static Oid
 ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 2412843..040c721 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -2846,6 +2846,396 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+	ObjTree	   *alterTableStmt;
+	ObjTree	   *tmp;
+	ObjTree	   *tmp2;
+	List	   *dpcontext;
+	Relation	rel;
+	List	   *subcmds = NIL;
+	ListCell   *cell;
+	char	   *command;
+
+	rel = heap_open(cmd->d.alterTable.objectId, AccessShareLock);
+	dpcontext = deparse_context_for(RelationGetRelationName(rel),
+									cmd->d.alterTable.objectId);
+
+	alterTableStmt =
+		new_objtree_VA("ALTER TABLE %{identity}D %{subcmds:, }s", 0);
+	tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace,
+								   RelationGetRelationName(rel));
+	append_object_object(alterTableStmt, "identity", tmp);
+
+	foreach(cell, cmd->d.alterTable.subcmds)
+	{
+		StashedATSubcmd	*substashed = (StashedATSubcmd *) lfirst(cell);
+		AlterTableCmd	*subcmd = (AlterTableCmd *) substashed->parsetree;
+		ObjTree	   *tree;
+
+		Assert(IsA(subcmd, AlterTableCmd));
+
+		switch (subcmd->subtype)
+		{
+			case AT_AddColumn:
+			case AT_AddColumnRecurse:
+				/* XXX need to set the "recurse" bit somewhere? */
+				Assert(IsA(subcmd->def, ColumnDef));
+				tree = deparse_ColumnDef(rel, dpcontext, false,
+										 (ColumnDef *) subcmd->def);
+				tmp = new_objtree_VA("ADD COLUMN %{definition}s",
+									 2, "type", ObjTypeString, "add column",
+									 "definition", ObjTypeObject, tree);
+				subcmds = lappend(subcmds,
+								  new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropColumnRecurse:
+			case AT_ValidateConstraintRecurse:
+			case AT_DropConstraintRecurse:
+			case AT_AddOidsRecurse:
+			case AT_AddIndexConstraint:
+			case AT_ReAddIndex:
+			case AT_ReAddConstraint:
+			case AT_ProcessedConstraint:
+			case AT_ReplaceRelOptions:
+				/* Subtypes used for internal operations; nothing to do here */
+				break;
+
+			case AT_AddColumnToView:
+				/* CREATE OR REPLACE VIEW -- nothing to do here */
+				break;
+
+			case AT_ColumnDefault:
+				if (subcmd->def == NULL)
+				{
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT",
+										 1, "type", ObjTypeString, "drop default");
+				}
+				else
+				{
+					List	   *dpcontext;
+					HeapTuple	attrtup;
+					AttrNumber	attno;
+
+					tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT %{definition}s",
+										 1, "type", ObjTypeString, "set default");
+
+					dpcontext = deparse_context_for(RelationGetRelationName(rel),
+													RelationGetRelid(rel));
+					attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name);
+					attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+					append_string_object(tmp, "definition",
+										 RelationGetColumnDefault(rel, attno, dpcontext));
+					ReleaseSysCache(attrtup);
+				}
+				append_string_object(tmp, "column", subcmd->name);
+
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropNotNull:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL",
+									 1, "type", ObjTypeString, "drop not null");
+				append_string_object(tmp, "column", subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetNotNull:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL",
+									 1, "type", ObjTypeString, "set not null");
+				append_string_object(tmp, "column", subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetStatistics:
+				/* not yet */
+				break;
+
+			case AT_SetOptions:
+				/* not yet */
+				break;
+
+			case AT_ResetOptions:
+				/* not yet */
+				break;
+
+			case AT_SetStorage:
+				Assert(IsA(subcmd->def, String));
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s",
+									 3, "type", ObjTypeString, "set storage",
+									 "column", ObjTypeString, subcmd->name,
+									 "storage", ObjTypeString,
+									 strVal((Value *) subcmd->def));
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropColumn:
+				tmp = new_objtree_VA("DROP COLUMN %{column}I",
+									 2, "type", ObjTypeString, "drop column",
+								 "column", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AddIndex:
+				{
+					Oid			idxOid = substashed->oid;
+					IndexStmt  *istmt;
+					Relation	idx;
+					const char *idxname;
+					Oid			constrOid;
+
+					Assert(IsA(subcmd->def, IndexStmt));
+					istmt = (IndexStmt *) subcmd->def;
+
+					if (!istmt->isconstraint)
+						break;
+
+					idx = relation_open(idxOid, AccessShareLock);
+					idxname = RelationGetRelationName(idx);
+
+					constrOid = get_relation_constraint_oid(
+						cmd->d.alterTable.objectId, idxname, false);
+
+					tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+										 3, "type", ObjTypeString, "add constraint",
+										 "name", ObjTypeString, idxname,
+										 "definition", ObjTypeString,
+										 pg_get_constraintdef_string(constrOid, false));
+					subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+
+					relation_close(idx, AccessShareLock);
+				}
+				break;
+
+			case AT_AddConstraint:
+			case AT_AddConstraintRecurse:
+				{
+					/* XXX need to set the "recurse" bit somewhere? */
+					Oid			constrOid = substashed->oid;
+
+					tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+										 3, "type", ObjTypeString, "add constraint",
+										 "name", ObjTypeString, get_constraint_name(constrOid),
+										 "definition", ObjTypeString,
+										 pg_get_constraintdef_string(constrOid, false));
+					subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				}
+				break;
+
+			case AT_AlterConstraint:
+				break;
+
+			case AT_ValidateConstraint:
+				tmp = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2,
+									 "type", ObjTypeString, "validate constraint",
+									 "constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropConstraint:
+				tmp = new_objtree_VA("DROP CONSTRAINT %{constraint}I", 2,
+									 "type", ObjTypeString, "drop constraint",
+									 "constraint", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AlterColumnType:
+				tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T collate_clause using_clause",
+									 2, "type", ObjTypeString, "alter column type",
+									 "column", ObjTypeString, subcmd->name);
+				/* FIXME figure out correct typid/typmod , collate clause, using_clause */
+				append_object_object(tmp, "datatype",
+									 new_objtree_for_type(INT4OID, -1));
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AlterColumnGenericOptions:
+				break;
+
+			case AT_ChangeOwner:
+				tmp = new_objtree_VA("OWNER TO %{owner}I",
+									 2, "type", ObjTypeString, "change owner",
+									 "owner",  ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_ClusterOn:
+				tmp = new_objtree_VA("CLUSTER ON %{index}I", 2,
+									 "type", ObjTypeString, "cluster on",
+									 "index", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropCluster:
+				tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+									 "type", ObjTypeString, "set without cluster");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_AddOids:
+				tmp = new_objtree_VA("SET WITH OIDS", 1,
+									 "type", ObjTypeString, "set with oids");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DropOids:
+				tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+									 "type", ObjTypeString, "set without oids");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetTableSpace:
+				tmp = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2,
+									 "type", ObjTypeString, "set tablespace",
+									 "tablespace", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_SetRelOptions:
+				break;
+
+			case AT_ResetRelOptions:
+				break;
+
+				/*
+				 * FIXME --- should we unify representation of all these
+				 * ENABLE/DISABLE TRIGGER commands??
+				 */
+			case AT_EnableTrig:
+				tmp = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableAlwaysTrig:
+				tmp = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable always trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableReplicaTrig:
+				tmp = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "enable replica trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrig:
+				tmp = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2,
+									 "type", ObjTypeString, "disable trigger",
+									 "trigger", ObjTypeString, subcmd->name);
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableTrigAll:
+				tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "enable trigger all");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrigAll:
+				tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+									 "type", ObjTypeString, "disable trigger all");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableTrigUser:
+				tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "enable trigger user");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_DisableTrigUser:
+				tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+									 "type", ObjTypeString, "disable trigger user");
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_EnableRule:
+				break;
+
+			case AT_EnableAlwaysRule:
+				break;
+
+			case AT_EnableReplicaRule:
+				break;
+
+			case AT_DisableRule:
+				break;
+
+			case AT_AddInherit:
+				/*
+				 * XXX this case is interesting: we cannot rely on parse node
+				 * because parent name might be unqualified; but there's no way
+				 * to extract it from catalog either, since we don't know which
+				 * of the parents is the new one.
+				 */
+				break;
+
+			case AT_DropInherit:
+				/* XXX ditto ... */
+				break;
+
+			case AT_AddOf:
+				break;
+
+			case AT_DropOf:
+				break;
+
+			case AT_ReplicaIdentity:
+				tmp = new_objtree_VA("REPLICA IDENTITY %{ident}s", 1,
+									 "type", ObjTypeString, "replica identity");
+				switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+				{
+					case REPLICA_IDENTITY_DEFAULT:
+						append_string_object(tmp, "ident", "DEFAULT");
+						break;
+					case REPLICA_IDENTITY_FULL:
+						append_string_object(tmp, "ident", "FULL");
+						break;
+					case REPLICA_IDENTITY_NOTHING:
+						append_string_object(tmp, "ident", "NOTHING");
+						break;
+					case REPLICA_IDENTITY_INDEX:
+						tmp2 = new_objtree_VA("USING INDEX %{index}I", 1,
+											  "index", ObjTypeString,
+											  ((ReplicaIdentityStmt *) subcmd->def)->name);
+						append_object_object(tmp, "ident", tmp2);
+						break;
+				}
+				subcmds = lappend(subcmds, new_object_object(NULL, tmp));
+				break;
+
+			case AT_GenericOptions:
+				break;
+
+			default:
+				elog(WARNING, "unsupported alter table subtype %d",
+					 subcmd->subtype);
+				break;
+		}
+	}
+
+	if (list_length(subcmds) == 0)
+	{
+		command = NULL;
+	}
+	else
+	{
+		append_array_object(alterTableStmt, "subcmds", subcmds);
+		command = jsonize_objtree(alterTableStmt);
+	}
+
+	free_objtree(alterTableStmt);
+	heap_close(rel, AccessShareLock);
+
+	return command;
+}
+
+static char *
 deparse_parsenode_cmd(StashedCommand *cmd)
 {
 	Oid			objectId;
@@ -2859,6 +3249,10 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 		case SCT_Basic:
 			objectId = cmd->d.basic.objectId;
 			break;
+		case SCT_AlterTable:
+			/* XXX needed? */
+			objectId = cmd->d.alterTable.objectId;
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
@@ -2954,7 +3348,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 			break;
 
 		case T_AlterTableStmt:
-			command = NULL;
+			command = deparse_AlterTableStmt(cmd);
 			break;
 
 		case T_AlterEnumStmt:
@@ -3013,6 +3407,7 @@ deparse_utility_command(StashedCommand *cmd)
 	switch (cmd->type)
 	{
 		case SCT_Basic:
+		case SCT_AlterTable:
 			command = deparse_parsenode_cmd(cmd);
 			break;
 		default:
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index beedb01..03547a6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -982,6 +982,10 @@ ProcessUtilitySlow(Node *parsetree,
 						stmts = transformAlterTableStmt(relid, atstmt,
 														queryString);
 
+						/* ... ensure we have an event trigger context ... */
+						EventTriggerComplexCmdStart(parsetree, atstmt->relkind);
+						EventTriggerComplexCmdSetOid(relid);
+
 						/* ... and do it */
 						foreach(l, stmts)
 						{
@@ -995,19 +999,32 @@ ProcessUtilitySlow(Node *parsetree,
 							}
 							else
 							{
-								/* Recurse for anything else */
+								/*
+								 * Recurse for anything else.  If we need to do
+								 * so, "close" the current complex-command set,
+								 * and start a new one at the bottom; this is
+								 * needed to ensure the ordering of queued
+								 * commands is consistent with the way they are
+								 * executed here.
+								 */
+								EventTriggerComplexCmdEnd();
 								ProcessUtility(stmt,
 											   queryString,
 											   PROCESS_UTILITY_SUBCOMMAND,
 											   params,
 											   None_Receiver,
 											   NULL);
+								EventTriggerComplexCmdStart(parsetree, atstmt->relkind);
+								EventTriggerComplexCmdSetOid(relid);
 							}
 
 							/* Need CCI between commands */
 							if (lnext(l) != NULL)
 								CommandCounterIncrement();
 						}
+
+						/* done */
+						EventTriggerComplexCmdEnd();
 					}
 					else
 						ereport(NOTICE,
@@ -1156,6 +1173,7 @@ ProcessUtilitySlow(Node *parsetree,
 					stmt = transformIndexStmt(relid, stmt, queryString);
 
 					/* ... and do it */
+					EventTriggerComplexCmdStart(parsetree, OBJECT_INDEX);	/* relkind? */
 					objectId =
 						DefineIndex(relid,	/* OID of heap relation */
 									stmt,
@@ -1166,6 +1184,7 @@ ProcessUtilitySlow(Node *parsetree,
 									false); /* quiet */
 					EventTriggerStashCommand(objectId, OBJECT_INDEX,
 											 parsetree);
+					EventTriggerComplexCmdEnd();
 				}
 				break;
 
@@ -1249,8 +1268,10 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_ViewStmt:	/* CREATE VIEW */
+				EventTriggerComplexCmdStart(parsetree, OBJECT_VIEW);	/* XXX relkind? */
 				objectId = DefineView((ViewStmt *) parsetree, queryString);
 				EventTriggerStashCommand(objectId, OBJECT_VIEW, parsetree);
+				EventTriggerComplexCmdEnd();
 				break;
 
 			case T_CreateFunctionStmt:	/* CREATE FUNCTION */
@@ -1377,7 +1398,9 @@ ProcessUtilitySlow(Node *parsetree,
 				break;
 
 			case T_AlterTableSpaceMoveStmt:
+				EventTriggerComplexCmdStart(parsetree, OBJECT_TABLE);	/* XXX relkind? */
 				AlterTableSpaceMove((AlterTableSpaceMoveStmt *) parsetree);
+				EventTriggerComplexCmdEnd();
 				break;
 
 			case T_AlterOwnerStmt:
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 8ddca3d..3e84cf7 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -56,5 +56,10 @@ extern void EventTriggerStashExtensionStart(void);
 extern void EventTriggerStashExtensionStop(void);
 extern void EventTriggerStashCommand(Oid objectId, ObjectType objtype,
 						 Node *parsetree);
+extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype);
+extern void EventTriggerComplexCmdSetOid(Oid objectId);
+extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid,
+						 AttrNumber attnum, Oid newoid);
+extern void EventTriggerComplexCmdEnd(void);
 
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 865e018..37b38f0 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -26,6 +26,7 @@
 typedef enum StashedCommandType
 {
 	SCT_Basic,
+	SCT_AlterTable
 } StashedCommandType;
 
 /*
@@ -42,6 +43,7 @@ typedef struct StashedCommand
 {
 	StashedCommandType type;
 	bool		in_extension;
+	List	   *subcmds;	/* list of StashedATSubcmd */
 	Node	   *parsetree;
 
 	union
@@ -51,6 +53,13 @@ typedef struct StashedCommand
 			Oid			objectId;
 			ObjectType	objtype;
 		} basic;
+
+		struct AlterTableCommand
+		{
+			Oid		objectId;
+			ObjectType objtype;
+			List   *subcmds;
+		} alterTable;
 	} d;
 } StashedCommand;
 
-- 
1.9.1

0024-deparse-Support-CREATE-OPERATOR-FAMILY.patchtext/x-diff; charset=us-asciiDownload
>From e71d30d3e066a44fca77458d76574da1710b89b4 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Wed, 30 Apr 2014 12:37:41 +0530
Subject: [PATCH 24/36] deparse: Support CREATE OPERATOR FAMILY

---
 src/backend/tcop/deparse_utility.c | 45 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 040c721..09b5215 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_language.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
@@ -2846,6 +2847,45 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_CreateOpFamily(Oid objectId, Node *parsetree)
+{
+	HeapTuple   opfTup;
+	HeapTuple   amTup;
+	Form_pg_opfamily opfForm;
+	Form_pg_am  amForm;
+	ObjTree	   *copfStmt;
+	ObjTree	   *tmp;
+	char	   *command;
+
+	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(opfTup))
+		elog(ERROR, "cache lookup failed for operator family with OID %u", objectId);
+	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
+
+	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
+	if (!HeapTupleIsValid(amTup))
+		elog(ERROR, "cache lookup failed for access method %u",
+			 opfForm->opfmethod);
+	amForm = (Form_pg_am) GETSTRUCT(amTup);
+
+	copfStmt = new_objtree_VA("CREATE OPERATOR FAMILY %{identity}D USING %{amname}s",
+							  0);
+
+	tmp = new_objtree_for_qualname(opfForm->opfnamespace,
+								   NameStr(opfForm->opfname));
+	append_object_object(copfStmt, "identity", tmp);
+	append_string_object(copfStmt, "amname", NameStr(amForm->amname));
+
+	command = jsonize_objtree(copfStmt);
+	free_objtree(copfStmt);
+
+	ReleaseSysCache(amTup);
+	ReleaseSysCache(opfTup);
+
+	return command;
+}
+
+static char *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -3338,10 +3378,13 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 		case T_CreateConversionStmt:
 		case T_CreateCastStmt:
 		case T_CreateOpClassStmt:
-		case T_CreateOpFamilyStmt:
 			command = NULL;
 			break;
 
+		case T_CreateOpFamilyStmt:
+			command = deparse_CreateOpFamily(objectId, parsetree);
+			break;
+
 			/* matviews */
 		case T_RefreshMatViewStmt:
 			command = NULL;
-- 
1.9.1

0025-deparse-Support-CREATE-CONVERSION.patchtext/x-diff; charset=us-asciiDownload
>From 3b8a7c993b0183cfd861ce6e8992bd2affbeb983 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Wed, 30 Apr 2014 17:30:07 +0530
Subject: [PATCH 25/36] deparse: Support CREATE CONVERSION

---
 src/backend/tcop/deparse_utility.c | 44 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 09b5215..458e5d1 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
+#include "catalog/pg_conversion.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_inherits.h"
@@ -52,6 +53,7 @@
 #include "funcapi.h"
 #include "lib/ilist.h"
 #include "lib/stringinfo.h"
+#include "mb/pg_wchar.h"
 #include "nodes/makefuncs.h"
 #include "nodes/parsenodes.h"
 #include "parser/analyze.h"
@@ -2847,6 +2849,43 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
 }
 
 static char *
+deparse_CreateConversion(Oid objectId, Node *parsetree)
+{
+	HeapTuple   conTup;
+	Form_pg_conversion conForm;
+	ObjTree	   *ccStmt;
+	char	   *command;
+
+	conTup = SearchSysCache1(CONDEFAULT, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(conTup))
+		elog(ERROR, "cache lookup failed for conversion with OID %u", objectId);
+	conForm = (Form_pg_conversion) GETSTRUCT(conTup);
+
+	ccStmt = new_objtree_VA("CREATE %{default}s CONVERSION %{identity}D FOR "
+							"%{source}L TO %{dest}L FROM %{function}D", 0);
+
+	append_string_object(ccStmt, "default",
+						 conForm->condefault ? "DEFAULT" : "");
+	append_object_object(ccStmt, "identity",
+						 new_objtree_for_qualname(conForm->connamespace,
+												  NameStr(conForm->conname)));
+	append_string_object(ccStmt, "source", (char *)
+						 pg_encoding_to_char(conForm->conforencoding));
+	append_string_object(ccStmt, "dest", (char *)
+						 pg_encoding_to_char(conForm->contoencoding));
+	append_object_object(ccStmt, "function",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 conForm->conproc));
+
+	command = jsonize_objtree(ccStmt);
+	free_objtree(ccStmt);
+
+	ReleaseSysCache(conTup);
+
+	return command;
+}
+
+static char *
 deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 {
 	HeapTuple   opfTup;
@@ -3375,12 +3414,15 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 
 		case T_CreateTableAsStmt:
 		case T_CreatePLangStmt:
-		case T_CreateConversionStmt:
 		case T_CreateCastStmt:
 		case T_CreateOpClassStmt:
 			command = NULL;
 			break;
 
+		case T_CreateConversionStmt:
+			command = deparse_CreateConversion(objectId, parsetree);
+			break;
+
 		case T_CreateOpFamilyStmt:
 			command = deparse_CreateOpFamily(objectId, parsetree);
 			break;
-- 
1.9.1

0026-deparse-Support-CREATE-OPERATOR-via-DefineStmt.patchtext/x-diff; charset=us-asciiDownload
>From b83fffb0e07b32d3a709d0cab386d59135b4c504 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 5 May 2014 12:20:58 +0530
Subject: [PATCH 26/36] deparse: Support CREATE OPERATOR via DefineStmt

---
 src/backend/tcop/deparse_utility.c | 127 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 126 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 458e5d1..8ab47b1 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -632,6 +632,131 @@ get_persistence_str(char persistence)
 	}
 }
 
+static ObjTree *
+deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   oprTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	Form_pg_operator oprForm;
+
+	oprTup = SearchSysCache1(OPEROID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(oprTup))
+		elog(ERROR, "cache lookup failed for operator with OID %u", objectId);
+	oprForm = (Form_pg_operator) GETSTRUCT(oprTup);
+
+	stmt = new_objtree_VA("CREATE OPERATOR %{identity}O (%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(oprForm->oprnamespace,
+												  NameStr(oprForm->oprname)));
+
+	list = NIL;
+
+	tmp = new_objtree_VA("PROCEDURE=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 oprForm->oprcode));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	if (OidIsValid(oprForm->oprleft))
+	{
+		tmp = new_objtree_VA("LEFTARG=%{type}T", 0);
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprleft, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprright))
+	{
+		tmp = new_objtree_VA("RIGHTARG=%{type}T", 0);
+		append_object_object(tmp, "type",
+							 new_objtree_for_type(oprForm->oprright, -1));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprcom))
+	{
+		tmp = new_objtree_VA("COMMUTATOR=%{oper}O", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprcom));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprnegate))
+	{
+		tmp = new_objtree_VA("NEGATOR=%{oper}O", 0);
+		append_object_object(tmp, "oper",
+							 new_objtree_for_qualname_id(OperatorRelationId,
+														 oprForm->oprnegate));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprrest))
+	{
+		tmp = new_objtree_VA("RESTRICT=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprrest));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (OidIsValid(oprForm->oprjoin))
+	{
+		tmp = new_objtree_VA("JOIN=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 oprForm->oprjoin));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	if (oprForm->oprcanmerge)
+		list = lappend(list, new_object_object(NULL,
+											   new_objtree_VA("MERGES", 0)));
+	if (oprForm->oprcanhash)
+		list = lappend(list, new_object_object(NULL,
+											   new_objtree_VA("HASHES", 0)));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(oprTup);
+
+	return stmt;
+}
+
+static char *
+deparse_DefineStmt(Oid objectId, Node *parsetree)
+{
+	DefineStmt *define = (DefineStmt *) parsetree;
+	ObjTree	   *defStmt;
+	char	   *command;
+
+	switch (define->kind)
+	{
+		case OBJECT_OPERATOR:
+			defStmt = deparse_DefineStmt_Operator(objectId, define);
+			break;
+
+		default:
+		case OBJECT_AGGREGATE:
+		case OBJECT_TYPE:
+		case OBJECT_TSPARSER:
+		case OBJECT_TSDICTIONARY:
+		case OBJECT_TSTEMPLATE:
+		case OBJECT_TSCONFIGURATION:
+		case OBJECT_COLLATION:
+			elog(ERROR, "unsupported object kind");
+			return NULL;
+	}
+
+	command = jsonize_objtree(defStmt);
+	free_objtree(defStmt);
+
+	return command;
+}
+
 /*
  * deparse_CreateExtensionStmt
  *		deparse a CreateExtensionStmt
@@ -3381,7 +3506,7 @@ deparse_parsenode_cmd(StashedCommand *cmd)
 
 			/* other local objects */
 		case T_DefineStmt:
-			command = NULL;
+			command = deparse_DefineStmt(objectId, parsetree);
 			break;
 
 		case T_CreateExtensionStmt:
-- 
1.9.1

0027-deparse-Support-CREATE-COLLATION-via-DefineStmt.patchtext/x-diff; charset=us-asciiDownload
>From 0bc63764436a52c07c6729471438096b25fca02f Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 5 May 2014 13:22:58 +0530
Subject: [PATCH 27/36] deparse: Support CREATE COLLATION via DefineStmt

---
 src/backend/tcop/deparse_utility.c | 32 +++++++++++++++++++++++++++++++-
 1 file changed, 31 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 8ab47b1..a6ca3cc 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -633,6 +633,33 @@ get_persistence_str(char persistence)
 }
 
 static ObjTree *
+deparse_DefineStmt_Collation(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   colTup;
+	ObjTree	   *stmt;
+	Form_pg_collation colForm;
+
+	colTup = SearchSysCache1(COLLOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(colTup))
+		elog(ERROR, "cache lookup failed for collation with OID %u", objectId);
+	colForm = (Form_pg_collation) GETSTRUCT(colTup);
+
+	stmt = new_objtree_VA("CREATE COLLATION %{identity}O "
+						  "(LC_COLLATE = %{collate}L,"
+						  " LC_CTYPE = %{ctype}L)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(colForm->collnamespace,
+												  NameStr(colForm->collname)));
+	append_string_object(stmt, "collate", NameStr(colForm->collcollate));
+	append_string_object(stmt, "ctype", NameStr(colForm->collctype));
+
+	ReleaseSysCache(colTup);
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define)
 {
 	HeapTuple   oprTup;
@@ -735,6 +762,10 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 
 	switch (define->kind)
 	{
+		case OBJECT_COLLATION:
+			defStmt = deparse_DefineStmt_Collation(objectId, define);
+			break;
+
 		case OBJECT_OPERATOR:
 			defStmt = deparse_DefineStmt_Operator(objectId, define);
 			break;
@@ -746,7 +777,6 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_TSCONFIGURATION:
-		case OBJECT_COLLATION:
 			elog(ERROR, "unsupported object kind");
 			return NULL;
 	}
-- 
1.9.1

0028-deparse-Support-CREATE-TEXT-SEARCH-TEMPLATE-via-Defi.patchtext/x-diff; charset=us-asciiDownload
>From 0f77fe48ea93cb9761168c70218fd9b14071993f Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 5 May 2014 14:12:18 +0530
Subject: [PATCH 28/36] deparse: Support CREATE TEXT SEARCH TEMPLATE via
 DefineStmt

---
 src/backend/tcop/deparse_utility.c | 52 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 51 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index a6ca3cc..777fd55 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/sequence.h"
@@ -753,6 +754,52 @@ deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define)
 	return stmt;
 }
 
+static ObjTree *
+deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   tstTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	Form_pg_ts_template tstForm;
+
+	tstTup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tstTup))
+		elog(ERROR, "cache lookup failed for text search template with OID %u",
+			 objectId);
+	tstForm = (Form_pg_ts_template) GETSTRUCT(tstTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH TEMPLATE %{identity}D "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tstForm->tmplnamespace,
+												  NameStr(tstForm->tmplname)));
+
+	list = NIL;
+
+	if (OidIsValid(tstForm->tmplinit))
+	{
+		tmp = new_objtree_VA("INIT=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tstForm->tmplinit));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	tmp = new_objtree_VA("LEXIZE=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tstForm->tmpllexize));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tstTup);
+
+	return stmt;
+}
+
 static char *
 deparse_DefineStmt(Oid objectId, Node *parsetree)
 {
@@ -770,12 +817,15 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 			defStmt = deparse_DefineStmt_Operator(objectId, define);
 			break;
 
+		case OBJECT_TSTEMPLATE:
+			defStmt = deparse_DefineStmt_TSTemplate(objectId, define);
+			break;
+
 		default:
 		case OBJECT_AGGREGATE:
 		case OBJECT_TYPE:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSDICTIONARY:
-		case OBJECT_TSTEMPLATE:
 		case OBJECT_TSCONFIGURATION:
 			elog(ERROR, "unsupported object kind");
 			return NULL;
-- 
1.9.1

0029-deparse-Support-CREATE-TEXT-SEARCH-PARSER-via-Define.patchtext/x-diff; charset=us-asciiDownload
>From 268455aba42d647e2fd31916f4d12afcbcb09ab0 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 5 May 2014 14:21:42 +0530
Subject: [PATCH 29/36] deparse: Support CREATE TEXT SEARCH PARSER via
 DefineStmt

---
 src/backend/tcop/deparse_utility.c | 70 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 69 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 777fd55..4e499cf 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -755,6 +756,70 @@ deparse_DefineStmt_Operator(Oid objectId, DefineStmt *define)
 }
 
 static ObjTree *
+deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   tspTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	Form_pg_ts_parser tspForm;
+
+	tspTup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tspTup))
+		elog(ERROR, "cache lookup failed for text search parser with OID %u",
+			 objectId);
+	tspForm = (Form_pg_ts_parser) GETSTRUCT(tspTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH PARSER %{identity}D "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tspForm->prsnamespace,
+												  NameStr(tspForm->prsname)));
+
+	list = NIL;
+
+	tmp = new_objtree_VA("START=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prsstart));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("GETTOKEN=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prstoken));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("END=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prsend));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	tmp = new_objtree_VA("LEXTYPES=%{procedure}D", 0);
+	append_object_object(tmp, "procedure",
+						 new_objtree_for_qualname_id(ProcedureRelationId,
+													 tspForm->prslextype));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	if (OidIsValid(tspForm->prsheadline))
+	{
+		tmp = new_objtree_VA("HEADLINE=%{procedure}D", 0);
+		append_object_object(tmp, "procedure",
+							 new_objtree_for_qualname_id(ProcedureRelationId,
+														 tspForm->prsheadline));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tspTup);
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define)
 {
 	HeapTuple   tstTup;
@@ -817,6 +882,10 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 			defStmt = deparse_DefineStmt_Operator(objectId, define);
 			break;
 
+		case OBJECT_TSPARSER:
+			defStmt = deparse_DefineStmt_TSParser(objectId, define);
+			break;
+
 		case OBJECT_TSTEMPLATE:
 			defStmt = deparse_DefineStmt_TSTemplate(objectId, define);
 			break;
@@ -824,7 +893,6 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 		default:
 		case OBJECT_AGGREGATE:
 		case OBJECT_TYPE:
-		case OBJECT_TSPARSER:
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
 			elog(ERROR, "unsupported object kind");
-- 
1.9.1

0030-deparse-Support-CREATE-TEXT-SEARCH-DICTIONARY-via-De.patchtext/x-diff; charset=us-asciiDownload
>From 2757be542d642065cdc3024f9f3bac32a0b7f827 Mon Sep 17 00:00:00 2001
From: Abhijit Menon-Sen <ams@2ndQuadrant.com>
Date: Mon, 5 May 2014 14:44:18 +0530
Subject: [PATCH 30/36] deparse: Support CREATE TEXT SEARCH DICTIONARY via
 DefineStmt

---
 src/backend/tcop/deparse_utility.c | 55 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 54 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 4e499cf..9a325ac 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_trigger.h"
+#include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
@@ -820,6 +821,55 @@ deparse_DefineStmt_TSParser(Oid objectId, DefineStmt *define)
 }
 
 static ObjTree *
+deparse_DefineStmt_TSDictionary(Oid objectId, DefineStmt *define)
+{
+	HeapTuple   tsdTup;
+	ObjTree	   *stmt;
+	ObjTree	   *tmp;
+	List	   *list;
+	Datum		options;
+	bool		isnull;
+	Form_pg_ts_dict tsdForm;
+
+	tsdTup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(objectId));
+	if (!HeapTupleIsValid(tsdTup))
+		elog(ERROR, "cache lookup failed for text search dictionary "
+			 "with OID %u", objectId);
+	tsdForm = (Form_pg_ts_dict) GETSTRUCT(tsdTup);
+
+	stmt = new_objtree_VA("CREATE TEXT SEARCH DICTIONARY %{identity}D "
+						  "(%{elems:, }s)", 0);
+
+	append_object_object(stmt, "identity",
+						 new_objtree_for_qualname(tsdForm->dictnamespace,
+												  NameStr(tsdForm->dictname)));
+
+	list = NIL;
+
+	tmp = new_objtree_VA("TEMPLATE=%{template}D", 0);
+	append_object_object(tmp, "template",
+						 new_objtree_for_qualname_id(TSTemplateRelationId,
+													 tsdForm->dicttemplate));
+	list = lappend(list, new_object_object(NULL, tmp));
+
+	options = SysCacheGetAttr(TSDICTOID, tsdTup,
+							  Anum_pg_ts_dict_dictinitoption,
+							  &isnull);
+	if (!isnull)
+	{
+		tmp = new_objtree_VA("%{options}s", 0);
+		append_string_object(tmp, "options", TextDatumGetCString(options));
+		list = lappend(list, new_object_object(NULL, tmp));
+	}
+
+	append_array_object(stmt, "elems", list);
+
+	ReleaseSysCache(tsdTup);
+
+	return stmt;
+}
+
+static ObjTree *
 deparse_DefineStmt_TSTemplate(Oid objectId, DefineStmt *define)
 {
 	HeapTuple   tstTup;
@@ -886,6 +936,10 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 			defStmt = deparse_DefineStmt_TSParser(objectId, define);
 			break;
 
+		case OBJECT_TSDICTIONARY:
+			defStmt = deparse_DefineStmt_TSDictionary(objectId, define);
+			break;
+
 		case OBJECT_TSTEMPLATE:
 			defStmt = deparse_DefineStmt_TSTemplate(objectId, define);
 			break;
@@ -893,7 +947,6 @@ deparse_DefineStmt(Oid objectId, Node *parsetree)
 		default:
 		case OBJECT_AGGREGATE:
 		case OBJECT_TYPE:
-		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSCONFIGURATION:
 			elog(ERROR, "unsupported object kind");
 			return NULL;
-- 
1.9.1

#50Jim Nasby
jim@nasby.net
In reply to: Alvaro Herrera (#46)
Re: Add CREATE support to event triggers

On 2/6/14, 11:20 AM, Alvaro Herrera wrote:

NOTICE: JSON blob: {
"definition": [
{
"clause": "owned",
"fmt": "OWNED BY %{owner}D",
"owner": {
"attrname": "a",
"objname": "t1",
"schemaname": "public"
}
}
],
"fmt": "ALTER SEQUENCE %{identity}D %{definition: }s",
"identity": {
"objname": "t1_a_seq",
"schemaname": "public"
}
}
NOTICE: expanded: ALTER SEQUENCE public.t1_a_seq OWNED BY public.t1.a

Apologies if this has been discussed and I missed it, but shouldn't part of the JSON be a field that indicates what command is being run? It doesn't seem wise to conflate detecting what the command is with the overall format string.
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Jim Nasby (#50)
Re: Add CREATE support to event triggers

Jim Nasby wrote:

On 2/6/14, 11:20 AM, Alvaro Herrera wrote:

NOTICE: JSON blob: {
"definition": [
{
"clause": "owned",
"fmt": "OWNED BY %{owner}D",
"owner": {
"attrname": "a",
"objname": "t1",
"schemaname": "public"
}
}
],
"fmt": "ALTER SEQUENCE %{identity}D %{definition: }s",
"identity": {
"objname": "t1_a_seq",
"schemaname": "public"
}
}
NOTICE: expanded: ALTER SEQUENCE public.t1_a_seq OWNED BY public.t1.a

Apologies if this has been discussed and I missed it, but shouldn't part of the JSON be a field that indicates what command is being run? It doesn't seem wise to conflate detecting what the command is with the overall format string.

That's reported as a separate field by the
pg_event_trigger_creation_commands function.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#49)
Re: Add CREATE support to event triggers

On Sat, Jun 14, 2014 at 5:31 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Here's a refreshed version of this patch. I have split it up in a
largish number of pieces, which hopefully makes it easier to understand
what is going on.

Alvaro,

Could you confirm that the patches you just committed are 1, 3 and 6?

Regards,
--
Michael

#53Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#52)
Re: Add CREATE support to event triggers

Michael Paquier wrote:

On Sat, Jun 14, 2014 at 5:31 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Here's a refreshed version of this patch. I have split it up in a
largish number of pieces, which hopefully makes it easier to understand
what is going on.

Alvaro,

Could you confirm that the patches you just committed are 1, 3 and 6?

And 4. Yes, they are. I wanted to get trivial stuff out of the way
while I had some other trivial patch at hand. I'm dealing with another
patch from the commitfest now, so I'm not posting a rebased version
right away, apologies.

How do people like this patch series? It would be much easier for me to
submit a single patch, but I feel handing it in little pieces makes it
easier for reviewers.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#53)
Re: Add CREATE support to event triggers

On Tue, Aug 26, 2014 at 5:33 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Michael Paquier wrote:
And 4. Yes, they are. I wanted to get trivial stuff out of the way
while I had some other trivial patch at hand. I'm dealing with another
patch from the commitfest now, so I'm not posting a rebased version
right away, apologies.

No problems. I imagine that most of the patches still apply.

How do people like this patch series? It would be much easier for me to
submit a single patch, but I feel handing it in little pieces makes it
easier for reviewers.

Well, I like the patch series for what it counts as long as you can
submit it as such. That's far easier to test and certainly helps in
spotting issues when kicking different code paths.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#54)
Re: Add CREATE support to event triggers

On Tue, Aug 26, 2014 at 8:10 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Well, I like the patch series for what it counts as long as you can
submit it as such. That's far easier to test and certainly helps in
spotting issues when kicking different code paths.

So, for patch 2, which is a cosmetic change for
pg_event_trigger_dropped_objects:
=# select pg_event_trigger_dropped_objects();
ERROR: 0A000: pg_event_trigger_dropped_objects can only be called in
a sql_drop event trigger function
LOCATION: pg_event_trigger_dropped_objects, event_trigger.c:1220
This drops "()" from the error message with the function name. I guess
that this is fine. But PG_FUNCNAME_MACRO is used nowhere except
elog.h, and can as well be NULL. So if that's really necessary
shouldn't we use FunctionCallInfo instead. It is not as well not that
bad to hardcode the function name in the error message as well IMO.

For patch 5:
+1 for this move. When working on Postgres-XC a couple of years back I
wondered why this distinction was not made. Wouldn't it make sense to
move as well the declaration of quote_all_identifiers to ruleutils.h.
That's a GUC and moving it out of builtins.h would make sense IMO.

Patch 8 needs a rebase (patch independent on 1~6 it seems):
1 out of 57 hunks FAILED -- saving rejects to file
src/backend/commands/tablecmds.c.rej
(Stripping trailing CRs from patch.)

Patch 9:
1) It needs a rebase, AlterTableMoveAllStmt has been renamed to
AlterTableMoveAllStmt by commit 3c4cf08
2) Table summarizing event trigger firing needs to be updated with the
new command supported (src/sgml/event-trigger.sgml)

Patch 10, similar problems as patch 9:
1) Needs a rebase
2) table summarizing supported commands should be updated.
You could directly group patches 9 and 10 in the final commit IMO.
GRANT/REVOKE would also be the first command that would be supported
by event triggers that is not of the type CREATE/DROP/ALTER, hence
once it is rebased I would like to do some testing with it (same with
patch 9 btw) and see how it reacts with the event sql_drop
particularly (it should error out but still).

Patch 11: applies with some hunks.
So... This patch introduces an operation performing doing reverse
engineering of format_type_internal... I think that this would be more
adapted with a couple of new routines in lsyscache.[c|h] instead:
- One for the type name
- One for typmodout
- One for is_array
- One for its namespace
TBH, I wanted those routines a couple of times when working on XC and
finished coding them at the end, but in XC only :)

Patch 12: patch applies correctly.
Form_pg_sequence is already exposed in sequence.h even if it is only
used in sequence.c, so yes it seems to be the correct way to do it
here assuming that we need this data to rebuild a DDL. Why is
ACL_UPDATE needed when checking permissions? This new routine only
reads the values and does not update it. And a confirmation: ACL_USAGE
is used to make this routine usable for PL languages in this case,
right?
I think that you should mention at the top of get_sequence_values that
it returns a palloc'd result, and that it is the responsibility of
caller to free it.

That's all I have for now.
Regards,
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#55)
Re: Add CREATE support to event triggers

On Wed, Aug 27, 2014 at 1:10 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Tue, Aug 26, 2014 at 8:10 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Well, I like the patch series for what it counts as long as you can
submit it as such. That's far easier to test and certainly helps in
spotting issues when kicking different code paths.

So, for patch 2, which is a cosmetic change for
pg_event_trigger_dropped_objects:
=# select pg_event_trigger_dropped_objects();
ERROR: 0A000: pg_event_trigger_dropped_objects can only be called in
a sql_drop event trigger function
LOCATION: pg_event_trigger_dropped_objects, event_trigger.c:1220
This drops "()" from the error message with the function name. I guess
that this is fine. But PG_FUNCNAME_MACRO is used nowhere except
elog.h, and can as well be NULL. So if that's really necessary
shouldn't we use FunctionCallInfo instead. It is not as well not that
bad to hardcode the function name in the error message as well IMO.

For patch 5:
+1 for this move. When working on Postgres-XC a couple of years back I
wondered why this distinction was not made. Wouldn't it make sense to
move as well the declaration of quote_all_identifiers to ruleutils.h.
That's a GUC and moving it out of builtins.h would make sense IMO.

Patch 8 needs a rebase (patch independent on 1~6 it seems):
1 out of 57 hunks FAILED -- saving rejects to file
src/backend/commands/tablecmds.c.rej
(Stripping trailing CRs from patch.)

Patch 9:
1) It needs a rebase, AlterTableMoveAllStmt has been renamed to
AlterTableMoveAllStmt by commit 3c4cf08
2) Table summarizing event trigger firing needs to be updated with the
new command supported (src/sgml/event-trigger.sgml)

Patch 10, similar problems as patch 9:
1) Needs a rebase
2) table summarizing supported commands should be updated.
You could directly group patches 9 and 10 in the final commit IMO.
GRANT/REVOKE would also be the first command that would be supported
by event triggers that is not of the type CREATE/DROP/ALTER, hence
once it is rebased I would like to do some testing with it (same with
patch 9 btw) and see how it reacts with the event sql_drop
particularly (it should error out but still).

Patch 11: applies with some hunks.
So... This patch introduces an operation performing doing reverse
engineering of format_type_internal... I think that this would be more
adapted with a couple of new routines in lsyscache.[c|h] instead:
- One for the type name
- One for typmodout
- One for is_array
- One for its namespace
TBH, I wanted those routines a couple of times when working on XC and
finished coding them at the end, but in XC only :)

Patch 12: patch applies correctly.
Form_pg_sequence is already exposed in sequence.h even if it is only
used in sequence.c, so yes it seems to be the correct way to do it
here assuming that we need this data to rebuild a DDL. Why is
ACL_UPDATE needed when checking permissions? This new routine only
reads the values and does not update it. And a confirmation: ACL_USAGE
is used to make this routine usable for PL languages in this case,
right?
I think that you should mention at the top of get_sequence_values that
it returns a palloc'd result, and that it is the responsibility of
caller to free it.

And a last one before lunch, closing the review for all the basic things...
Patch 13: Could you explain why this is necessary?
+extern PGDLLIMPORT bool creating_extension;
It may make sense by looking at the core features (then why isn't it
with the core features?), but I am trying to review the patches in
order.
-- 
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#56)
Re: Add CREATE support to event triggers

On Tue, Aug 26, 2014 at 11:14 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

And a last one before lunch, closing the review for all the basic things...
Patch 13: Could you explain why this is necessary?
+extern PGDLLIMPORT bool creating_extension;
It may make sense by looking at the core features (then why isn't it
with the core features?), but I am trying to review the patches in
order.

Those patches have been reviewed up to number 14. Some of them could
be applied as-is as they are useful taken independently, but most of
them need a rebase, hence marking it as returned with feedback.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers